diff options
Diffstat (limited to 'release/src/router/busybox/coreutils/test.c')
-rw-r--r-- | release/src/router/busybox/coreutils/test.c | 968 |
1 files changed, 600 insertions, 368 deletions
diff --git a/release/src/router/busybox/coreutils/test.c b/release/src/router/busybox/coreutils/test.c index 2ad326ea..ab7b4168 100644 --- a/release/src/router/busybox/coreutils/test.c +++ b/release/src/router/busybox/coreutils/test.c @@ -2,48 +2,39 @@ /* * test implementation for busybox * - * Copyright (c) by a whole pile of folks: + * 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 <andersen@codepoet.org> to be used - * in busybox. + * 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 <andersen@codepoet.org> to be used + * in busybox. + * modified by Bernhard Reutner-Fischer to be useable (i.e. a bit less bloaty). * - * 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 + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. * * Original copyright notice states: - * "This program is in the Public Domain." + * "This program is in the Public Domain." */ -#include <sys/types.h> -#include <unistd.h> -#include <ctype.h> -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include "busybox.h" +#include "libbb.h" +#include <setjmp.h> + +/* This is a NOFORK applet. Be very careful! */ + +/* test_main() is called from shells, and we need to be extra careful here. + * This is true regardless of PREFER_APPLETS and STANDALONE_SHELL + * state. */ + /* test(1) accepts the following grammar: oexpr ::= aexpr | aexpr "-o" oexpr ; aexpr ::= nexpr | nexpr "-a" aexpr ; nexpr ::= primary | "!" primary - primary ::= unary-operator operand + primary ::= unary-operator operand | operand binary-operator operand | operand | "(" oexpr ")" @@ -51,11 +42,13 @@ 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"| + binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| "-nt"|"-ot"|"-ef"; operand ::= <any legal UNIX file name> */ +#define TEST_DEBUG 0 + enum token { EOI, FILRD, @@ -98,6 +91,84 @@ enum token { RPAREN, OPERAND }; +#define is_int_op(a) (((unsigned char)((a) - INTEQ)) <= 5) +#define is_str_op(a) (((unsigned char)((a) - STREZ)) <= 5) +#define is_file_op(a) (((unsigned char)((a) - FILNT)) <= 2) +#define is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2) +#define is_file_type(a) (((unsigned char)((a) - FILREG)) <= 5) +#define is_file_bit(a) (((unsigned char)((a) - FILSUID)) <= 2) + +#if TEST_DEBUG +int depth; +#define nest_msg(...) do { \ + depth++; \ + fprintf(stderr, "%*s", depth*2, ""); \ + fprintf(stderr, __VA_ARGS__); \ +} while (0) +#define unnest_msg(...) do { \ + fprintf(stderr, "%*s", depth*2, ""); \ + fprintf(stderr, __VA_ARGS__); \ + depth--; \ +} while (0) +#define dbg_msg(...) do { \ + fprintf(stderr, "%*s", depth*2, ""); \ + fprintf(stderr, __VA_ARGS__); \ +} while (0) +#define unnest_msg_and_return(expr, ...) do { \ + number_t __res = (expr); \ + fprintf(stderr, "%*s", depth*2, ""); \ + fprintf(stderr, __VA_ARGS__, res); \ + depth--; \ + return __res; \ +} while (0) +static const char *const TOKSTR[] = { + "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" +}; +#else +#define nest_msg(...) ((void)0) +#define unnest_msg(...) ((void)0) +#define dbg_msg(...) ((void)0) +#define unnest_msg_and_return(expr, ...) return expr +#endif enum token_types { UNOP, @@ -107,245 +178,324 @@ enum token_types { PAREN }; -static const struct t_op { - const char *op_text; - short op_num, op_type; -} 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} +struct operator_t { + char op_text[4]; + unsigned char op_num, op_type; }; -static char **t_wp; -static struct t_op const *t_wp_op; -static gid_t *group_array = NULL; -static int ngroups; - -static enum token t_lex(char *s); -static int oexpr(enum token n); -static int aexpr(enum token n); -static int nexpr(enum token n); -static int binop(void); -static int primary(enum token n); -static int filstat(char *nm, enum token mode); -static int getn(const char *s); -static int newerf(const char *f1, const char *f2); -static int olderf(const char *f1, const char *f2); -static int equalf(const char *f1, const char *f2); -static void syntax(const char *op, const char *msg); -static int test_eaccess(char *path, int mode); -static int is_a_group_member(gid_t gid); -static void initialize_group_array(void); - -extern int test_main(int argc, char **argv) -{ - int res; +static const struct operator_t 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 }, + { "==" , 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 }, +}; + + +#if ENABLE_FEATURE_TEST_64 +typedef int64_t number_t; +#else +typedef int number_t; +#endif - if (strcmp(bb_applet_name, "[") == 0) { - if (strcmp(argv[--argc], "]")) - bb_error_msg_and_die("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)); +/* We try to minimize both static and stack usage. */ +struct test_statics { + char **args; + /* set only by check_operator(), either to bogus struct + * or points to matching operator_t struct. Never NULL. */ + const struct operator_t *last_operator; + gid_t *group_array; + int ngroups; + jmp_buf leaving; +}; - if (*t_wp != NULL && *++t_wp != NULL) - syntax(*t_wp, "unknown operand"); +/* See test_ptr_hack.c */ +extern struct test_statics *const test_ptr_to_statics; - return (res); -} +#define S (*test_ptr_to_statics) +#define args (S.args ) +#define last_operator (S.last_operator) +#define group_array (S.group_array ) +#define ngroups (S.ngroups ) +#define leaving (S.leaving ) +#define INIT_S() do { \ + (*(struct test_statics**)&test_ptr_to_statics) = xzalloc(sizeof(S)); \ + barrier(); \ +} while (0) +#define DEINIT_S() do { \ + free(test_ptr_to_statics); \ +} while (0) + +static number_t primary(enum token n); + +static void syntax(const char *op, const char *msg) NORETURN; static void syntax(const char *op, const char *msg) { if (op && *op) { - bb_error_msg_and_die("%s: %s", op, msg); + bb_error_msg("%s: %s", op, msg); } else { - bb_error_msg_and_die("%s", msg); + bb_error_msg("%s: %s"+4, msg); } + longjmp(leaving, 2); } -static int oexpr(enum token n) +/* atoi with error detection */ +//XXX: FIXME: duplicate of existing libbb function? +static number_t getn(const char *s) { - int res; + char *p; +#if ENABLE_FEATURE_TEST_64 + long long r; +#else + long r; +#endif - res = aexpr(n); - if (t_lex(*++t_wp) == BOR) { - return oexpr(t_lex(*++t_wp)) || res; - } - t_wp--; - return res; + errno = 0; +#if ENABLE_FEATURE_TEST_64 + r = strtoll(s, &p, 10); +#else + r = strtol(s, &p, 10); +#endif + + if (errno != 0) + syntax(s, "out of range"); + + if (*(skip_whitespace(p))) + syntax(s, "bad number"); + + return r; } -static int aexpr(enum token n) +/* UNUSED +static int newerf(const char *f1, const char *f2) { - int res; + struct stat b1, b2; - res = nexpr(n); - if (t_lex(*++t_wp) == BAND) - return aexpr(t_lex(*++t_wp)) && res; - t_wp--; - return res; + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime); } -static int nexpr(enum token n) +static int olderf(const char *f1, const char *f2) { - if (n == UNOT) - return !nexpr(t_lex(*++t_wp)); - return primary(n); + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime); } -static int primary(enum token n) +static int equalf(const char *f1, const char *f2) { - int res; + struct stat b1, b2; - 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; + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); +} +*/ + + +static enum token check_operator(char *s) +{ + static const struct operator_t no_op = { + .op_num = -1, + .op_type = -1 + }; + const struct operator_t *op; + + last_operator = &no_op; + if (s == NULL) { + return EOI; } - 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); + + op = ops; + do { + if (strcmp(s, op->op_text) == 0) { + last_operator = op; + return op->op_num; } + op++; + } while (op < ops + ARRAY_SIZE(ops)); + + return OPERAND; +} + + +static int binop(void) +{ + const char *opnd1, *opnd2; + const struct operator_t *op; + number_t val1, val2; + + opnd1 = *args; + check_operator(*++args); + op = last_operator; + + opnd2 = *++args; + if (opnd2 == NULL) + syntax(op->op_text, "argument expected"); + + if (is_int_op(op->op_num)) { + val1 = getn(opnd1); + val2 = getn(opnd2); + if (op->op_num == INTEQ) + return val1 == val2; + if (op->op_num == INTNE) + return val1 != val2; + if (op->op_num == INTGE) + return val1 >= val2; + if (op->op_num == INTGT) + return val1 > val2; + if (op->op_num == INTLE) + return val1 <= val2; + if (op->op_num == INTLT) + return val1 < val2; + } + if (is_str_op(op->op_num)) { + val1 = strcmp(opnd1, opnd2); + if (op->op_num == STREQ) + return val1 == 0; + if (op->op_num == STRNE) + return val1 != 0; + if (op->op_num == STRLT) + return val1 < 0; + if (op->op_num == STRGT) + return val1 > 0; } + /* We are sure that these three are by now the only binops we didn't check + * yet, so we do not check if the class is correct: + */ +/* if (is_file_op(op->op_num)) */ + { + struct stat b1, b2; + + if (stat(opnd1, &b1) || stat(opnd2, &b2)) + return 0; /* false, since at least one stat failed */ + if (op->op_num == FILNT) + return b1.st_mtime > b2.st_mtime; + if (op->op_num == FILOT) + return b1.st_mtime < b2.st_mtime; + if (op->op_num == FILEQ) + return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino; + } + return 1; /* NOTREACHED */ +} + - if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { - return binop(); +static void initialize_group_array(void) +{ + ngroups = getgroups(0, NULL); + if (ngroups > 0) { + /* FIXME: ash tries so hard to not die on OOM, + * and we spoil it with just one xrealloc here */ + /* We realloc, because test_main can be entered repeatedly by shell. + * Testcase (ash): 'while true; do test -x some_file; done' + * and watch top. (some_file must have owner != you) */ + group_array = xrealloc(group_array, ngroups * sizeof(gid_t)); + getgroups(ngroups, group_array); } +} - return strlen(*t_wp) > 0; + +/* Return non-zero if GID is one that we have in our groups list. */ +//XXX: FIXME: duplicate of existing libbb function? +// see toplevel TODO file: +// possible code duplication ingroup() and is_a_group_member() +static int is_a_group_member(gid_t gid) +{ + 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; } -static int binop() + +/* 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(char *path, int mode) { - const char *opnd1, *opnd2; - struct t_op const *op; + struct stat st; + unsigned int euid = geteuid(); - opnd1 = *t_wp; - (void) t_lex(*++t_wp); - op = t_wp_op; + if (stat(path, &st) < 0) + return -1; - if ((opnd2 = *++t_wp) == (char *) 0) - syntax(op->op_text, "argument expected"); + if (euid == 0) { + /* Root can read or write any file. */ + if (mode != X_OK) + return 0; - 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); + /* 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; } - /* NOTREACHED */ - return 1; + + 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 int filstat(char *nm, enum token mode) { struct stat s; - unsigned int i; + unsigned i = i; /* gcc 3.x thinks it can be used uninitialized */ if (mode == FILSYM) { #ifdef S_IFLNK @@ -359,187 +509,269 @@ static int filstat(char *nm, enum token mode) 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: + if (mode == 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: + if (is_file_access(mode)) { + if (mode == FILRD) + i = R_OK; + if (mode == FILWR) + i = W_OK; + if (mode == FILEX) + i = X_OK; + return test_eaccess(nm, i) == 0; + } + if (is_file_type(mode)) { + if (mode == FILREG) + i = S_IFREG; + if (mode == FILDIR) + i = S_IFDIR; + if (mode == FILCDEV) + i = S_IFCHR; + if (mode == FILBDEV) + i = S_IFBLK; + if (mode == FILFIFO) { #ifdef S_IFIFO - i = S_IFIFO; - goto filetype; + i = S_IFIFO; #else - return 0; + return 0; #endif - case FILSOCK: + } + if (mode == FILSOCK) { #ifdef S_IFSOCK - i = S_IFSOCK; - goto filetype; + i = S_IFSOCK; #else - return 0; + 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: + } + filetype: + return ((s.st_mode & S_IFMT) == i); + } + if (is_file_bit(mode)) { + if (mode == FILSUID) + i = S_ISUID; + if (mode == FILSGID) + i = S_ISGID; + if (mode == FILSTCK) + i = S_ISVTX; + return ((s.st_mode & i) != 0); + } + if (mode == FILGZ) return s.st_size > 0L; - case FILUID: + if (mode == FILUID) return s.st_uid == geteuid(); - case FILGID: + if (mode == FILGID) return s.st_gid == getegid(); - default: - return 1; - } - - filetype: - return ((s.st_mode & S_IFMT) == i); - - filebit: - return ((s.st_mode & i) != 0); + return 1; /* NOTREACHED */ } -static enum token t_lex(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; +static number_t nexpr(enum token n) +{ + number_t res; + + nest_msg(">nexpr(%s)\n", TOKSTR[n]); + if (n == UNOT) { + n = check_operator(*++args); + if (n == EOI) { + /* special case: [ ! ], [ a -a ! ] are valid */ + /* IOW, "! ARG" may miss ARG */ + unnest_msg("<nexpr:1 (!EOI)\n"); + return 1; } - op++; + res = !nexpr(n); + unnest_msg("<nexpr:%lld\n", res); + return res; } - t_wp_op = (struct t_op *) 0; - return OPERAND; + res = primary(n); + unnest_msg("<nexpr:%lld\n", res); + return res; } -/* atoi with error detection */ -static int getn(const char *s) -{ - char *p; - long r; - - errno = 0; - r = strtol(s, &p, 10); - - if (errno != 0) - bb_error_msg_and_die("%s: out of range", s); - /* p = bb_skip_whitespace(p); avoid const warning */ - if (*(bb_skip_whitespace(p))) - bb_error_msg_and_die("%s: bad number", s); - - return (int) r; -} - -static int newerf(const char *f1, const char *f2) +static number_t aexpr(enum token n) { - struct stat b1, b2; + number_t res; - return (stat(f1, &b1) == 0 && - stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime); + nest_msg(">aexpr(%s)\n", TOKSTR[n]); + res = nexpr(n); + dbg_msg("aexpr: nexpr:%lld, next args:%s\n", res, args[1]); + if (check_operator(*++args) == BAND) { + dbg_msg("aexpr: arg is AND, next args:%s\n", args[1]); + res = aexpr(check_operator(*++args)) && res; + unnest_msg("<aexpr:%lld\n", res); + return res; + } + args--; + unnest_msg("<aexpr:%lld, args:%s\n", res, args[0]); + return res; } -static int olderf(const char *f1, const char *f2) -{ - struct stat b1, b2; - - return (stat(f1, &b1) == 0 && - stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime); -} -static int equalf(const char *f1, const char *f2) +static number_t oexpr(enum token n) { - struct stat b1, b2; + number_t res; - return (stat(f1, &b1) == 0 && - stat(f2, &b2) == 0 && - b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); + nest_msg(">oexpr(%s)\n", TOKSTR[n]); + res = aexpr(n); + dbg_msg("oexpr: aexpr:%lld, next args:%s\n", res, args[1]); + if (check_operator(*++args) == BOR) { + dbg_msg("oexpr: next arg is OR, next args:%s\n", args[1]); + res = oexpr(check_operator(*++args)) || res; + unnest_msg("<oexpr:%lld\n", res); + return res; + } + args--; + unnest_msg("<oexpr:%lld, args:%s\n", res, args[0]); + return res; } -/* 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(char *path, int mode) + +static number_t primary(enum token n) { - struct stat st; - unsigned int euid = geteuid(); +#if TEST_DEBUG + number_t res = res; /* for compiler */ +#else + number_t res; +#endif + const struct operator_t *args0_op; - if (stat(path, &st) < 0) - return (-1); + nest_msg(">primary(%s)\n", TOKSTR[n]); + if (n == EOI) { + syntax(NULL, "argument expected"); + } + if (n == LPAREN) { + res = oexpr(check_operator(*++args)); + if (check_operator(*++args) != RPAREN) + syntax(NULL, "closing paren expected"); + unnest_msg("<primary:%lld\n", res); + return res; + } - if (euid == 0) { - /* Root can read or write any file. */ - if (mode != X_OK) - return (0); + /* coreutils 6.9 checks "is args[1] binop and args[2] exist?" first, + * do the same */ + args0_op = last_operator; + /* last_operator = operator at args[1] */ + if (check_operator(args[1]) != EOI) { /* if args[1] != NULL */ + if (args[2]) { + // coreutils also does this: + // if (args[3] && args[0]="-l" && args[2] is BINOP) + // return binop(1 /* prepended by -l */); + if (last_operator->op_type == BINOP) + unnest_msg_and_return(binop(), "<primary: binop:%lld\n"); + } + } + /* check "is args[0] unop?" second */ + if (args0_op->op_type == UNOP) { + /* unary expression */ + if (args[1] == NULL) +// syntax(args0_op->op_text, "argument expected"); + goto check_emptiness; + args++; + if (n == STREZ) + unnest_msg_and_return(args[0][0] == '\0', "<primary:%lld\n"); + if (n == STRNZ) + unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n"); + if (n == FILTT) + unnest_msg_and_return(isatty(getn(*args)), "<primary: isatty(%s)%lld\n", *args); + unnest_msg_and_return(filstat(*args, n), "<primary: filstat(%s):%lld\n", *args); + } - /* 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); + /*check_operator(args[1]); - already done */ + if (last_operator->op_type == BINOP) { + /* args[2] is known to be NULL, isn't it bound to fail? */ + unnest_msg_and_return(binop(), "<primary:%lld\n"); } + check_emptiness: + unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n"); +} - 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); +int test_main(int argc, char **argv) +{ + int res; + const char *arg0; +// bool negate = 0; + + arg0 = bb_basename(argv[0]); + if (arg0[0] == '[') { + --argc; + if (!arg0[1]) { /* "[" ? */ + if (NOT_LONE_CHAR(argv[argc], ']')) { + bb_error_msg("missing ]"); + return 2; + } + } else { /* assuming "[[" */ + if (strcmp(argv[argc], "]]") != 0) { + bb_error_msg("missing ]]"); + return 2; + } + } + argv[argc] = NULL; + } - return (-1); -} + /* We must do DEINIT_S() prior to returning */ + INIT_S(); -static void initialize_group_array() -{ - ngroups = getgroups(0, NULL); - group_array = xrealloc(group_array, ngroups * sizeof(gid_t)); - getgroups(ngroups, group_array); -} + res = setjmp(leaving); + if (res) + goto ret; -/* Return non-zero if GID is one that we have in our groups list. */ -static int is_a_group_member(gid_t gid) -{ - register int i; + /* resetting ngroups is probably unnecessary. it will + * force a new call to getgroups(), which prevents using + * group data fetched during a previous call. but the + * only way the group data could be stale is if there's + * been an intervening call to setgroups(), and this + * isn't likely in the case of a shell. paranoia + * prevails... + */ + ngroups = 0; - /* Short-circuit if possible, maybe saving a call to getgroups(). */ - if (gid == getgid() || gid == getegid()) - return (1); + //argc--; + argv++; - if (ngroups == 0) - initialize_group_array(); + /* Implement special cases from POSIX.2, section 4.62.4 */ + if (!argv[0]) { /* "test" */ + res = 1; + goto ret; + } +#if 0 +// Now it's fixed in the parser and should not be needed + if (LONE_CHAR(argv[0], '!') && argv[1]) { + negate = 1; + //argc--; + argv++; + } + if (!argv[1]) { /* "test [!] arg" */ + res = (*argv[0] == '\0'); + goto ret; + } + if (argv[2] && !argv[3]) { + check_operator(argv[1]); + if (last_operator->op_type == BINOP) { + /* "test [!] arg1 <binary_op> arg2" */ + args = argv; + res = (binop() == 0); + goto ret; + } + } - /* Search through the list looking for GID. */ - for (i = 0; i < ngroups; i++) - if (gid == group_array[i]) - return (1); + /* Some complex expression. Undo '!' removal */ + if (negate) { + negate = 0; + //argc++; + argv--; + } +#endif + args = argv; + res = !oexpr(check_operator(*args)); - return (0); + if (*args != NULL && *++args != NULL) { + /* TODO: example when this happens? */ + bb_error_msg("%s: unknown operand", *args); + res = 2; + } + ret: + DEINIT_S(); +// return negate ? !res : res; + return res; } |