/* test.c - evaluate expression * * Copyright 2013 Rob Landley * * 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(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(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; }