1/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */
2
3/*-
4 * test(1); version 7-like -- author Erik Baalbergen
5 * modified by Eric Gisin to be used as built-in.
6 * modified by Arnold Robbins to add SVR3 compatibility
7 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
8 * modified by J.T. Conklin for NetBSD.
9 *
10 * This program is in the Public Domain.
11 */
12/*
13 * Important: This file is used both as a standalone program /bin/test and
14 * as a builtin for /bin/sh (#define SHELL).
15 */
16
17#ifndef SHELL
18#ifndef __dead2
19#define __dead2 __attribute__((__noreturn__))
20#endif
21#ifndef __printf0like
22#define __printf0like(n, m) __attribute__((__format__(__printf__, n, m)))
23#endif
24#ifndef __nonstring
25#if defined(__has_attribute)
26#if __has_attribute(__nonstring__)
27#define __nonstring __attribute__((__nonstring__))
28#elif __has_attribute(nonstring)
29#define __nonstring __attribute__((nonstring))
30#endif
31#endif
32#endif
33#ifndef __nonstring
34#define __nonstring
35#endif
36#ifndef eaccess
37#ifdef AT_EACCESS
38#define eaccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_EACCESS)
39#else
40#define eaccess(path, mode) access((path), (mode))
41#endif
42#endif
43#endif
44
45#include <sys/stat.h>
46#include <sys/types.h>
47
48#include <ctype.h>
49#include <err.h>
50#include <errno.h>
51#include <inttypes.h>
52#include <stdarg.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56
57#ifdef SHELL
58#define main testcmd
59#include "bltin.h"
60#else
61#include <locale.h>
62
63static void error(const char *, ...) __dead2 __printf0like(1, 2);
64
65static void
66error(const char *msg, ...)
67{
68 va_list ap;
69 va_start(ap, msg);
70 verrx(2, msg, ap);
71 /*NOTREACHED*/
72 va_end(ap);
73}
74#endif
75
76/* test(1) accepts the following grammar:
77 oexpr ::= aexpr | aexpr "-o" oexpr ;
78 aexpr ::= nexpr | nexpr "-a" aexpr ;
79 nexpr ::= primary | "!" primary
80 primary ::= unary-operator operand
81 | operand binary-operator operand
82 | operand
83 | "(" oexpr ")"
84 ;
85 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
86 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
87
88 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
89 "-nt"|"-ot"|"-ef";
90 operand ::= <any legal UNIX file name>
91*/
92
93enum token_types { UNOP = 0x100, BINOP = 0x200, BUNOP = 0x300, BBINOP = 0x400, PAREN = 0x500 };
94
95enum token {
96 EOI,
97 OPERAND,
98 FILRD = UNOP + 1,
99 FILWR,
100 FILEX,
101 FILEXIST,
102 FILREG,
103 FILDIR,
104 FILCDEV,
105 FILBDEV,
106 FILFIFO,
107 FILSOCK,
108 FILSYM,
109 FILGZ,
110 FILTT,
111 FILSUID,
112 FILSGID,
113 FILSTCK,
114 STREZ,
115 STRNZ,
116 FILUID,
117 FILGID,
118 FILNT = BINOP + 1,
119 FILOT,
120 FILEQ,
121 STREQ,
122 STRNE,
123 STRLT,
124 STRGT,
125 INTEQ,
126 INTNE,
127 INTGE,
128 INTGT,
129 INTLE,
130 INTLT,
131 UNOT = BUNOP + 1,
132 BAND = BBINOP + 1,
133 BOR,
134 LPAREN = PAREN + 1,
135 RPAREN
136};
137
138#define TOKEN_TYPE(token) ((token) & 0xff00)
139
140static const struct t_op {
141 char op_text[2] __nonstring;
142 short op_num;
143} ops1[] =
144 {
145 {"=", STREQ},
146 {"<", STRLT},
147 {">", STRGT},
148 {"!", UNOT},
149 {"(", LPAREN},
150 {")", RPAREN},
151},
152 opsm1[] =
153 {
154 {"r", FILRD}, {"w", FILWR}, {"x", FILEX}, {"e", FILEXIST}, {"f", FILREG},
155 {"d", FILDIR}, {"c", FILCDEV}, {"b", FILBDEV}, {"p", FILFIFO}, {"u", FILSUID},
156 {"g", FILSGID}, {"k", FILSTCK}, {"s", FILGZ}, {"t", FILTT}, {"z", STREZ},
157 {"n", STRNZ}, {"h", FILSYM}, /* for backwards compat */
158 {"O", FILUID}, {"G", FILGID}, {"L", FILSYM}, {"S", FILSOCK}, {"a", BAND},
159 {"o", BOR},
160},
161 ops2[] =
162 {
163 {"==", STREQ},
164 {"!=", STRNE},
165},
166 opsm2[] = {
167 {"eq", INTEQ},
168 {"ne", INTNE},
169 {"ge", INTGE},
170 {"gt", INTGT},
171 {"le", INTLE},
172 {"lt", INTLT},
173 {"nt", FILNT},
174 {"ot", FILOT},
175 {"ef", FILEQ},
176};
177
178static int nargc;
179static char **t_wp;
180static int parenlevel;
181
182static int aexpr(enum token);
183static int binop(enum token);
184static int equalf(const char *, const char *);
185static int filstat(char *, enum token);
186static int getn(const char *);
187static intmax_t getq(const char *);
188static int intcmp(const char *, const char *);
189static int isunopoperand(void);
190static int islparenoperand(void);
191static int isrparenoperand(void);
192static int newerf(const char *, const char *);
193static int nexpr(enum token);
194static int oexpr(enum token);
195static int olderf(const char *, const char *);
196static int primary(enum token);
197static void syntax(const char *, const char *);
198static enum token t_lex(char *);
199
200int
201main(int argc, char **argv)
202{
203 int res;
204 char *p;
205
206 if ((p = strrchr(argv[0], '/')) == NULL)
207 p = argv[0];
208 else
209 p++;
210 if (strcmp(p, "[") == 0) {
211 if (strcmp(argv[--argc], "]") != 0)
212 error("missing ']'");
213 argv[argc] = NULL;
214 }
215
216 /* no expression => false */
217 if (--argc <= 0)
218 return 1;
219
220#ifndef SHELL
221 (void)setlocale(LC_CTYPE, "");
222#endif
223 nargc = argc;
224 t_wp = &argv[1];
225 parenlevel = 0;
226 if (nargc == 4 && strcmp(*t_wp, "!") == 0) {
227 /* Things like ! "" -o x do not fit in the normal grammar. */
228 --nargc;
229 ++t_wp;
230 res = oexpr(t_lex(*t_wp));
231 } else
232 res = !oexpr(t_lex(*t_wp));
233
234 if (--nargc > 0)
235 syntax(*t_wp, "unexpected operator");
236
237 return res;
238}
239
240static void
241syntax(const char *op, const char *msg)
242{
243 if (op && *op)
244 error("%s: %s", op, msg);
245 else
246 error("%s", msg);
247}
248
249static int
250oexpr(enum token n)
251{
252 int res;
253
254 res = aexpr(n);
255 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR)
256 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || res;
257 t_wp--;
258 nargc++;
259 return res;
260}
261
262static int
263aexpr(enum token n)
264{
265 int res;
266
267 res = nexpr(n);
268 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND)
269 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && res;
270 t_wp--;
271 nargc++;
272 return res;
273}
274
275static int
276nexpr(enum token n)
277{
278 if (n == UNOT)
279 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL));
280 return primary(n);
281}
282
283static int
284primary(enum token n)
285{
286 enum token nn;
287 int res;
288
289 if (n == EOI)
290 return 0; /* missing expression */
291 if (n == LPAREN) {
292 parenlevel++;
293 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == RPAREN) {
294 parenlevel--;
295 return 0; /* missing expression */
296 }
297 res = oexpr(nn);
298 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
299 syntax(NULL, "closing paren expected");
300 parenlevel--;
301 return res;
302 }
303 if (TOKEN_TYPE(n) == UNOP) {
304 /* unary expression */
305 if (--nargc == 0)
306 syntax(NULL, "argument expected"); /* impossible */
307 switch (n) {
308 case STREZ:
309 return strlen(*++t_wp) == 0;
310 case STRNZ:
311 return strlen(*++t_wp) != 0;
312 case FILTT:
313 return isatty(getn(*++t_wp));
314 default:
315 return filstat(*++t_wp, n);
316 }
317 }
318
319 nn = t_lex(nargc > 0 ? t_wp[1] : NULL);
320 if (TOKEN_TYPE(nn) == BINOP)
321 return binop(nn);
322
323 return strlen(*t_wp) > 0;
324}
325
326static int
327binop(enum token n)
328{
329 const char *opnd1, *op, *opnd2;
330
331 opnd1 = *t_wp;
332 op = nargc > 0 ? (--nargc, *++t_wp) : NULL;
333
334 if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
335 syntax(op, "argument expected");
336
337 switch (n) {
338 case STREQ:
339 return strcmp(opnd1, opnd2) == 0;
340 case STRNE:
341 return strcmp(opnd1, opnd2) != 0;
342 case STRLT:
343 return strcmp(opnd1, opnd2) < 0;
344 case STRGT:
345 return strcmp(opnd1, opnd2) > 0;
346 case INTEQ:
347 return intcmp(opnd1, opnd2) == 0;
348 case INTNE:
349 return intcmp(opnd1, opnd2) != 0;
350 case INTGE:
351 return intcmp(opnd1, opnd2) >= 0;
352 case INTGT:
353 return intcmp(opnd1, opnd2) > 0;
354 case INTLE:
355 return intcmp(opnd1, opnd2) <= 0;
356 case INTLT:
357 return intcmp(opnd1, opnd2) < 0;
358 case FILNT:
359 return newerf(opnd1, opnd2);
360 case FILOT:
361 return olderf(opnd1, opnd2);
362 case FILEQ:
363 return equalf(opnd1, opnd2);
364 default:
365 abort();
366 /* NOTREACHED */
367 }
368}
369
370static int
371filstat(char *nm, enum token mode)
372{
373 struct stat s;
374
375 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
376 return 0;
377
378 switch (mode) {
379 case FILRD:
380 return (eaccess(nm, R_OK) == 0);
381 case FILWR:
382 return (eaccess(nm, W_OK) == 0);
383 case FILEX:
384 /* XXX work around eaccess(2) false positives for superuser */
385 if (eaccess(nm, X_OK) != 0)
386 return 0;
387 if (S_ISDIR(s.st_mode) || geteuid() != 0)
388 return 1;
389 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
390 case FILEXIST:
391 return (eaccess(nm, F_OK) == 0);
392 case FILREG:
393 return S_ISREG(s.st_mode);
394 case FILDIR:
395 return S_ISDIR(s.st_mode);
396 case FILCDEV:
397 return S_ISCHR(s.st_mode);
398 case FILBDEV:
399 return S_ISBLK(s.st_mode);
400 case FILFIFO:
401 return S_ISFIFO(s.st_mode);
402 case FILSOCK:
403 return S_ISSOCK(s.st_mode);
404 case FILSYM:
405 return S_ISLNK(s.st_mode);
406 case FILSUID:
407 return (s.st_mode & S_ISUID) != 0;
408 case FILSGID:
409 return (s.st_mode & S_ISGID) != 0;
410 case FILSTCK:
411 return (s.st_mode & S_ISVTX) != 0;
412 case FILGZ:
413 return s.st_size > (off_t)0;
414 case FILUID:
415 return s.st_uid == geteuid();
416 case FILGID:
417 return s.st_gid == getegid();
418 default:
419 return 1;
420 }
421}
422
423static int
424find_op_1char(const struct t_op *op, const struct t_op *end, const char *s)
425{
426 char c;
427
428 c = s[0];
429 while (op != end) {
430 if (c == *op->op_text)
431 return op->op_num;
432 op++;
433 }
434 return OPERAND;
435}
436
437static int
438find_op_2char(const struct t_op *op, const struct t_op *end, const char *s)
439{
440 while (op != end) {
441 if (s[0] == op->op_text[0] && s[1] == op->op_text[1])
442 return op->op_num;
443 op++;
444 }
445 return OPERAND;
446}
447
448static int
449find_op(const char *s)
450{
451 if (s[0] == '\0')
452 return OPERAND;
453 else if (s[1] == '\0')
454 return find_op_1char(ops1, (&ops1)[1], s);
455 else if (s[2] == '\0')
456 return s[0] == '-' ? find_op_1char(opsm1, (&opsm1)[1], s + 1)
457 : find_op_2char(ops2, (&ops2)[1], s);
458 else if (s[3] == '\0')
459 return s[0] == '-' ? find_op_2char(opsm2, (&opsm2)[1], s + 1) : OPERAND;
460 else
461 return OPERAND;
462}
463
464static enum token
465t_lex(char *s)
466{
467 int num;
468
469 if (s == NULL) {
470 return EOI;
471 }
472 num = find_op(s);
473 if (((TOKEN_TYPE(num) == UNOP || TOKEN_TYPE(num) == BUNOP) && isunopoperand())
474 || (num == LPAREN && islparenoperand()) || (num == RPAREN && isrparenoperand()))
475 return OPERAND;
476 return num;
477}
478
479static int
480isunopoperand(void)
481{
482 char *s;
483 char *t;
484 int num;
485
486 if (nargc == 1)
487 return 1;
488 s = *(t_wp + 1);
489 if (nargc == 2)
490 return parenlevel == 1 && strcmp(s, ")") == 0;
491 t = *(t_wp + 2);
492 num = find_op(s);
493 return TOKEN_TYPE(num) == BINOP && (parenlevel == 0 || t[0] != ')' || t[1] != '\0');
494}
495
496static int
497islparenoperand(void)
498{
499 char *s;
500 int num;
501
502 if (nargc == 1)
503 return 1;
504 s = *(t_wp + 1);
505 if (nargc == 2)
506 return parenlevel == 1 && strcmp(s, ")") == 0;
507 if (nargc != 3)
508 return 0;
509 num = find_op(s);
510 return TOKEN_TYPE(num) == BINOP;
511}
512
513static int
514isrparenoperand(void)
515{
516 char *s;
517
518 if (nargc == 1)
519 return 0;
520 s = *(t_wp + 1);
521 if (nargc == 2)
522 return parenlevel == 1 && strcmp(s, ")") == 0;
523 return 0;
524}
525
526/* atoi with error detection */
527static int
528getn(const char *s)
529{
530 char *p;
531 long r;
532
533 errno = 0;
534 r = strtol(s, &p, 10);
535
536 if (s == p)
537 error("%s: bad number", s);
538
539 if (errno != 0)
540 error((errno == EINVAL) ? "%s: bad number" : "%s: out of range", s);
541
542 while (isspace((unsigned char)*p))
543 p++;
544
545 if (*p)
546 error("%s: bad number", s);
547
548 return (int)r;
549}
550
551/* atoi with error detection and 64 bit range */
552static intmax_t
553getq(const char *s)
554{
555 char *p;
556 intmax_t r;
557
558 errno = 0;
559 r = strtoimax(s, &p, 10);
560
561 if (s == p)
562 error("%s: bad number", s);
563
564 if (errno != 0)
565 error((errno == EINVAL) ? "%s: bad number" : "%s: out of range", s);
566
567 while (isspace((unsigned char)*p))
568 p++;
569
570 if (*p)
571 error("%s: bad number", s);
572
573 return r;
574}
575
576static int
577intcmp(const char *s1, const char *s2)
578{
579 intmax_t q1, q2;
580
581 q1 = getq(s1);
582 q2 = getq(s2);
583
584 if (q1 > q2)
585 return 1;
586
587 if (q1 < q2)
588 return -1;
589
590 return 0;
591}
592
593static int
594newerf(const char *f1, const char *f2)
595{
596 struct stat b1, b2;
597
598 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
599 return 0;
600
601 if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec)
602 return 1;
603 if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec)
604 return 0;
605
606 return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec);
607}
608
609static int
610olderf(const char *f1, const char *f2)
611{
612 return (newerf(f2, f1));
613}
614
615static int
616equalf(const char *f1, const char *f2)
617{
618 struct stat b1, b2;
619
620 return (
621 stat(f1, &b1) == 0 && stat(f2, &b2) == 0 && b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino
622 );
623}