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