aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--toys/pending/test.c217
-rw-r--r--toys/posix/test.c159
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;
+}