diff options
-rw-r--r-- | toys/pending/test.c | 217 | ||||
-rw-r--r-- | toys/posix/test.c | 159 |
2 files changed, 159 insertions, 217 deletions
diff --git a/toys/pending/test.c b/toys/pending/test.c deleted file mode 100644 index d887fc9f..00000000 --- a/toys/pending/test.c +++ /dev/null @@ -1,217 +0,0 @@ -/* test.c - evaluate expression - * - * Copyright 2013 Rob Landley <rob@landley.net> - * - * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html - -USE_TEST(NEWTOY(test, NULL, TOYFLAG_USR|TOYFLAG_BIN)) - -config TEST - bool "test" - default n - help - usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y] - - Return true or false by performing tests. (With no arguments return false.) - - --- Tests with a single argument (after the option): - PATH is/has: - -b block device -f regular file -p fifo -u setuid bit - -c char device -g setgid -r read bit -w write bit - -d directory -h symlink -S socket -x execute bit - -e exists -L symlink -s nonzero size - STRING is: - -n nonzero size -z zero size (STRING by itself implies -n) - FD (integer file descriptor) is: - -t a TTY - - --- Tests with one argument on each side of an operator: - Two strings: - = are identical != differ - Two integers: - -eq equal -gt first > second -lt first < second - -ne not equal -ge first >= second -le first <= second - - --- Modify or combine tests: - ! EXPR not (swap true/false) EXPR -a EXPR and (are both true) - ( EXPR ) evaluate this first EXPR -o EXPR or (is either true) -*/ - -#include "toys.h" - -int get_stat(struct stat *st, int *link, char* s) -{ - if (lstat(s, st) == -1) return 0; - *link = S_ISLNK(st->st_mode); - if (*link && (stat(s, st) == -1)) return 0; - return 1; -} - -// basic expression without !, -o, -a, ( -int test_basic(int optb, int opte) -{ - int id, val; - char *s, *err_fmt = "Bad flag '%s'", *err_int = "Bad integer '%s'"; - - if (optb == opte) val = 0; - else if (optb + 1 == opte) val = !!*toys.optargs[optb]; - else if (optb + 2 == opte && toys.optargs[optb][0] == '-') { - char c = toys.optargs[optb][1]; - struct stat st; - int link; - - if (!c || toys.optargs[optb][2]) error_exit(err_fmt, toys.optargs[optb]); - s = toys.optargs[optb + 1]; - if (c == 'b') val = get_stat(&st, &link, s) && S_ISBLK(st.st_mode); - else if (c == 'c') val = get_stat(&st, &link, s) && S_ISCHR(st.st_mode); - else if (c == 'd') val = get_stat(&st, &link, s) && S_ISDIR(st.st_mode); - else if (c == 'e') val = get_stat(&st, &link, s); - else if (c == 'f') val = get_stat(&st, &link, s) && S_ISREG(st.st_mode); - else if (c == 'g') val = get_stat(&st, &link, s) && (st.st_mode & S_ISGID); - else if (c == 'h' || c == 'L') val = get_stat(&st, &link, s) && link; - else if (c == 'p') val = get_stat(&st, &link, s) && S_ISFIFO(st.st_mode); - else if (c == 'S') val = get_stat(&st, &link, s) && S_ISSOCK(st.st_mode); - else if (c == 's') val = get_stat(&st, &link, s) && st.st_size != 0; - else if (c == 'u') val = get_stat(&st, &link, s) && (st.st_mode & S_ISUID); - else if (c == 'r') val = access(s, R_OK) != -1; - else if (c == 'w') val = access(s, W_OK) != -1; - else if (c == 'x') val = access(s, X_OK) != -1; - else if (c == 'z') val = !*s; - else if (c == 'n') val = !!*s; - else if (c == 't') { - struct termios termios; - val = tcgetattr(atoi(s), &termios) != -1; - } - else error_exit(err_fmt, toys.optargs[optb]); - } - else if (optb + 3 == opte) { - if (*toys.optargs[optb + 1] == '-') { - char *end_a, *end_b; - long a, b; - int errno_a, errno_b; - - errno = 0; - a = strtol(toys.optargs[optb], &end_a, 10); - errno_a = errno; - b = strtol(toys.optargs[optb + 2], &end_b, 10); - errno_b = errno; - s = toys.optargs[optb + 1] + 1; - if (!strcmp("eq", s)) val = a == b; - else if (!strcmp("ne", s)) val = a != b; - else if (!strcmp("gt", s)) val = a > b; - else if (!strcmp("ge", s)) val = a >= b; - else if (!strcmp("lt", s)) val = a < b; - else if (!strcmp("le", s)) val = a <= b; - else error_exit(err_fmt, toys.optargs[optb + 1]); - if (!*toys.optargs[optb] || *end_a || errno_a) - error_exit(err_int, toys.optargs[optb]); - if (!*toys.optargs[optb + 2] || *end_b || errno_b) - error_exit(err_int, toys.optargs[optb + 2]); - } - else { - int result = strcmp(toys.optargs[optb], toys.optargs[optb + 2]); - - s = toys.optargs[optb + 1]; - if (!strcmp("=", s)) val = !result; - else if (!strcmp("!=", s)) val = !!result; - else error_exit(err_fmt, toys.optargs[optb + 1]); - } - } - else error_exit("Syntax error"); - - return val; -} - -int test_sub(int optb, int opte) -{ - int not, and = 1, or = 0, i, expr; - char *err_syntax = "Syntax error"; - - for (;;) { - not = 0; - while (optb < opte && !strcmp("!", toys.optargs[optb])) { - not = !not; - optb++; - } - if (optb < opte && !strcmp("(", toys.optargs[optb])) { - int par = 1; - - for (i = optb + 1; par && i < opte; i++) { - if (!strcmp(")", toys.optargs[i])) par--; - else if (!strcmp("(", toys.optargs[i])) par++; - } - if (par) error_exit("Missing ')'"); - expr = not ^ test_sub(optb + 1, i - 1); - optb = i; - } - else { - for (i = 0; i < 4; ++i) { - if (optb + i == opte || !strcmp("-a", toys.optargs[optb + i]) - || !strcmp("-o", toys.optargs[optb + i])) break; - } - if (i == 4) error_exit_raw(err_syntax); - expr = not ^ test_basic(optb, optb + i); - optb += i; - } - - if (optb == opte) { - return or || (and && expr); - } - else if (!strcmp("-o", toys.optargs[optb])) { - or = or || (and && expr); - and = 1; - optb++; - } - else if (!strcmp("-a", toys.optargs[optb])) { - and = and && expr; - optb++; - } - else error_exit_raw(err_syntax); - } -} - -int test_few_args(int optb, int opte) -{ - if (optb == opte) return 0; - else if (optb + 1 == opte) return !!*toys.optargs[optb]; - else if (optb + 2 == opte) { - if (!strcmp("!", toys.optargs[optb])) return !*toys.optargs[optb + 1]; - else if (toys.optargs[optb][0] == '-' && - stridx("bcdefghLpSsurwxznt", toys.optargs[optb][1]) != -1 && - !toys.optargs[optb][2]) return test_basic(optb, opte); - } - else if (optb + 3 == opte) { - if (!strcmp("-eq", toys.optargs[optb + 1]) || - !strcmp("-ne", toys.optargs[optb + 1]) || - !strcmp("-gt", toys.optargs[optb + 1]) || - !strcmp("-ge", toys.optargs[optb + 1]) || - !strcmp("-lt", toys.optargs[optb + 1]) || - !strcmp("-le", toys.optargs[optb + 1]) || - !strcmp("=", toys.optargs[optb + 1]) || - !strcmp("!=", toys.optargs[optb + 1])) return test_basic(optb, opte); - else if (!strcmp("!", toys.optargs[optb])) - return !test_few_args(optb + 1, opte); - else if (!strcmp("(", toys.optargs[optb]) && - !strcmp(")", toys.optargs[optb + 2])) - return !!*toys.optargs[optb + 1]; - } - else { - if (!strcmp("!", toys.optargs[optb])) return !test_few_args(optb + 1, opte); - else if (!strcmp("(", toys.optargs[optb]) && - !strcmp(")", toys.optargs[optb + 3])) - return test_few_args(optb + 1, opte - 1); - } - return test_sub(optb, opte); -} - -void test_main(void) -{ - int optc = toys.optc; - - toys.exitval = 2; - if (!strcmp("[", toys.which->name)) - if (!optc || strcmp("]", toys.optargs[--optc])) error_exit("Missing ']'"); - if (optc <= 4) toys.exitval = !test_few_args(0, optc); - else toys.exitval = !test_sub(0, optc); - return; -} diff --git a/toys/posix/test.c b/toys/posix/test.c new file mode 100644 index 00000000..052b8deb --- /dev/null +++ b/toys/posix/test.c @@ -0,0 +1,159 @@ +/* test.c - evaluate expression + * + * Copyright 2018 Rob Landley <rob@landley.net> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html + +USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP)) + +config TEST + bool "test" + default y + help + usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y] + + Return true or false by performing tests. (With no arguments return false.) + + --- Tests with a single argument (after the option): + PATH is/has: + -b block device -f regular file -p fifo -u setuid bit + -c char device -g setgid -r read bit -w write bit + -d directory -h symlink -S socket -x execute bit + -e exists -L symlink -s nonzero size + STRING is: + -n nonzero size -z zero size (STRING by itself implies -n) + FD (integer file descriptor) is: + -t a TTY + + --- Tests with one argument on each side of an operator: + Two strings: + = are identical != differ + Two integers: + -eq equal -gt first > second -lt first < second + -ne not equal -ge first >= second -le first <= second + + --- Modify or combine tests: + ! EXPR not (swap true/false) EXPR -a EXPR and (are both true) + ( EXPR ) evaluate this first EXPR -o EXPR or (is either true) +*/ + +#include "toys.h" + +// Consume 3, 2, or 1 argument test, returning result and *count used. +int do_test(char **args, int *count) +{ + char c, *s; + int i; + + if (*count>=3) { + *count = 3; + char *s = args[1], *ss = "eqnegtgeltle"; + if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]); + if (!strcmp(s, "!=")) return strcmp(args[0], args[2]); + if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) { + long long a = atolx(args[0]), b = atolx(args[2]); + + if (!i) return a == b; + if (i==2) return a != b; + if (i==4) return a > b; + if (i==6) return a >= b; + if (i==8) return a < b; + if (i==10) return a<= b; + } + } + s = *args; + if (*count>=2 && *s == '-' && s[1] && !s[2]) { + *count = 2; + c = s[1]; + if (-1 != (i = stridx("hLbcdefgpSusxwr", c))) { + struct stat st; + + // stat or lstat, then handle rwx and s + if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0; + if (i>=12) return !!(st.st_mode&(0x111<<(i-12))); + if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0 + + // handle file type checking and SUID/SGID + if ((i = (unsigned short []){80,80,48,16,32,0,64,2,8,96,4}[i]<<9)>=4096) + return (st.st_mode&S_IFMT) == i; + else return (st.st_mode & i) == i; + } else if (c == 'z') return !*args[1]; + else if (c == 'n') return *args[1]; + else if (c == 't') return isatty(atolx(args[1])); + } + return *count = 0; +} + +#define NOT 1 // Most recent test had an odd number of preceding ! +#define AND 2 // test before -a failed since -o or ( so force false +#define OR 4 // test before -o succeeded since ( so force true +void test_main(void) +{ + char *s; + int pos, paren, pstack, result = 0; + + toys.exitval = 2; + if (!strcmp("[", toys.which->name)) + if (!toys.optc || strcmp("]", toys.optargs[--toys.optc])) + error_exit("Missing ']'"); + + // loop through command line arguments + if (toys.optc) for (pos = paren = pstack = 0; ; pos++) { + int len = toys.optc-pos; + + if (!toys.optargs[pos]) perror_exit("need arg @%d", pos); + + // Evaluate next test + result = do_test(toys.optargs+pos, &len); + pos += len; + // Single argument could be ! ( or nonempty + if (!len) { + if (toys.optargs[pos+1]) { + if (!strcmp("!", toys.optargs[pos])) { + pstack ^= NOT; + continue; + } + if (!strcmp("(", toys.optargs[pos])) { + if (++paren>9) perror_exit("bad ("); + pstack <<= 3; + continue; + } + } + result = *toys.optargs[pos++]; + } + s = toys.optargs[pos]; + for (;;) { + + // Handle pending ! -a -o (the else means -o beats -a) + if (pstack&NOT) result = !result; + pstack &= ~NOT; + if (pstack&OR) result = 1; + else if (pstack&AND) result = 0; + + // Do it again for every ) + if (!paren || !s || strcmp(")", s)) break; + paren--; + pstack >>= 3; + s = toys.optargs[++pos]; + } + + // Out of arguments? + if (!s) { + if (paren) perror_exit("need )"); + break; + } + + // are we followed by -a or -o? + + if (!strcmp("-a", s)) { + if (!result) pstack |= AND; + } else if (!strcmp("-o", s)) { + // -o flushes -a even if previous test was false + pstack &=~AND; + if (result) pstack |= OR; + } else error_exit("too many arguments"); + } + + // Invert C logic to get shell logic + toys.exitval = !result; +} |