From 13456d1fcd0122d8464c3c3e1c356d86a56e6c08 Mon Sep 17 00:00:00 2001 From: Erik Andersen Date: Thu, 16 Mar 2000 08:09:57 +0000 Subject: [PATCH] Forgot these files... -Erik --- cmdedit.c | 405 ++++++++++++++++++++++++++++++++ cmdedit.h | 17 ++ coreutils/echo.c | 126 ++++++++++ coreutils/test.c | 583 +++++++++++++++++++++++++++++++++++++++++++++++ echo.c | 126 ++++++++++ shell/cmdedit.c | 405 ++++++++++++++++++++++++++++++++ shell/cmdedit.h | 17 ++ test.c | 583 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 2262 insertions(+) create mode 100644 cmdedit.c create mode 100644 cmdedit.h create mode 100644 coreutils/echo.c create mode 100644 coreutils/test.c create mode 100644 echo.c create mode 100644 shell/cmdedit.c create mode 100644 shell/cmdedit.h create mode 100644 test.c diff --git a/cmdedit.c b/cmdedit.c new file mode 100644 index 000000000..d1604f1d1 --- /dev/null +++ b/cmdedit.c @@ -0,0 +1,405 @@ +/* + * Termios command line History and Editting for NetBSD sh (ash) + * Copyright (c) 1999 + * Main code: Adam Rogoyski + * Etc: Dave Cinege + * Adjusted for busybox: Erik Andersen + * + * You may use this code as you wish, so long as the original author(s) + * are attributed in any redistributions of the source code. + * This code is 'as is' with no warranty. + * This code may safely be consumed by a BSD or GPL license. + * + * v 0.5 19990328 Initial release + * + * Future plans: Simple file and path name completion. (like BASH) + * + */ + +/* + Usage and Known bugs: + Terminal key codes are not extensive, and more will probably + need to be added. This version was created on Debian GNU/Linux 2.x. + Delete, Backspace, Home, End, and the arrow keys were tested + to work in an Xterm and console. Ctrl-A also works as Home. + Ctrl-E also works as End. The binary size increase is <3K. + + Editting will not display correctly for lines greater then the + terminal width. (more then one line.) However, history will. + */ + +#include "internal.h" +#ifdef BB_FEATURE_SH_COMMAND_EDITING + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cmdedit.h" + + +#define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */ + +#define ESC 27 +#define DEL 127 + +static struct history *his_front = NULL; /* First element in command line list */ +static struct history *his_end = NULL; /* Last element in command line list */ +static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */ + +static int history_counter = 0; /* Number of commands in history list */ +static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */ +char *parsenextc; /* copy of parsefile->nextc */ + +struct history { + char *s; + struct history *p; + struct history *n; +}; + + +/* Version of write which resumes after a signal is caught. */ +int xwrite(int fd, char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + for (;;) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } +} + + +/* Version of ioctl that retries after a signal is caught. */ +int xioctl(int fd, unsigned long request, char *arg) +{ + int i; + while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR); + return i; +} + + +void cmdedit_reset_term(void) +{ + if (reset_term) + xioctl(fileno(stdin), TCSETA, (void *) &old_term); +} + +void gotaSignal(int sig) +{ + cmdedit_reset_term(); + fprintf(stdout, "\n"); + exit(TRUE); +} + +void input_home(int outputFd, int *cursor) +{ /* Command line input routines */ + while (*cursor > 0) { + xwrite(outputFd, "\b", 1); + --*cursor; + } +} + + +void input_delete(int outputFd, int cursor) +{ + int j = 0; + + memmove(parsenextc + cursor, parsenextc + cursor + 1, + BUFSIZ - cursor - 1); + for (j = cursor; j < (BUFSIZ - 1); j++) { + if (!*(parsenextc + j)) + break; + else + xwrite(outputFd, (parsenextc + j), 1); + } + + xwrite(outputFd, " \b", 2); + + while (j-- > cursor) + xwrite(outputFd, "\b", 1); +} + + +void input_end(int outputFd, int *cursor, int len) +{ + while (*cursor < len) { + xwrite(outputFd, "\033[C", 3); + ++*cursor; + } +} + + +void input_backspace(int outputFd, int *cursor, int *len) +{ + int j = 0; + + if (*cursor > 0) { + xwrite(outputFd, "\b \b", 3); + --*cursor; + memmove(parsenextc + *cursor, parsenextc + *cursor + 1, + BUFSIZ - *cursor + 1); + + for (j = *cursor; j < (BUFSIZ - 1); j++) { + if (!*(parsenextc + j)) + break; + else + xwrite(outputFd, (parsenextc + j), 1); + } + + xwrite(outputFd, " \b", 2); + + while (j-- > *cursor) + xwrite(outputFd, "\b", 1); + + --*len; + } +} + +extern int cmdedit_read_input(int inputFd, int outputFd, + char command[BUFSIZ]) +{ + + int nr = 0; + int len = 0; + int j = 0; + int cursor = 0; + int break_out = 0; + int ret = 0; + char c = 0; + struct history *hp = his_end; + + memset(command, 0, sizeof(command)); + parsenextc = command; + if (!reset_term) { + xioctl(inputFd, TCGETA, (void *) &old_term); + memcpy(&new_term, &old_term, sizeof(struct termio)); + new_term.c_cc[VMIN] = 1; + new_term.c_cc[VTIME] = 0; + new_term.c_lflag &= ~ICANON; /* unbuffered input */ + new_term.c_lflag &= ~ECHO; + xioctl(inputFd, TCSETA, (void *) &new_term); + reset_term = 1; + } else { + xioctl(inputFd, TCSETA, (void *) &new_term); + } + + memset(parsenextc, 0, BUFSIZ); + + while (1) { + + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + + switch (c) { + case 1: /* Control-A Beginning of line */ + input_home(outputFd, &cursor); + break; + case 5: /* Control-E EOL */ + input_end(outputFd, &cursor, len); + break; + case 4: /* Control-D */ + if (cursor != len) { + input_delete(outputFd, cursor); + len--; + } + break; + case '\b': /* Backspace */ + case DEL: + input_backspace(outputFd, &cursor, &len); + break; + case '\n': /* Enter */ + *(parsenextc + len++ + 1) = c; + xwrite(outputFd, &c, 1); + break_out = 1; + break; + case ESC: /* escape sequence follows */ + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + + if (c == '[') { /* 91 */ + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + + switch (c) { + case 'A': + if (hp && hp->p) { /* Up */ + hp = hp->p; + goto hop; + } + break; + case 'B': + if (hp && hp->n && hp->n->s) { /* Down */ + hp = hp->n; + goto hop; + } + break; + + hop: /* hop */ + len = strlen(parsenextc); + + for (; cursor > 0; cursor--) /* return to begining of line */ + xwrite(outputFd, "\b", 1); + + for (j = 0; j < len; j++) /* erase old command */ + xwrite(outputFd, " ", 1); + + for (j = len; j > 0; j--) /* return to begining of line */ + xwrite(outputFd, "\b", 1); + + strcpy(parsenextc, hp->s); /* write new command */ + len = strlen(hp->s); + xwrite(outputFd, parsenextc, len); + cursor = len; + break; + case 'C': /* Right */ + if (cursor < len) { + xwrite(outputFd, "\033[C", 3); + cursor++; + } + break; + case 'D': /* Left */ + if (cursor > 0) { + xwrite(outputFd, "\033[D", 3); + cursor--; + } + break; + case '3': /* Delete */ + if (cursor != len) { + input_delete(outputFd, cursor); + len--; + } + break; + case '1': /* Home (Ctrl-A) */ + input_home(outputFd, &cursor); + break; + case '4': /* End (Ctrl-E) */ + input_end(outputFd, &cursor, len); + break; + } + if (c == '1' || c == '3' || c == '4') + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; /* read 126 (~) */ + } + if (c == 'O') { /* 79 */ + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + switch (c) { + case 'H': /* Home (xterm) */ + input_home(outputFd, &cursor); + break; + case 'F': /* End (xterm) */ + input_end(outputFd, &cursor, len); + break; + } + } + c = 0; + break; + + default: /* If it's regular input, do the normal thing */ + + if (!isprint(c)) /* Skip non-printable characters */ + break; + + if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ + break; + + len++; + + if (cursor == (len - 1)) { /* Append if at the end of the line */ + *(parsenextc + cursor) = c; + } else { /* Insert otherwise */ + memmove(parsenextc + cursor + 1, parsenextc + cursor, + len - cursor - 1); + + *(parsenextc + cursor) = c; + + for (j = cursor; j < len; j++) + xwrite(outputFd, parsenextc + j, 1); + for (; j > cursor; j--) + xwrite(outputFd, "\033[D", 3); + } + + cursor++; + xwrite(outputFd, &c, 1); + break; + } + + if (break_out) /* Enter is the command terminator, no more input. */ + break; + } + + nr = len + 1; + xioctl(inputFd, TCSETA, (void *) &old_term); + reset_term = 0; + + + if (*(parsenextc)) { /* Handle command history log */ + + struct history *h = his_end; + + if (!h) { /* No previous history */ + h = his_front = malloc(sizeof(struct history)); + h->n = malloc(sizeof(struct history)); + h->p = NULL; + h->s = strdup(parsenextc); + + h->n->p = h; + h->n->n = NULL; + h->n->s = NULL; + his_end = h->n; + history_counter++; + } else { /* Add a new history command */ + + h->n = malloc(sizeof(struct history)); + + h->n->p = h; + h->n->n = NULL; + h->n->s = NULL; + h->s = strdup(parsenextc); + his_end = h->n; + + if (history_counter >= MAX_HISTORY) { /* After max history, remove the last known command */ + + struct history *p = his_front->n; + + p->p = NULL; + free(his_front->s); + free(his_front); + his_front = p; + } else { + history_counter++; + } + } + } + + return nr; +} + +extern void cmdedit_init(void) +{ + atexit(cmdedit_reset_term); + signal(SIGINT, gotaSignal); + signal(SIGQUIT, gotaSignal); + signal(SIGTERM, gotaSignal); +} +#endif /* BB_FEATURE_SH_COMMAND_EDITING */ diff --git a/cmdedit.h b/cmdedit.h new file mode 100644 index 000000000..e776543d6 --- /dev/null +++ b/cmdedit.h @@ -0,0 +1,17 @@ +/* + * Termios command line History and Editting for NetBSD sh (ash) + * Copyright (c) 1999 + * Main code: Adam Rogoyski + * Etc: Dave Cinege + * Adjusted for busybox: Erik Andersen + * + * You may use this code as you wish, so long as the original author(s) + * are attributed in any redistributions of the source code. + * This code is 'as is' with no warranty. + * This code may safely be consumed by a BSD or GPL license. + * + */ + +extern int cmdedit_read_input(int inputFd, int outputFd, char command[BUFSIZ]); +extern void cmdedit_init(void); + diff --git a/coreutils/echo.c b/coreutils/echo.c new file mode 100644 index 000000000..91f17aa0f --- /dev/null +++ b/coreutils/echo.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * echo implementation for busybox + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original copyright notice is retained at the end of this file. + */ + +#include "internal.h" +#include + +extern int +echo_main(int argc, char** argv) +{ + register char **ap; + register char *p; + register char c; + int nflag = 0; + int eflag = 0; + + ap = argv; + if (argc) + ap++; + while ((p = *ap) != NULL && *p == '-') { + if (strcmp(p, "-n")==0) { + nflag = 1; + } else if (strcmp(p, "-e")==0) { + eflag = 1; + } else if (strcmp(p, "-E")==0) { + eflag = 0; + } + else break; + ap++; + } + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (c = *p++) { + case 'a': c = '\007'; break; + case 'b': c = '\b'; break; + case 'c': exit( 0); /* exit */ + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if (*p >= '0' && *p <= '7') + c = c * 8 + (*p++ - '0'); + if (*p >= '0' && *p <= '7') + c = c * 8 + (*p++ - '0'); + break; + default: + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + fflush(stdout); + exit( 0); +} + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)echo.c 8.1 (Berkeley) 5/31/93 + */ + + diff --git a/coreutils/test.c b/coreutils/test.c new file mode 100644 index 000000000..85d06a83a --- /dev/null +++ b/coreutils/test.c @@ -0,0 +1,583 @@ +/* vi: set sw=4 ts=4: */ +/* + * echo implementation for busybox + * + * Copyright (c) by a whole pile of folks: + * + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. + * modified by Herbert Xu to be used as built-in in ash. + * modified by Erik Andersen to be used + * in busybox. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original copyright notice states: + * "This program is in the Public Domain." + */ + +#include "internal.h" +#include +#include +#include +#include +#include +#include +#include + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; + + binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= +*/ + +enum token { + EOI, + FILRD, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + FILNT, + FILOT, + FILEQ, + FILUID, + FILGID, + STREZ, + STRNZ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT, + BAND, + BOR, + LPAREN, + RPAREN, + OPERAND +}; + +enum token_types { + UNOP, + BINOP, + BUNOP, + BBINOP, + PAREN +}; + +struct t_op { + const char *op_text; + short op_num, op_type; +} const ops [] = { + {"-r", FILRD, UNOP}, + {"-w", FILWR, UNOP}, + {"-x", FILEX, UNOP}, + {"-e", FILEXIST,UNOP}, + {"-f", FILREG, UNOP}, + {"-d", FILDIR, UNOP}, + {"-c", FILCDEV,UNOP}, + {"-b", FILBDEV,UNOP}, + {"-p", FILFIFO,UNOP}, + {"-u", FILSUID,UNOP}, + {"-g", FILSGID,UNOP}, + {"-k", FILSTCK,UNOP}, + {"-s", FILGZ, UNOP}, + {"-t", FILTT, UNOP}, + {"-z", STREZ, UNOP}, + {"-n", STRNZ, UNOP}, + {"-h", FILSYM, UNOP}, /* for backwards compat */ + {"-O", FILUID, UNOP}, + {"-G", FILGID, UNOP}, + {"-L", FILSYM, UNOP}, + {"-S", FILSOCK,UNOP}, + {"=", STREQ, BINOP}, + {"!=", STRNE, BINOP}, + {"<", STRLT, BINOP}, + {">", STRGT, BINOP}, + {"-eq", INTEQ, BINOP}, + {"-ne", INTNE, BINOP}, + {"-ge", INTGE, BINOP}, + {"-gt", INTGT, BINOP}, + {"-le", INTLE, BINOP}, + {"-lt", INTLT, BINOP}, + {"-nt", FILNT, BINOP}, + {"-ot", FILOT, BINOP}, + {"-ef", FILEQ, BINOP}, + {"!", UNOT, BUNOP}, + {"-a", BAND, BBINOP}, + {"-o", BOR, BBINOP}, + {"(", LPAREN, PAREN}, + {")", RPAREN, PAREN}, + {0, 0, 0} +}; + +char **t_wp; +struct t_op const *t_wp_op; +static gid_t *group_array = NULL; +static int ngroups; + +static enum token t_lex(); +static int oexpr(); +static int aexpr(); +static int nexpr(); +static int binop(); +static int primary(); +static int filstat(); +static int getn(); +static int newerf(); +static int olderf(); +static int equalf(); +static void syntax(); +static int test_eaccess(); +static int is_a_group_member(); +static void initialize_group_array(); + +extern int +test_main(int argc, char** argv) +{ + int res; + + if (strcmp(argv[0], "[") == 0) { + if (strcmp(argv[--argc], "]")) + fatalError("missing ]"); + argv[argc] = NULL; + } + + /* Implement special cases from POSIX.2, section 4.62.4 */ + switch (argc) { + case 1: + exit( 1); + case 2: + exit (*argv[1] == '\0'); + case 3: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + exit (!(*argv[2] == '\0')); + } + break; + case 4: + if (argv[1][0] != '!' || argv[1][1] != '\0') { + if (t_lex(argv[2]), + t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[1]; + exit (binop() == 0); + } + } + break; + case 5: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + if (t_lex(argv[3]), + t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[2]; + exit (!(binop() == 0)); + } + } + break; + } + + t_wp = &argv[1]; + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) + syntax(*t_wp, "unknown operand"); + + exit( res); +} + +static void +syntax(op, msg) + char *op; + char *msg; +{ + if (op && *op) + fatalError("%s: %s", op, msg); + else + fatalError("%s", msg); +} + +static int +oexpr(n) + enum token n; +{ + int res; + + res = aexpr(n); + if (t_lex(*++t_wp) == BOR) + return oexpr(t_lex(*++t_wp)) || res; + t_wp--; + return res; +} + +static int +aexpr(n) + enum token n; +{ + int res; + + res = nexpr(n); + if (t_lex(*++t_wp) == BAND) + return aexpr(t_lex(*++t_wp)) && res; + t_wp--; + return res; +} + +static int +nexpr(n) + enum token n; /* token */ +{ + if (n == UNOT) + return !nexpr(t_lex(*++t_wp)); + return primary(n); +} + +static int +primary(n) + enum token n; +{ + int res; + + if (n == EOI) + syntax(NULL, "argument expected"); + if (n == LPAREN) { + res = oexpr(t_lex(*++t_wp)); + if (t_lex(*++t_wp) != RPAREN) + syntax(NULL, "closing paren expected"); + return res; + } + if (t_wp_op && t_wp_op->op_type == UNOP) { + /* unary expression */ + if (*++t_wp == NULL) + syntax(t_wp_op->op_text, "argument expected"); + switch (n) { + case STREZ: + return strlen(*t_wp) == 0; + case STRNZ: + return strlen(*t_wp) != 0; + case FILTT: + return isatty(getn(*t_wp)); + default: + return filstat(*t_wp, n); + } + } + + if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { + return binop(); + } + + return strlen(*t_wp) > 0; +} + +static int +binop() +{ + const char *opnd1, *opnd2; + struct t_op const *op; + + opnd1 = *t_wp; + (void) t_lex(*++t_wp); + op = t_wp_op; + + if ((opnd2 = *++t_wp) == (char *)0) + syntax(op->op_text, "argument expected"); + + switch (op->op_num) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return getn(opnd1) == getn(opnd2); + case INTNE: + return getn(opnd1) != getn(opnd2); + case INTGE: + return getn(opnd1) >= getn(opnd2); + case INTGT: + return getn(opnd1) > getn(opnd2); + case INTLE: + return getn(opnd1) <= getn(opnd2); + case INTLT: + return getn(opnd1) < getn(opnd2); + case FILNT: + return newerf (opnd1, opnd2); + case FILOT: + return olderf (opnd1, opnd2); + case FILEQ: + return equalf (opnd1, opnd2); + } + /* NOTREACHED */ + return 1; +} + +static int +filstat(nm, mode) + char *nm; + enum token mode; +{ + struct stat s; + int i; + + if (mode == FILSYM) { +#ifdef S_IFLNK + if (lstat(nm, &s) == 0) { + i = S_IFLNK; + goto filetype; + } +#endif + return 0; + } + + if (stat(nm, &s) != 0) + return 0; + + switch (mode) { + case FILRD: + return test_eaccess(nm, R_OK) == 0; + case FILWR: + return test_eaccess(nm, W_OK) == 0; + case FILEX: + return test_eaccess(nm, X_OK) == 0; + case FILEXIST: + return 1; + case FILREG: + i = S_IFREG; + goto filetype; + case FILDIR: + i = S_IFDIR; + goto filetype; + case FILCDEV: + i = S_IFCHR; + goto filetype; + case FILBDEV: + i = S_IFBLK; + goto filetype; + case FILFIFO: +#ifdef S_IFIFO + i = S_IFIFO; + goto filetype; +#else + return 0; +#endif + case FILSOCK: +#ifdef S_IFSOCK + i = S_IFSOCK; + goto filetype; +#else + return 0; +#endif + case FILSUID: + i = S_ISUID; + goto filebit; + case FILSGID: + i = S_ISGID; + goto filebit; + case FILSTCK: + i = S_ISVTX; + goto filebit; + case FILGZ: + return s.st_size > 0L; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } + +filetype: + return ((s.st_mode & S_IFMT) == i); + +filebit: + return ((s.st_mode & i) != 0); +} + +static enum token +t_lex(s) + char *s; +{ + struct t_op const *op = ops; + + if (s == 0) { + t_wp_op = (struct t_op *)0; + return EOI; + } + while (op->op_text) { + if (strcmp(s, op->op_text) == 0) { + t_wp_op = op; + return op->op_num; + } + op++; + } + t_wp_op = (struct t_op *)0; + return OPERAND; +} + +/* atoi with error detection */ +static int +getn(s) + char *s; +{ + char *p; + long r; + + errno = 0; + r = strtol(s, &p, 10); + + if (errno != 0) + fatalError("%s: out of range", s); + + while (isspace(*p)) + p++; + + if (*p) + fatalError("%s: bad number", s); + + return (int) r; +} + +static int +newerf (f1, f2) +char *f1, *f2; +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_mtime > b2.st_mtime); +} + +static int +olderf (f1, f2) +char *f1, *f2; +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_mtime < b2.st_mtime); +} + +static int +equalf (f1, f2) +char *f1, *f2; +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); +} + +/* Do the same thing access(2) does, but use the effective uid and gid, + and don't make the mistake of telling root that any file is + executable. */ +static int +test_eaccess (path, mode) +char *path; +int mode; +{ + struct stat st; + int euid = geteuid(); + + if (stat (path, &st) < 0) + return (-1); + + if (euid == 0) { + /* Root can read or write any file. */ + if (mode != X_OK) + return (0); + + /* Root can execute any file that has any one of the execute + bits set. */ + if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return (0); + } + + if (st.st_uid == euid) /* owner */ + mode <<= 6; + else if (is_a_group_member (st.st_gid)) + mode <<= 3; + + if (st.st_mode & mode) + return (0); + + return (-1); +} + +static void +initialize_group_array () +{ + ngroups = getgroups(0, NULL); + if ((group_array = realloc(group_array, ngroups * sizeof(gid_t))) == NULL) + fatalError("Out of space"); + + getgroups(ngroups, group_array); +} + +/* Return non-zero if GID is one that we have in our groups list. */ +static int +is_a_group_member (gid) +gid_t gid; +{ + register int i; + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == getgid() || gid == getegid()) + return (1); + + if (ngroups == 0) + initialize_group_array (); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == group_array[i]) + return (1); + + return (0); +} diff --git a/echo.c b/echo.c new file mode 100644 index 000000000..91f17aa0f --- /dev/null +++ b/echo.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * echo implementation for busybox + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original copyright notice is retained at the end of this file. + */ + +#include "internal.h" +#include + +extern int +echo_main(int argc, char** argv) +{ + register char **ap; + register char *p; + register char c; + int nflag = 0; + int eflag = 0; + + ap = argv; + if (argc) + ap++; + while ((p = *ap) != NULL && *p == '-') { + if (strcmp(p, "-n")==0) { + nflag = 1; + } else if (strcmp(p, "-e")==0) { + eflag = 1; + } else if (strcmp(p, "-E")==0) { + eflag = 0; + } + else break; + ap++; + } + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (c = *p++) { + case 'a': c = '\007'; break; + case 'b': c = '\b'; break; + case 'c': exit( 0); /* exit */ + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if (*p >= '0' && *p <= '7') + c = c * 8 + (*p++ - '0'); + if (*p >= '0' && *p <= '7') + c = c * 8 + (*p++ - '0'); + break; + default: + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + fflush(stdout); + exit( 0); +} + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)echo.c 8.1 (Berkeley) 5/31/93 + */ + + diff --git a/shell/cmdedit.c b/shell/cmdedit.c new file mode 100644 index 000000000..d1604f1d1 --- /dev/null +++ b/shell/cmdedit.c @@ -0,0 +1,405 @@ +/* + * Termios command line History and Editting for NetBSD sh (ash) + * Copyright (c) 1999 + * Main code: Adam Rogoyski + * Etc: Dave Cinege + * Adjusted for busybox: Erik Andersen + * + * You may use this code as you wish, so long as the original author(s) + * are attributed in any redistributions of the source code. + * This code is 'as is' with no warranty. + * This code may safely be consumed by a BSD or GPL license. + * + * v 0.5 19990328 Initial release + * + * Future plans: Simple file and path name completion. (like BASH) + * + */ + +/* + Usage and Known bugs: + Terminal key codes are not extensive, and more will probably + need to be added. This version was created on Debian GNU/Linux 2.x. + Delete, Backspace, Home, End, and the arrow keys were tested + to work in an Xterm and console. Ctrl-A also works as Home. + Ctrl-E also works as End. The binary size increase is <3K. + + Editting will not display correctly for lines greater then the + terminal width. (more then one line.) However, history will. + */ + +#include "internal.h" +#ifdef BB_FEATURE_SH_COMMAND_EDITING + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cmdedit.h" + + +#define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */ + +#define ESC 27 +#define DEL 127 + +static struct history *his_front = NULL; /* First element in command line list */ +static struct history *his_end = NULL; /* Last element in command line list */ +static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */ + +static int history_counter = 0; /* Number of commands in history list */ +static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */ +char *parsenextc; /* copy of parsefile->nextc */ + +struct history { + char *s; + struct history *p; + struct history *n; +}; + + +/* Version of write which resumes after a signal is caught. */ +int xwrite(int fd, char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + for (;;) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } +} + + +/* Version of ioctl that retries after a signal is caught. */ +int xioctl(int fd, unsigned long request, char *arg) +{ + int i; + while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR); + return i; +} + + +void cmdedit_reset_term(void) +{ + if (reset_term) + xioctl(fileno(stdin), TCSETA, (void *) &old_term); +} + +void gotaSignal(int sig) +{ + cmdedit_reset_term(); + fprintf(stdout, "\n"); + exit(TRUE); +} + +void input_home(int outputFd, int *cursor) +{ /* Command line input routines */ + while (*cursor > 0) { + xwrite(outputFd, "\b", 1); + --*cursor; + } +} + + +void input_delete(int outputFd, int cursor) +{ + int j = 0; + + memmove(parsenextc + cursor, parsenextc + cursor + 1, + BUFSIZ - cursor - 1); + for (j = cursor; j < (BUFSIZ - 1); j++) { + if (!*(parsenextc + j)) + break; + else + xwrite(outputFd, (parsenextc + j), 1); + } + + xwrite(outputFd, " \b", 2); + + while (j-- > cursor) + xwrite(outputFd, "\b", 1); +} + + +void input_end(int outputFd, int *cursor, int len) +{ + while (*cursor < len) { + xwrite(outputFd, "\033[C", 3); + ++*cursor; + } +} + + +void input_backspace(int outputFd, int *cursor, int *len) +{ + int j = 0; + + if (*cursor > 0) { + xwrite(outputFd, "\b \b", 3); + --*cursor; + memmove(parsenextc + *cursor, parsenextc + *cursor + 1, + BUFSIZ - *cursor + 1); + + for (j = *cursor; j < (BUFSIZ - 1); j++) { + if (!*(parsenextc + j)) + break; + else + xwrite(outputFd, (parsenextc + j), 1); + } + + xwrite(outputFd, " \b", 2); + + while (j-- > *cursor) + xwrite(outputFd, "\b", 1); + + --*len; + } +} + +extern int cmdedit_read_input(int inputFd, int outputFd, + char command[BUFSIZ]) +{ + + int nr = 0; + int len = 0; + int j = 0; + int cursor = 0; + int break_out = 0; + int ret = 0; + char c = 0; + struct history *hp = his_end; + + memset(command, 0, sizeof(command)); + parsenextc = command; + if (!reset_term) { + xioctl(inputFd, TCGETA, (void *) &old_term); + memcpy(&new_term, &old_term, sizeof(struct termio)); + new_term.c_cc[VMIN] = 1; + new_term.c_cc[VTIME] = 0; + new_term.c_lflag &= ~ICANON; /* unbuffered input */ + new_term.c_lflag &= ~ECHO; + xioctl(inputFd, TCSETA, (void *) &new_term); + reset_term = 1; + } else { + xioctl(inputFd, TCSETA, (void *) &new_term); + } + + memset(parsenextc, 0, BUFSIZ); + + while (1) { + + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + + switch (c) { + case 1: /* Control-A Beginning of line */ + input_home(outputFd, &cursor); + break; + case 5: /* Control-E EOL */ + input_end(outputFd, &cursor, len); + break; + case 4: /* Control-D */ + if (cursor != len) { + input_delete(outputFd, cursor); + len--; + } + break; + case '\b': /* Backspace */ + case DEL: + input_backspace(outputFd, &cursor, &len); + break; + case '\n': /* Enter */ + *(parsenextc + len++ + 1) = c; + xwrite(outputFd, &c, 1); + break_out = 1; + break; + case ESC: /* escape sequence follows */ + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + + if (c == '[') { /* 91 */ + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + + switch (c) { + case 'A': + if (hp && hp->p) { /* Up */ + hp = hp->p; + goto hop; + } + break; + case 'B': + if (hp && hp->n && hp->n->s) { /* Down */ + hp = hp->n; + goto hop; + } + break; + + hop: /* hop */ + len = strlen(parsenextc); + + for (; cursor > 0; cursor--) /* return to begining of line */ + xwrite(outputFd, "\b", 1); + + for (j = 0; j < len; j++) /* erase old command */ + xwrite(outputFd, " ", 1); + + for (j = len; j > 0; j--) /* return to begining of line */ + xwrite(outputFd, "\b", 1); + + strcpy(parsenextc, hp->s); /* write new command */ + len = strlen(hp->s); + xwrite(outputFd, parsenextc, len); + cursor = len; + break; + case 'C': /* Right */ + if (cursor < len) { + xwrite(outputFd, "\033[C", 3); + cursor++; + } + break; + case 'D': /* Left */ + if (cursor > 0) { + xwrite(outputFd, "\033[D", 3); + cursor--; + } + break; + case '3': /* Delete */ + if (cursor != len) { + input_delete(outputFd, cursor); + len--; + } + break; + case '1': /* Home (Ctrl-A) */ + input_home(outputFd, &cursor); + break; + case '4': /* End (Ctrl-E) */ + input_end(outputFd, &cursor, len); + break; + } + if (c == '1' || c == '3' || c == '4') + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; /* read 126 (~) */ + } + if (c == 'O') { /* 79 */ + if ((ret = read(inputFd, &c, 1)) < 1) + return ret; + switch (c) { + case 'H': /* Home (xterm) */ + input_home(outputFd, &cursor); + break; + case 'F': /* End (xterm) */ + input_end(outputFd, &cursor, len); + break; + } + } + c = 0; + break; + + default: /* If it's regular input, do the normal thing */ + + if (!isprint(c)) /* Skip non-printable characters */ + break; + + if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ + break; + + len++; + + if (cursor == (len - 1)) { /* Append if at the end of the line */ + *(parsenextc + cursor) = c; + } else { /* Insert otherwise */ + memmove(parsenextc + cursor + 1, parsenextc + cursor, + len - cursor - 1); + + *(parsenextc + cursor) = c; + + for (j = cursor; j < len; j++) + xwrite(outputFd, parsenextc + j, 1); + for (; j > cursor; j--) + xwrite(outputFd, "\033[D", 3); + } + + cursor++; + xwrite(outputFd, &c, 1); + break; + } + + if (break_out) /* Enter is the command terminator, no more input. */ + break; + } + + nr = len + 1; + xioctl(inputFd, TCSETA, (void *) &old_term); + reset_term = 0; + + + if (*(parsenextc)) { /* Handle command history log */ + + struct history *h = his_end; + + if (!h) { /* No previous history */ + h = his_front = malloc(sizeof(struct history)); + h->n = malloc(sizeof(struct history)); + h->p = NULL; + h->s = strdup(parsenextc); + + h->n->p = h; + h->n->n = NULL; + h->n->s = NULL; + his_end = h->n; + history_counter++; + } else { /* Add a new history command */ + + h->n = malloc(sizeof(struct history)); + + h->n->p = h; + h->n->n = NULL; + h->n->s = NULL; + h->s = strdup(parsenextc); + his_end = h->n; + + if (history_counter >= MAX_HISTORY) { /* After max history, remove the last known command */ + + struct history *p = his_front->n; + + p->p = NULL; + free(his_front->s); + free(his_front); + his_front = p; + } else { + history_counter++; + } + } + } + + return nr; +} + +extern void cmdedit_init(void) +{ + atexit(cmdedit_reset_term); + signal(SIGINT, gotaSignal); + signal(SIGQUIT, gotaSignal); + signal(SIGTERM, gotaSignal); +} +#endif /* BB_FEATURE_SH_COMMAND_EDITING */ diff --git a/shell/cmdedit.h b/shell/cmdedit.h new file mode 100644 index 000000000..e776543d6 --- /dev/null +++ b/shell/cmdedit.h @@ -0,0 +1,17 @@ +/* + * Termios command line History and Editting for NetBSD sh (ash) + * Copyright (c) 1999 + * Main code: Adam Rogoyski + * Etc: Dave Cinege + * Adjusted for busybox: Erik Andersen + * + * You may use this code as you wish, so long as the original author(s) + * are attributed in any redistributions of the source code. + * This code is 'as is' with no warranty. + * This code may safely be consumed by a BSD or GPL license. + * + */ + +extern int cmdedit_read_input(int inputFd, int outputFd, char command[BUFSIZ]); +extern void cmdedit_init(void); + diff --git a/test.c b/test.c new file mode 100644 index 000000000..85d06a83a --- /dev/null +++ b/test.c @@ -0,0 +1,583 @@ +/* vi: set sw=4 ts=4: */ +/* + * echo implementation for busybox + * + * Copyright (c) by a whole pile of folks: + * + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. + * modified by Herbert Xu to be used as built-in in ash. + * modified by Erik Andersen to be used + * in busybox. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original copyright notice states: + * "This program is in the Public Domain." + */ + +#include "internal.h" +#include +#include +#include +#include +#include +#include +#include + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; + + binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= +*/ + +enum token { + EOI, + FILRD, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + FILNT, + FILOT, + FILEQ, + FILUID, + FILGID, + STREZ, + STRNZ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT, + BAND, + BOR, + LPAREN, + RPAREN, + OPERAND +}; + +enum token_types { + UNOP, + BINOP, + BUNOP, + BBINOP, + PAREN +}; + +struct t_op { + const char *op_text; + short op_num, op_type; +} const ops [] = { + {"-r", FILRD, UNOP}, + {"-w", FILWR, UNOP}, + {"-x", FILEX, UNOP}, + {"-e", FILEXIST,UNOP}, + {"-f", FILREG, UNOP}, + {"-d", FILDIR, UNOP}, + {"-c", FILCDEV,UNOP}, + {"-b", FILBDEV,UNOP}, + {"-p", FILFIFO,UNOP}, + {"-u", FILSUID,UNOP}, + {"-g", FILSGID,UNOP}, + {"-k", FILSTCK,UNOP}, + {"-s", FILGZ, UNOP}, + {"-t", FILTT, UNOP}, + {"-z", STREZ, UNOP}, + {"-n", STRNZ, UNOP}, + {"-h", FILSYM, UNOP}, /* for backwards compat */ + {"-O", FILUID, UNOP}, + {"-G", FILGID, UNOP}, + {"-L", FILSYM, UNOP}, + {"-S", FILSOCK,UNOP}, + {"=", STREQ, BINOP}, + {"!=", STRNE, BINOP}, + {"<", STRLT, BINOP}, + {">", STRGT, BINOP}, + {"-eq", INTEQ, BINOP}, + {"-ne", INTNE, BINOP}, + {"-ge", INTGE, BINOP}, + {"-gt", INTGT, BINOP}, + {"-le", INTLE, BINOP}, + {"-lt", INTLT, BINOP}, + {"-nt", FILNT, BINOP}, + {"-ot", FILOT, BINOP}, + {"-ef", FILEQ, BINOP}, + {"!", UNOT, BUNOP}, + {"-a", BAND, BBINOP}, + {"-o", BOR, BBINOP}, + {"(", LPAREN, PAREN}, + {")", RPAREN, PAREN}, + {0, 0, 0} +}; + +char **t_wp; +struct t_op const *t_wp_op; +static gid_t *group_array = NULL; +static int ngroups; + +static enum token t_lex(); +static int oexpr(); +static int aexpr(); +static int nexpr(); +static int binop(); +static int primary(); +static int filstat(); +static int getn(); +static int newerf(); +static int olderf(); +static int equalf(); +static void syntax(); +static int test_eaccess(); +static int is_a_group_member(); +static void initialize_group_array(); + +extern int +test_main(int argc, char** argv) +{ + int res; + + if (strcmp(argv[0], "[") == 0) { + if (strcmp(argv[--argc], "]")) + fatalError("missing ]"); + argv[argc] = NULL; + } + + /* Implement special cases from POSIX.2, section 4.62.4 */ + switch (argc) { + case 1: + exit( 1); + case 2: + exit (*argv[1] == '\0'); + case 3: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + exit (!(*argv[2] == '\0')); + } + break; + case 4: + if (argv[1][0] != '!' || argv[1][1] != '\0') { + if (t_lex(argv[2]), + t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[1]; + exit (binop() == 0); + } + } + break; + case 5: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + if (t_lex(argv[3]), + t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[2]; + exit (!(binop() == 0)); + } + } + break; + } + + t_wp = &argv[1]; + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) + syntax(*t_wp, "unknown operand"); + + exit( res); +} + +static void +syntax(op, msg) + char *op; + char *msg; +{ + if (op && *op) + fatalError("%s: %s", op, msg); + else + fatalError("%s", msg); +} + +static int +oexpr(n) + enum token n; +{ + int res; + + res = aexpr(n); + if (t_lex(*++t_wp) == BOR) + return oexpr(t_lex(*++t_wp)) || res; + t_wp--; + return res; +} + +static int +aexpr(n) + enum token n; +{ + int res; + + res = nexpr(n); + if (t_lex(*++t_wp) == BAND) + return aexpr(t_lex(*++t_wp)) && res; + t_wp--; + return res; +} + +static int +nexpr(n) + enum token n; /* token */ +{ + if (n == UNOT) + return !nexpr(t_lex(*++t_wp)); + return primary(n); +} + +static int +primary(n) + enum token n; +{ + int res; + + if (n == EOI) + syntax(NULL, "argument expected"); + if (n == LPAREN) { + res = oexpr(t_lex(*++t_wp)); + if (t_lex(*++t_wp) != RPAREN) + syntax(NULL, "closing paren expected"); + return res; + } + if (t_wp_op && t_wp_op->op_type == UNOP) { + /* unary expression */ + if (*++t_wp == NULL) + syntax(t_wp_op->op_text, "argument expected"); + switch (n) { + case STREZ: + return strlen(*t_wp) == 0; + case STRNZ: + return strlen(*t_wp) != 0; + case FILTT: + return isatty(getn(*t_wp)); + default: + return filstat(*t_wp, n); + } + } + + if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { + return binop(); + } + + return strlen(*t_wp) > 0; +} + +static int +binop() +{ + const char *opnd1, *opnd2; + struct t_op const *op; + + opnd1 = *t_wp; + (void) t_lex(*++t_wp); + op = t_wp_op; + + if ((opnd2 = *++t_wp) == (char *)0) + syntax(op->op_text, "argument expected"); + + switch (op->op_num) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return getn(opnd1) == getn(opnd2); + case INTNE: + return getn(opnd1) != getn(opnd2); + case INTGE: + return getn(opnd1) >= getn(opnd2); + case INTGT: + return getn(opnd1) > getn(opnd2); + case INTLE: + return getn(opnd1) <= getn(opnd2); + case INTLT: + return getn(opnd1) < getn(opnd2); + case FILNT: + return newerf (opnd1, opnd2); + case FILOT: + return olderf (opnd1, opnd2); + case FILEQ: + return equalf (opnd1, opnd2); + } + /* NOTREACHED */ + return 1; +} + +static int +filstat(nm, mode) + char *nm; + enum token mode; +{ + struct stat s; + int i; + + if (mode == FILSYM) { +#ifdef S_IFLNK + if (lstat(nm, &s) == 0) { + i = S_IFLNK; + goto filetype; + } +#endif + return 0; + } + + if (stat(nm, &s) != 0) + return 0; + + switch (mode) { + case FILRD: + return test_eaccess(nm, R_OK) == 0; + case FILWR: + return test_eaccess(nm, W_OK) == 0; + case FILEX: + return test_eaccess(nm, X_OK) == 0; + case FILEXIST: + return 1; + case FILREG: + i = S_IFREG; + goto filetype; + case FILDIR: + i = S_IFDIR; + goto filetype; + case FILCDEV: + i = S_IFCHR; + goto filetype; + case FILBDEV: + i = S_IFBLK; + goto filetype; + case FILFIFO: +#ifdef S_IFIFO + i = S_IFIFO; + goto filetype; +#else + return 0; +#endif + case FILSOCK: +#ifdef S_IFSOCK + i = S_IFSOCK; + goto filetype; +#else + return 0; +#endif + case FILSUID: + i = S_ISUID; + goto filebit; + case FILSGID: + i = S_ISGID; + goto filebit; + case FILSTCK: + i = S_ISVTX; + goto filebit; + case FILGZ: + return s.st_size > 0L; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } + +filetype: + return ((s.st_mode & S_IFMT) == i); + +filebit: + return ((s.st_mode & i) != 0); +} + +static enum token +t_lex(s) + char *s; +{ + struct t_op const *op = ops; + + if (s == 0) { + t_wp_op = (struct t_op *)0; + return EOI; + } + while (op->op_text) { + if (strcmp(s, op->op_text) == 0) { + t_wp_op = op; + return op->op_num; + } + op++; + } + t_wp_op = (struct t_op *)0; + return OPERAND; +} + +/* atoi with error detection */ +static int +getn(s) + char *s; +{ + char *p; + long r; + + errno = 0; + r = strtol(s, &p, 10); + + if (errno != 0) + fatalError("%s: out of range", s); + + while (isspace(*p)) + p++; + + if (*p) + fatalError("%s: bad number", s); + + return (int) r; +} + +static int +newerf (f1, f2) +char *f1, *f2; +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_mtime > b2.st_mtime); +} + +static int +olderf (f1, f2) +char *f1, *f2; +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_mtime < b2.st_mtime); +} + +static int +equalf (f1, f2) +char *f1, *f2; +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); +} + +/* Do the same thing access(2) does, but use the effective uid and gid, + and don't make the mistake of telling root that any file is + executable. */ +static int +test_eaccess (path, mode) +char *path; +int mode; +{ + struct stat st; + int euid = geteuid(); + + if (stat (path, &st) < 0) + return (-1); + + if (euid == 0) { + /* Root can read or write any file. */ + if (mode != X_OK) + return (0); + + /* Root can execute any file that has any one of the execute + bits set. */ + if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return (0); + } + + if (st.st_uid == euid) /* owner */ + mode <<= 6; + else if (is_a_group_member (st.st_gid)) + mode <<= 3; + + if (st.st_mode & mode) + return (0); + + return (-1); +} + +static void +initialize_group_array () +{ + ngroups = getgroups(0, NULL); + if ((group_array = realloc(group_array, ngroups * sizeof(gid_t))) == NULL) + fatalError("Out of space"); + + getgroups(ngroups, group_array); +} + +/* Return non-zero if GID is one that we have in our groups list. */ +static int +is_a_group_member (gid) +gid_t gid; +{ + register int i; + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == getgid() || gid == getegid()) + return (1); + + if (ngroups == 0) + initialize_group_array (); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == group_array[i]) + return (1); + + return (0); +}