X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=coreutils%2Ftest.c;h=7b8b10cd9736eb58ee676c5647835df805c18e82;hb=d1ed3e68b8080161642cc106099c0a17ac7892e6;hp=419da5101898fefc3c2f28b3f7e1004bee859495;hpb=c7bda1ce659294d6e22c06e087f6f265983c7578;p=oweals%2Fbusybox.git diff --git a/coreutils/test.c b/coreutils/test.c index 419da5101..7b8b10cd9 100644 --- a/coreutils/test.c +++ b/coreutils/test.c @@ -4,46 +4,73 @@ * * 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. + * 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. + * 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 source tree. * * Original copyright notice states: - * "This program is in the Public Domain." + * "This program is in the Public Domain." */ -#include -#include -#include -#include -#include -#include -#include "busybox.h" +//kbuild:lib-$(CONFIG_TEST) += test.o test_ptr_hack.o +//kbuild:lib-$(CONFIG_ASH) += test.o test_ptr_hack.o +//kbuild:lib-$(CONFIG_HUSH) += test.o test_ptr_hack.o + +//config:config TEST +//config: bool "test" +//config: default y +//config: help +//config: test is used to check file types and compare values, +//config: returning an appropriate exit code. The bash shell +//config: has test built in, ash can build it in optionally. +//config: +//config:config FEATURE_TEST_64 +//config: bool "Extend test to 64 bit" +//config: default y +//config: depends on TEST || ASH_BUILTIN_TEST || HUSH +//config: help +//config: Enable 64-bit support in test. + +/* "test --help" is special-cased to ignore --help */ +//usage:#define test_trivial_usage NOUSAGE_STR +//usage:#define test_full_usage "" +//usage: +//usage:#define test_example_usage +//usage: "$ test 1 -eq 2\n" +//usage: "$ echo $?\n" +//usage: "1\n" +//usage: "$ test 1 -eq 1\n" +//usage: "$ echo $?\n" +//usage: "0\n" +//usage: "$ [ -d /etc ]\n" +//usage: "$ echo $?\n" +//usage: "0\n" +//usage: "$ [ -d /junk ]\n" +//usage: "$ echo $?\n" +//usage: "1\n" + +#include "libbb.h" +#include + +/* 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 SH_STANDALONE + * state. */ /* test(1) accepts the following grammar: - oexpr ::= aexpr | aexpr "-o" oexpr ; - aexpr ::= nexpr | nexpr "-a" aexpr ; - nexpr ::= primary | "!" primary - primary ::= unary-operator operand + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand | operand binary-operator operand | operand | "(" oexpr ")" @@ -51,46 +78,102 @@ 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 ::= */ +/* TODO: handle [[ expr ]] bashism bash-compatibly. + * [[ ]] is meant to be a "better [ ]", with less weird syntax + * and without the risk of variables and quoted strings misinterpreted + * as operators. + * This will require support from shells - we need to know quote status + * of each parameter (see below). + * + * Word splitting and pathname expansion should NOT be performed: + * # a="a b"; [[ $a = "a b" ]] && echo YES + * YES + * # [[ /bin/m* ]] && echo YES + * YES + * + * =~ should do regexp match + * = and == should do pattern match against right side: + * # [[ *a* == bab ]] && echo YES + * # [[ bab == *a* ]] && echo YES + * YES + * != does the negated == (i.e., also with pattern matching). + * Pattern matching is quotation-sensitive: + * # [[ bab == "b"a* ]] && echo YES + * YES + * # [[ bab == b"a*" ]] && echo YES + * + * Conditional operators such as -f must be unquoted literals to be recognized: + * # [[ -e /bin ]] && echo YES + * YES + * # [[ '-e' /bin ]] && echo YES + * bash: conditional binary operator expected... + * # A='-e'; [[ $A /bin ]] && echo YES + * bash: conditional binary operator expected... + * + * || and && should work as -o and -a work in [ ] + * -a and -o aren't recognized (&& and || are to be used instead) + * ( and ) do not need to be quoted unlike in [ ]: + * # [[ ( abc ) && '' ]] && echo YES + * # [[ ( abc ) || '' ]] && echo YES + * YES + * # [[ ( abc ) -o '' ]] && echo YES + * bash: syntax error in conditional expression... + * + * Apart from the above, [[ expr ]] should work as [ expr ] + */ + +#define TEST_DEBUG 0 + enum token { EOI, - FILRD, + + FILRD, /* file access */ FILWR, FILEX, + FILEXIST, - FILREG, + + FILREG, /* file type */ FILDIR, FILCDEV, FILBDEV, FILFIFO, FILSOCK, + FILSYM, FILGZ, FILTT, - FILSUID, + + FILSUID, /* file bit */ FILSGID, FILSTCK, - FILNT, + + FILNT, /* file ops */ FILOT, FILEQ, + FILUID, FILGID, - STREZ, + + STREZ, /* str ops */ STRNZ, STREQ, STRNE, STRLT, STRGT, - INTEQ, + + INTEQ, /* int ops */ INTNE, INTGE, INTGT, INTLE, INTLT, + UNOT, BAND, BOR, @@ -98,8 +181,86 @@ 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 { +enum { UNOP, BINOP, BUNOP, @@ -107,245 +268,364 @@ 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 { + 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_table[] = { + { /* "-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 }, +}; +/* Please keep these two tables in sync */ +static const char ops_texts[] ALIGN1 = + "-r" "\0" + "-w" "\0" + "-x" "\0" + "-e" "\0" + "-f" "\0" + "-d" "\0" + "-c" "\0" + "-b" "\0" + "-p" "\0" + "-u" "\0" + "-g" "\0" + "-k" "\0" + "-s" "\0" + "-t" "\0" + "-z" "\0" + "-n" "\0" + "-h" "\0" + + "-O" "\0" + "-G" "\0" + "-L" "\0" + "-S" "\0" + "=" "\0" + "==" "\0" + "!=" "\0" + "<" "\0" + ">" "\0" + "-eq" "\0" + "-ne" "\0" + "-ge" "\0" + "-gt" "\0" + "-le" "\0" + "-lt" "\0" + "-nt" "\0" + "-ot" "\0" + "-ef" "\0" + "!" "\0" + "-a" "\0" + "-o" "\0" + "(" "\0" + ")" "\0" +; + + +#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; +}; + +/* See test_ptr_hack.c */ +extern struct test_statics *const test_ptr_to_statics; - if (*t_wp != NULL && *++t_wp != NULL) - syntax(*t_wp, "unknown operand"); +#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 ) - return (res); -} +#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 (p == s || *(skip_whitespace(p)) != '\0') + 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"); + 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(const char *s) +{ + static const struct operator_t no_op = { + .op_num = -1, + .op_type = -1 + }; + int n; + + last_operator = &no_op; + if (s == NULL) + return EOI; + n = index_in_strings(ops_texts, s); + if (n < 0) + return OPERAND; + last_operator = &ops_table[n]; + return ops_table[n].op_num; +} + + +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(args[-1], "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 (n == LPAREN) { - res = oexpr(t_lex(*++t_wp)); - if (t_lex(*++t_wp) != RPAREN) - syntax(NULL, "closing paren expected"); - return res; + 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; } - 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); - } + /* 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(); - } - return strlen(*t_wp) > 0; +static void initialize_group_array(void) +{ + int n; + + /* getgroups may be expensive, try to use it only once */ + ngroups = 32; + do { + /* 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) */ + n = ngroups; + group_array = xrealloc(group_array, n * sizeof(gid_t)); + ngroups = getgroups(n, group_array); + } while (ngroups > n); } -static int binop() + +/* 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) { - 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); + 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; +} + + +/* 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) +{ + struct stat st; + unsigned 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; } - /* 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 +639,274 @@ 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 */ + args--; + unnest_msg(" b2.st_mtime); + nest_msg(">aexpr(%s)\n", TOKSTR[n]); + res = nexpr(n); + dbg_msg("aexpr: nexpr:%lld, next args:%s(%p)\n", res, args[1], &args[1]); + if (check_operator(*++args) == BAND) { + dbg_msg("aexpr: arg is AND, next args:%s(%p)\n", args[1], &args[1]); + res = aexpr(check_operator(*++args)) && res; + unnest_msg("oexpr(%s)\n", TOKSTR[n]); + res = aexpr(n); + dbg_msg("oexpr: aexpr:%lld, next args:%s(%p)\n", res, args[1], &args[1]); + if (check_operator(*++args) == BOR) { + dbg_msg("oexpr: next arg is OR, next args:%s(%p)\n", args[1], &args[1]); + res = oexpr(check_operator(*++args)) || res; + unnest_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("op_type == BINOP) + unnest_msg_and_return(binop(), "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', "op_type == BINOP) { + /* args[2] is known to be NULL, isn't it bound to fail? */ + unnest_msg_and_return(binop(), "op_type == BINOP) { + /* "test [!] arg1 arg2" */ + args = argv; + res = (binop() == 0); + ret_special: + /* If there was leading "!" op... */ + res ^= negate; + goto ret; + } + } + if (LONE_CHAR(argv[0], '!')) { + argv++; + negate ^= 1; + goto again; + } + } - /* Search through the list looking for GID. */ - for (i = 0; i < ngroups; i++) - if (gid == group_array[i]) - return (1); + res = !oexpr(check_operator(*args)); - return (0); + if (*args != NULL && *++args != NULL) { + /* Examples: + * test 3 -lt 5 6 + * test -t 1 2 + */ + bb_error_msg("%s: unknown operand", *args); + res = 2; + } + ret: + DEINIT_S(); + return res; }