1 /* test.c - evaluate expression
2  *
3  * Copyright 2018 Rob Landley <rob@landley.net>
4  *
5  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
6  *
7  * TODO sh [[ ]] options: <   aaa<bbb  >   bbb>aaa   ~= regex
8 
9 USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
10 USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
11 
12 config TEST
13   bool "test"
14   default y
15   help
16     usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
17 
18     Return true or false by performing tests. (With no arguments return false.)
19 
20     --- Tests with a single argument (after the option):
21     PATH is/has:
22       -b  block device   -f  regular file   -p  fifo           -u  setuid bit
23       -c  char device    -g  setgid         -r  read bit       -w  write bit
24       -d  directory      -h  symlink        -S  socket         -x  execute bit
25       -e  exists         -L  symlink        -s  nonzero size   -k  sticky bit
26     STRING is:
27       -n  nonzero size   -z  zero size      (STRING by itself implies -n)
28     FD (integer file descriptor) is:
29       -t  a TTY
30 
31     --- Tests with one argument on each side of an operator:
32     Two strings:
33       =  are identical   !=  differ
34 
35     Two integers:
36       -eq  equal         -gt  first > second    -lt  first < second
37       -ne  not equal     -ge  first >= second   -le  first <= second
38 
39     --- Modify or combine tests:
40       ! EXPR     not (swap true/false)   EXPR -a EXPR    and (are both true)
41       ( EXPR )   evaluate this first     EXPR -o EXPR    or (is either true)
42 
43 config TEST_GLUE
44   bool
45   default y
46   depends on TEST || SH
47 */
48 
49 #include "toys.h"
50 
51 // Consume 3, 2, or 1 argument test, returning result and *count used.
do_test(char ** args,int * count)52 static int do_test(char **args, int *count)
53 {
54   char c, *s;
55   int i;
56 
57   if (*count>=3) {
58     *count = 3;
59     char *s = args[1], *ss = "eqnegtgeltle";
60     if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
61     if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
62     if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
63       long long a = atolx(args[0]), b = atolx(args[2]);
64 
65       if (!i) return a == b;
66       if (i==2) return a != b;
67       if (i==4) return a > b;
68       if (i==6) return a >= b;
69       if (i==8) return a < b;
70       if (i==10) return a<= b;
71     }
72   }
73   s = *args;
74   if (*count>=2 && *s == '-' && s[1] && !s[2]) {
75     *count = 2;
76     c = s[1];
77     if (-1 != (i = stridx("hLbcdefgkpSusxwr", c))) {
78       struct stat st;
79 
80       // stat or lstat, then handle rwx and s
81       if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
82       if (i>=13) return !!(st.st_mode&(0111<<(i-13)));
83       if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
84 
85       // handle file type checking and SUID/SGID
86       if ((i = ((char []){80,80,48,16,32,0,64,2,1,8,96,4}[i])<<9)>=4096)
87         return (st.st_mode&S_IFMT) == i;
88       else return (st.st_mode & i) == i;
89     } else if (c == 'z') return !*args[1];
90     else if (c == 'n') return *args[1];
91     else if (c == 't') return isatty(atolx(args[1]));
92   }
93   return *count = 0;
94 }
95 
96 #define NOT 1  // Most recent test had an odd number of preceding !
97 #define AND 2  // test before -a failed since -o or ( so force false
98 #define OR  4  // test before -o succeeded since ( so force true
test_main(void)99 void test_main(void)
100 {
101   char *s;
102   int pos, paren, pstack, result = 0;
103 
104   toys.exitval = 2;
105   if (CFG_TOYBOX && !strcmp("[", toys.which->name))
106     if (!toys.optc || strcmp("]", toys.optargs[--toys.optc]))
107       error_exit("Missing ']'");
108 
109   // loop through command line arguments
110   if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
111     int len = toys.optc-pos;
112 
113     if (!len) perror_exit("need arg @%d", pos);
114 
115     // Evaluate next test
116     result = do_test(toys.optargs+pos, &len);
117     pos += len;
118     // Single argument could be ! ( or nonempty
119     if (!len) {
120       if (toys.optargs[pos+1]) {
121         if (!strcmp("!", toys.optargs[pos])) {
122           pstack ^= NOT;
123           continue;
124         }
125         if (!strcmp("(", toys.optargs[pos])) {
126           if (++paren>9) perror_exit("bad (");
127           pstack <<= 3;
128           continue;
129         }
130       }
131       result = *toys.optargs[pos++];
132     }
133     s = toys.optargs[pos];
134     for (;;) {
135 
136       // Handle pending ! -a -o (the else means -o beats -a)
137       if (pstack&NOT) result = !result;
138       pstack &= ~NOT;
139       if (pstack&OR) result = 1;
140       else if (pstack&AND) result = 0;
141 
142       // Do it again for every )
143       if (!paren || pos==toys.optc || strcmp(")", s)) break;
144       paren--;
145       pstack >>= 3;
146       s = toys.optargs[++pos];
147     }
148 
149     // Out of arguments?
150     if (pos==toys.optc) {
151       if (paren) perror_exit("need )");
152       break;
153     }
154 
155     // are we followed by -a or -o?
156 
157     if (!strcmp("-a", s)) {
158       if (!result) pstack |= AND;
159     } else if (!strcmp("-o", s)) {
160       // -o flushes -a even if previous test was false
161       pstack &=~AND;
162       if (result) pstack |= OR;
163     } else error_exit("too many arguments");
164   }
165 
166   // Invert C logic to get shell logic
167   toys.exitval = !result;
168 }
169