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 * Deviations from posix: -k, [[ < > =~ ]]
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 USE_SH(OLDTOY([[, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
12
13 config TEST
14 bool "test"
15 default y
16 help
17 usage: test [-bcdefghkLprSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
18
19 Return true or false by performing tests. No arguments is false, one argument
20 is true if not empty string.
21
22 --- Tests with a single argument (after the option):
23 PATH is/has:
24 -b block device -f regular file -p fifo -u setuid bit
25 -c char device -g setgid -r readable -w writable
26 -d directory -h symlink -S socket -x executable
27 -e exists -L symlink -s nonzero size -k sticky bit
28 STRING is:
29 -n nonzero size -z zero size
30 FD (integer file descriptor) is:
31 -t a TTY
32
33 --- Tests with one argument on each side of an operator:
34 Two strings:
35 = are identical != differ =~ string matches regex
36 Alphabetical sort:
37 < first is lower > first higher
38 Two integers:
39 -eq equal -gt first > second -lt first < second
40 -ne not equal -ge first >= second -le first <= second
41 Two files:
42 -ot Older mtime -nt Newer mtime -ef same dev/inode
43
44 --- Modify or combine tests:
45 ! EXPR not (swap true/false) EXPR -a EXPR and (are both true)
46 ( EXPR ) evaluate this first EXPR -o EXPR or (is either true)
47
48 config TEST_GLUE
49 bool
50 default y
51 depends on TEST || SH
52 */
53
54 #include "toys.h"
55
56 // Consume 3, 2, or 1 argument test, returning result and *count used.
do_test(char ** args,int * count)57 static int do_test(char **args, int *count)
58 {
59 char c, *s;
60 int i;
61
62 if (*count>=3) {
63 *count = 3;
64 char *s = args[1], *ss = "eqnegtgeltleefotnt";
65 // TODO shell integration case insensitivity
66 if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
67 if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
68 if (!strcmp(s, "=~")) {
69 regex_t reg;
70
71 // TODO: regex needs integrated quoting support with the shell.
72 // Ala [[ abc =~ "1"* ]] matches but [[ abc =~ 1"*" ]] does not
73 xregcomp(®, args[2], REG_NOSUB); // REG_EXTENDED? REG_ICASE?
74 i = regexec(®, args[0], 0, 0, 0);
75 regfree(®);
76
77 return !i;
78 }
79 if ((*s=='<' || *s=='>') && !s[1]) {
80 i = strcmp(args[0], args[2]);
81 return (*s=='<') ? i<0 : i>0;
82 }
83 if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
84 struct stat st1, st2;
85 long long a QUIET, b QUIET;
86 if (i <= 10) {
87 a = atolx(args[0]);
88 b = atolx(args[2]);
89 } else {
90 if ((i == 12 ? stat : lstat)(args[0], &st1)
91 || (i == 12 ? stat : lstat)(args[2], &st2)) return 0;
92 }
93
94 if (!i) return a == b;
95 if (i==2) return a != b;
96 if (i==4) return a > b;
97 if (i==6) return a >= b;
98 if (i==8) return a < b;
99 if (i==10) return a<= b;
100 if (i==12) return (st1.st_dev==st2.st_dev) && (st1.st_ino==st2.st_ino);
101 if (i==14) return (st1.st_mtim.tv_sec < st2.st_mtim.tv_sec) ||
102 (st1.st_mtim.tv_sec == st2.st_mtim.tv_sec &&
103 st1.st_mtim.tv_nsec < st2.st_mtim.tv_nsec);
104 if (i==16) return (st1.st_mtim.tv_sec > st2.st_mtim.tv_sec) ||
105 (st1.st_mtim.tv_sec == st2.st_mtim.tv_sec &&
106 st1.st_mtim.tv_nsec > st2.st_mtim.tv_nsec);
107 }
108 }
109 s = *args;
110 if (*count>=2 && *s == '-' && s[1] && !s[2]) {
111 *count = 2;
112 c = s[1];
113 if (c=='a') c = 'e';
114 if (-1 != (i = stridx("hLbcdefgkpSusxwr", c))) {
115 struct stat st;
116
117 if (i>=13) return !access(args[1], 1<<(i-13));
118 // stat or lstat, check s
119 if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
120 if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
121
122 // handle file type checking and SUID/SGID
123 if ((i = ((char []){80,80,48,16,32,0,64,2,1,8,96,4}[i])<<9)>=4096)
124 return (st.st_mode&S_IFMT) == i;
125 else return (st.st_mode & i) == i;
126 } else if (c == 'z') return !*args[1];
127 else if (c == 'n') return *args[1];
128 else if (c == 't') return isatty(atolx(args[1]));
129 }
130 return *count = 0;
131 }
132
133 #define NOT 1 // Most recent test had an odd number of preceding !
134 #define AND 2 // test before -a failed since -o or ( so force false
135 #define OR 4 // test before -o succeeded since ( so force true
test_main(void)136 void test_main(void)
137 {
138 char *s = (void *)1;
139 int pos, paren, pstack, result = 0;
140
141 toys.exitval = 2;
142 if (CFG_TOYBOX && *toys.which->name=='[') {
143 if (toys.optc) for (s = toys.optargs[--toys.optc]; *s==']'; s++);
144 if (*s) error_exit("Missing ']'");
145 }
146
147 // loop through command line arguments
148 if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
149 int len = toys.optc-pos;
150
151 if (!len) perror_exit("need arg @%d", pos);
152
153 // Evaluate next test
154 result = do_test(toys.optargs+pos, &len);
155 pos += len;
156 // Single argument could be ! ( or nonempty
157 if (!len) {
158 if (toys.optargs[pos+1]) {
159 if (!strcmp("!", toys.optargs[pos])) {
160 pstack ^= NOT;
161 continue;
162 }
163 if (!strcmp("(", toys.optargs[pos])) {
164 if (++paren>9) perror_exit("bad (");
165 pstack <<= 3;
166 continue;
167 }
168 }
169 result = *toys.optargs[pos++];
170 }
171 s = toys.optargs[pos];
172 for (;;) {
173
174 // Handle pending ! -a -o (the else means -o beats -a)
175 if (pstack&NOT) result = !result;
176 pstack &= ~NOT;
177 if (pstack&OR) result = 1;
178 else if (pstack&AND) result = 0;
179
180 // Do it again for every )
181 if (!paren || pos==toys.optc || strcmp(")", s)) break;
182 paren--;
183 pstack >>= 3;
184 s = toys.optargs[++pos];
185 }
186
187 // Out of arguments?
188 if (pos==toys.optc) {
189 if (paren) perror_exit("need )");
190 break;
191 }
192
193 // are we followed by -a or -o?
194
195 if (!strcmp("-a", s)) {
196 if (!result) pstack |= AND;
197 } else if (!strcmp("-o", s)) {
198 // -o flushes -a even if previous test was false
199 pstack &=~AND;
200 if (result) pstack |= OR;
201 } else error_exit("too many arguments");
202 }
203
204 // Invert C logic to get shell logic
205 toys.exitval = !result;
206 }
207