1/* See LICENSE file for copyright and license details. */
2
3#include <sys/stat.h>
4
5#include <ctype.h>
6#include <fcntl.h>
7#include <string.h>
8#include <unistd.h>
9
10#include "util.h"
11
12static int
13intcmp(char *a, char *b)
14{
15 char *s;
16 int asign = *a == '-' ? -1 : 1;
17 int bsign = *b == '-' ? -1 : 1;
18
19 if (*a == '-' || *a == '+')
20 a += 1;
21 if (*b == '-' || *b == '+')
22 b += 1;
23
24 if (!*a || !*b)
25 goto noint;
26 for (s = a; *s; s++)
27 if (!isdigit(*s))
28 goto noint;
29 for (s = b; *s; s++)
30 if (!isdigit(*s))
31 goto noint;
32
33 while (*a == '0')
34 a++;
35 while (*b == '0')
36 b++;
37 asign *= !!*a;
38 bsign *= !!*b;
39
40 if (asign != bsign)
41 return asign < bsign ? -1 : 1;
42 else if (strlen(a) != strlen(b))
43 return asign * (strlen(a) < strlen(b) ? -1 : 1);
44 else
45 return asign * strcmp(a, b);
46
47noint:
48 enprintf(2, "expected integer operands\n");
49
50 return 0; /* not reached */
51}
52
53static int
54mtimecmp(struct stat *buf1, struct stat *buf2)
55{
56 if (buf1->st_mtime < buf2->st_mtime)
57 return -1;
58 if (buf1->st_mtime > buf2->st_mtime)
59 return +1;
60#ifdef st_mtime
61 if (buf1->st_mtim.tv_nsec < buf2->st_mtim.tv_nsec)
62 return -1;
63 if (buf1->st_mtim.tv_nsec > buf2->st_mtim.tv_nsec)
64 return +1;
65#endif
66 return 0;
67}
68
69static int
70unary_b(char *s)
71{
72 struct stat buf;
73 if (stat(s, &buf))
74 return 0;
75 return S_ISBLK(buf.st_mode);
76}
77static int
78unary_c(char *s)
79{
80 struct stat buf;
81 if (stat(s, &buf))
82 return 0;
83 return S_ISCHR(buf.st_mode);
84}
85static int
86unary_d(char *s)
87{
88 struct stat buf;
89 if (stat(s, &buf))
90 return 0;
91 return S_ISDIR(buf.st_mode);
92}
93static int
94unary_f(char *s)
95{
96 struct stat buf;
97 if (stat(s, &buf))
98 return 0;
99 return S_ISREG(buf.st_mode);
100}
101static int
102unary_g(char *s)
103{
104 struct stat buf;
105 if (stat(s, &buf))
106 return 0;
107 return S_ISGID & buf.st_mode;
108}
109static int
110unary_h(char *s)
111{
112 struct stat buf;
113 if (lstat(s, &buf))
114 return 0;
115 return S_ISLNK(buf.st_mode);
116}
117static int
118unary_k(char *s)
119{
120 struct stat buf;
121 if (stat(s, &buf))
122 return 0;
123 return S_ISVTX & buf.st_mode;
124}
125static int
126unary_p(char *s)
127{
128 struct stat buf;
129 if (stat(s, &buf))
130 return 0;
131 return S_ISFIFO(buf.st_mode);
132}
133static int
134unary_S(char *s)
135{
136 struct stat buf;
137 if (stat(s, &buf))
138 return 0;
139 return S_ISSOCK(buf.st_mode);
140}
141static int
142unary_s(char *s)
143{
144 struct stat buf;
145 if (stat(s, &buf))
146 return 0;
147 return buf.st_size;
148}
149static int
150unary_u(char *s)
151{
152 struct stat buf;
153 if (stat(s, &buf))
154 return 0;
155 return S_ISUID & buf.st_mode;
156}
157
158static int
159unary_n(char *s)
160{
161 return *s;
162}
163static int
164unary_z(char *s)
165{
166 return !*s;
167}
168
169static int
170unary_e(char *s)
171{
172 return !faccessat(AT_FDCWD, s, F_OK, AT_EACCESS);
173}
174static int
175unary_r(char *s)
176{
177 return !faccessat(AT_FDCWD, s, R_OK, AT_EACCESS);
178}
179static int
180unary_w(char *s)
181{
182 return !faccessat(AT_FDCWD, s, W_OK, AT_EACCESS);
183}
184static int
185unary_x(char *s)
186{
187 return !faccessat(AT_FDCWD, s, X_OK, AT_EACCESS);
188}
189
190static int
191unary_t(char *s)
192{
193 int fd = enstrtonum(2, s, 0, INT_MAX);
194 return isatty(fd);
195}
196
197static int
198binary_se(char *s1, char *s2)
199{
200 return !strcmp(s1, s2);
201}
202static int
203binary_sn(char *s1, char *s2)
204{
205 return strcmp(s1, s2);
206}
207
208static int
209binary_eq(char *s1, char *s2)
210{
211 return intcmp(s1, s2) == 0;
212}
213static int
214binary_ne(char *s1, char *s2)
215{
216 return intcmp(s1, s2) != 0;
217}
218static int
219binary_gt(char *s1, char *s2)
220{
221 return intcmp(s1, s2) > 0;
222}
223static int
224binary_ge(char *s1, char *s2)
225{
226 return intcmp(s1, s2) >= 0;
227}
228static int
229binary_lt(char *s1, char *s2)
230{
231 return intcmp(s1, s2) < 0;
232}
233static int
234binary_le(char *s1, char *s2)
235{
236 return intcmp(s1, s2) <= 0;
237}
238
239static int
240binary_ef(char *s1, char *s2)
241{
242 struct stat buf1, buf2;
243 if (stat(s1, &buf1) || stat(s2, &buf2))
244 return 0;
245 return buf1.st_dev == buf2.st_dev && buf1.st_ino == buf2.st_ino;
246}
247
248static int
249binary_ot(char *s1, char *s2)
250{
251 struct stat buf1, buf2;
252 if (stat(s1, &buf1) || stat(s2, &buf2))
253 return 0;
254 return mtimecmp(&buf1, &buf2) < 0;
255}
256
257static int
258binary_nt(char *s1, char *s2)
259{
260 struct stat buf1, buf2;
261 if (stat(s1, &buf1) || stat(s2, &buf2))
262 return 0;
263 return mtimecmp(&buf1, &buf2) > 0;
264}
265
266struct test {
267 char *name;
268 union {
269 int (*u)(char *);
270 int (*b)(char *, char *);
271 } func;
272};
273
274static struct test unary[] = {
275 {"-b", {.u = unary_b}},
276 {"-c", {.u = unary_c}},
277 {"-d", {.u = unary_d}},
278 {"-e", {.u = unary_e}},
279 {"-f", {.u = unary_f}},
280 {"-g", {.u = unary_g}},
281 {"-h", {.u = unary_h}},
282 {"-k", {.u = unary_k}},
283 {"-L", {.u = unary_h}},
284 {"-n", {.u = unary_n}},
285 {"-p", {.u = unary_p}},
286 {"-r", {.u = unary_r}},
287 {"-S", {.u = unary_S}},
288 {"-s", {.u = unary_s}},
289 {"-t", {.u = unary_t}},
290 {"-u", {.u = unary_u}},
291 {"-w", {.u = unary_w}},
292 {"-x", {.u = unary_x}},
293 {"-z", {.u = unary_z}},
294
295 {NULL},
296};
297
298static struct test binary[] = {
299 {"=", {.b = binary_se}},
300 {"!=", {.b = binary_sn}},
301 {"-eq", {.b = binary_eq}},
302 {"-ne", {.b = binary_ne}},
303 {"-gt", {.b = binary_gt}},
304 {"-ge", {.b = binary_ge}},
305 {"-lt", {.b = binary_lt}},
306 {"-le", {.b = binary_le}},
307 {"-ef", {.b = binary_ef}},
308 {"-ot", {.b = binary_ot}},
309 {"-nt", {.b = binary_nt}},
310
311 {NULL},
312};
313
314static struct test *
315find_test(struct test *tests, char *name)
316{
317 struct test *t;
318
319 for (t = tests; t->name; t++)
320 if (!strcmp(t->name, name))
321 return t;
322
323 return NULL;
324}
325
326static int
327noarg(char *argv[])
328{
329 (void)argv;
330 return 0;
331}
332
333static int
334onearg(char *argv[])
335{
336 return unary_n(argv[0]);
337}
338
339static int
340twoarg(char *argv[])
341{
342 struct test *t;
343
344 if (!strcmp(argv[0], "!"))
345 return !onearg(argv + 1);
346
347 if ((t = find_test(unary, *argv)))
348 return t->func.u(argv[1]);
349
350 enprintf(2, "bad unary test %s\n", argv[0]);
351
352 return 0; /* not reached */
353}
354
355static int
356threearg(char *argv[])
357{
358 struct test *t = find_test(binary, argv[1]);
359
360 if (t)
361 return t->func.b(argv[0], argv[2]);
362
363 if (!strcmp(argv[0], "!"))
364 return !twoarg(argv + 1);
365
366 enprintf(2, "bad binary test %s\n", argv[1]);
367
368 return 0; /* not reached */
369}
370
371static int
372fourarg(char *argv[])
373{
374 if (!strcmp(argv[0], "!"))
375 return !threearg(argv + 1);
376
377 enprintf(2, "too many arguments\n");
378
379 return 0; /* not reached */
380}
381
382// ?man test: evaluate condition
383// ?man check file types and compare values, returning 0 or 1
384int
385main(int argc, char *argv[])
386{
387 int (*narg[])(char *[]) = {noarg, onearg, twoarg, threearg, fourarg};
388 size_t len;
389
390 argv0 = *argv, argv0 ? (argc--, argv++) : (void *)0;
391
392 len = argv0 ? strlen(argv0) : 0;
393 if (len && argv0[--len] == '[' && (!len || argv0[--len] == '/') && strcmp(argv[--argc], "]"))
394 enprintf(2, "no matching ]\n");
395
396 if (argc > 4)
397 enprintf(2, "too many arguments\n");
398
399 return !narg[argc](argv);
400}