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/types.h>
46#include <sys/stat.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 {
94 UNOP = 0x100,
95 BINOP = 0x200,
96 BUNOP = 0x300,
97 BBINOP = 0x400,
98 PAREN = 0x500
99};
100
101enum token {
102 EOI,
103 OPERAND,
104 FILRD = UNOP + 1,
105 FILWR,
106 FILEX,
107 FILEXIST,
108 FILREG,
109 FILDIR,
110 FILCDEV,
111 FILBDEV,
112 FILFIFO,
113 FILSOCK,
114 FILSYM,
115 FILGZ,
116 FILTT,
117 FILSUID,
118 FILSGID,
119 FILSTCK,
120 STREZ,
121 STRNZ,
122 FILUID,
123 FILGID,
124 FILNT = BINOP + 1,
125 FILOT,
126 FILEQ,
127 STREQ,
128 STRNE,
129 STRLT,
130 STRGT,
131 INTEQ,
132 INTNE,
133 INTGE,
134 INTGT,
135 INTLE,
136 INTLT,
137 UNOT = BUNOP + 1,
138 BAND = BBINOP + 1,
139 BOR,
140 LPAREN = PAREN + 1,
141 RPAREN
142};
143
144#define TOKEN_TYPE(token) ((token) & 0xff00)
145
146static const struct t_op {
147 char op_text[2] __nonstring;
148 short op_num;
149} ops1[] = {
150 {"=", STREQ},
151 {"<", STRLT},
152 {">", STRGT},
153 {"!", UNOT},
154 {"(", LPAREN},
155 {")", RPAREN},
156}, opsm1[] = {
157 {"r", FILRD},
158 {"w", FILWR},
159 {"x", FILEX},
160 {"e", FILEXIST},
161 {"f", FILREG},
162 {"d", FILDIR},
163 {"c", FILCDEV},
164 {"b", FILBDEV},
165 {"p", FILFIFO},
166 {"u", FILSUID},
167 {"g", FILSGID},
168 {"k", FILSTCK},
169 {"s", FILGZ},
170 {"t", FILTT},
171 {"z", STREZ},
172 {"n", STRNZ},
173 {"h", FILSYM}, /* for backwards compat */
174 {"O", FILUID},
175 {"G", FILGID},
176 {"L", FILSYM},
177 {"S", FILSOCK},
178 {"a", BAND},
179 {"o", BOR},
180}, ops2[] = {
181 {"==", STREQ},
182 {"!=", STRNE},
183}, opsm2[] = {
184 {"eq", INTEQ},
185 {"ne", INTNE},
186 {"ge", INTGE},
187 {"gt", INTGT},
188 {"le", INTLE},
189 {"lt", INTLT},
190 {"nt", FILNT},
191 {"ot", FILOT},
192 {"ef", FILEQ},
193};
194
195static int nargc;
196static char **t_wp;
197static int parenlevel;
198
199static int aexpr(enum token);
200static int binop(enum token);
201static int equalf(const char *, const char *);
202static int filstat(char *, enum token);
203static int getn(const char *);
204static intmax_t getq(const char *);
205static int intcmp(const char *, const char *);
206static int isunopoperand(void);
207static int islparenoperand(void);
208static int isrparenoperand(void);
209static int newerf(const char *, const char *);
210static int nexpr(enum token);
211static int oexpr(enum token);
212static int olderf(const char *, const char *);
213static int primary(enum token);
214static void syntax(const char *, const char *);
215static enum token t_lex(char *);
216
217int
218main(int argc, char **argv)
219{
220 int res;
221 char *p;
222
223 if ((p = strrchr(argv[0], '/')) == NULL)
224 p = argv[0];
225 else
226 p++;
227 if (strcmp(p, "[") == 0) {
228 if (strcmp(argv[--argc], "]") != 0)
229 error("missing ']'");
230 argv[argc] = NULL;
231 }
232
233 /* no expression => false */
234 if (--argc <= 0)
235 return 1;
236
237#ifndef SHELL
238 (void)setlocale(LC_CTYPE, "");
239#endif
240 nargc = argc;
241 t_wp = &argv[1];
242 parenlevel = 0;
243 if (nargc == 4 && strcmp(*t_wp, "!") == 0) {
244 /* Things like ! "" -o x do not fit in the normal grammar. */
245 --nargc;
246 ++t_wp;
247 res = oexpr(t_lex(*t_wp));
248 } else
249 res = !oexpr(t_lex(*t_wp));
250
251 if (--nargc > 0)
252 syntax(*t_wp, "unexpected operator");
253
254 return res;
255}
256
257static void
258syntax(const char *op, const char *msg)
259{
260
261 if (op && *op)
262 error("%s: %s", op, msg);
263 else
264 error("%s", msg);
265}
266
267static int
268oexpr(enum token n)
269{
270 int res;
271
272 res = aexpr(n);
273 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR)
274 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ||
275 res;
276 t_wp--;
277 nargc++;
278 return res;
279}
280
281static int
282aexpr(enum token n)
283{
284 int res;
285
286 res = nexpr(n);
287 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND)
288 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) &&
289 res;
290 t_wp--;
291 nargc++;
292 return res;
293}
294
295static int
296nexpr(enum token n)
297{
298 if (n == UNOT)
299 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL));
300 return primary(n);
301}
302
303static int
304primary(enum token n)
305{
306 enum token nn;
307 int res;
308
309 if (n == EOI)
310 return 0; /* missing expression */
311 if (n == LPAREN) {
312 parenlevel++;
313 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ==
314 RPAREN) {
315 parenlevel--;
316 return 0; /* missing expression */
317 }
318 res = oexpr(nn);
319 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
320 syntax(NULL, "closing paren expected");
321 parenlevel--;
322 return res;
323 }
324 if (TOKEN_TYPE(n) == UNOP) {
325 /* unary expression */
326 if (--nargc == 0)
327 syntax(NULL, "argument expected"); /* impossible */
328 switch (n) {
329 case STREZ:
330 return strlen(*++t_wp) == 0;
331 case STRNZ:
332 return strlen(*++t_wp) != 0;
333 case FILTT:
334 return isatty(getn(*++t_wp));
335 default:
336 return filstat(*++t_wp, n);
337 }
338 }
339
340 nn = t_lex(nargc > 0 ? t_wp[1] : NULL);
341 if (TOKEN_TYPE(nn) == BINOP)
342 return binop(nn);
343
344 return strlen(*t_wp) > 0;
345}
346
347static int
348binop(enum token n)
349{
350 const char *opnd1, *op, *opnd2;
351
352 opnd1 = *t_wp;
353 op = nargc > 0 ? (--nargc, *++t_wp) : NULL;
354
355 if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
356 syntax(op, "argument expected");
357
358 switch (n) {
359 case STREQ:
360 return strcmp(opnd1, opnd2) == 0;
361 case STRNE:
362 return strcmp(opnd1, opnd2) != 0;
363 case STRLT:
364 return strcmp(opnd1, opnd2) < 0;
365 case STRGT:
366 return strcmp(opnd1, opnd2) > 0;
367 case INTEQ:
368 return intcmp(opnd1, opnd2) == 0;
369 case INTNE:
370 return intcmp(opnd1, opnd2) != 0;
371 case INTGE:
372 return intcmp(opnd1, opnd2) >= 0;
373 case INTGT:
374 return intcmp(opnd1, opnd2) > 0;
375 case INTLE:
376 return intcmp(opnd1, opnd2) <= 0;
377 case INTLT:
378 return intcmp(opnd1, opnd2) < 0;
379 case FILNT:
380 return newerf (opnd1, opnd2);
381 case FILOT:
382 return olderf (opnd1, opnd2);
383 case FILEQ:
384 return equalf (opnd1, opnd2);
385 default:
386 abort();
387 /* NOTREACHED */
388 }
389}
390
391static int
392filstat(char *nm, enum token mode)
393{
394 struct stat s;
395
396 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
397 return 0;
398
399 switch (mode) {
400 case FILRD:
401 return (eaccess(nm, R_OK) == 0);
402 case FILWR:
403 return (eaccess(nm, W_OK) == 0);
404 case FILEX:
405 /* XXX work around eaccess(2) false positives for superuser */
406 if (eaccess(nm, X_OK) != 0)
407 return 0;
408 if (S_ISDIR(s.st_mode) || geteuid() != 0)
409 return 1;
410 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
411 case FILEXIST:
412 return (eaccess(nm, F_OK) == 0);
413 case FILREG:
414 return S_ISREG(s.st_mode);
415 case FILDIR:
416 return S_ISDIR(s.st_mode);
417 case FILCDEV:
418 return S_ISCHR(s.st_mode);
419 case FILBDEV:
420 return S_ISBLK(s.st_mode);
421 case FILFIFO:
422 return S_ISFIFO(s.st_mode);
423 case FILSOCK:
424 return S_ISSOCK(s.st_mode);
425 case FILSYM:
426 return S_ISLNK(s.st_mode);
427 case FILSUID:
428 return (s.st_mode & S_ISUID) != 0;
429 case FILSGID:
430 return (s.st_mode & S_ISGID) != 0;
431 case FILSTCK:
432 return (s.st_mode & S_ISVTX) != 0;
433 case FILGZ:
434 return s.st_size > (off_t)0;
435 case FILUID:
436 return s.st_uid == geteuid();
437 case FILGID:
438 return s.st_gid == getegid();
439 default:
440 return 1;
441 }
442}
443
444static int
445find_op_1char(const struct t_op *op, const struct t_op *end, const char *s)
446{
447 char c;
448
449 c = s[0];
450 while (op != end) {
451 if (c == *op->op_text)
452 return op->op_num;
453 op++;
454 }
455 return OPERAND;
456}
457
458static int
459find_op_2char(const struct t_op *op, const struct t_op *end, const char *s)
460{
461 while (op != end) {
462 if (s[0] == op->op_text[0] && s[1] == op->op_text[1])
463 return op->op_num;
464 op++;
465 }
466 return OPERAND;
467}
468
469static int
470find_op(const char *s)
471{
472 if (s[0] == '\0')
473 return OPERAND;
474 else if (s[1] == '\0')
475 return find_op_1char(ops1, (&ops1)[1], s);
476 else if (s[2] == '\0')
477 return s[0] == '-' ? find_op_1char(opsm1, (&opsm1)[1], s + 1) :
478 find_op_2char(ops2, (&ops2)[1], s);
479 else if (s[3] == '\0')
480 return s[0] == '-' ? find_op_2char(opsm2, (&opsm2)[1], s + 1) :
481 OPERAND;
482 else
483 return OPERAND;
484}
485
486static enum token
487t_lex(char *s)
488{
489 int num;
490
491 if (s == NULL) {
492 return EOI;
493 }
494 num = find_op(s);
495 if (((TOKEN_TYPE(num) == UNOP || TOKEN_TYPE(num) == BUNOP)
496 && isunopoperand()) ||
497 (num == LPAREN && islparenoperand()) ||
498 (num == RPAREN && isrparenoperand()))
499 return OPERAND;
500 return num;
501}
502
503static int
504isunopoperand(void)
505{
506 char *s;
507 char *t;
508 int num;
509
510 if (nargc == 1)
511 return 1;
512 s = *(t_wp + 1);
513 if (nargc == 2)
514 return parenlevel == 1 && strcmp(s, ")") == 0;
515 t = *(t_wp + 2);
516 num = find_op(s);
517 return TOKEN_TYPE(num) == BINOP &&
518 (parenlevel == 0 || t[0] != ')' || t[1] != '\0');
519}
520
521static int
522islparenoperand(void)
523{
524 char *s;
525 int num;
526
527 if (nargc == 1)
528 return 1;
529 s = *(t_wp + 1);
530 if (nargc == 2)
531 return parenlevel == 1 && strcmp(s, ")") == 0;
532 if (nargc != 3)
533 return 0;
534 num = find_op(s);
535 return TOKEN_TYPE(num) == BINOP;
536}
537
538static int
539isrparenoperand(void)
540{
541 char *s;
542
543 if (nargc == 1)
544 return 0;
545 s = *(t_wp + 1);
546 if (nargc == 2)
547 return parenlevel == 1 && strcmp(s, ")") == 0;
548 return 0;
549}
550
551/* atoi with error detection */
552static int
553getn(const char *s)
554{
555 char *p;
556 long r;
557
558 errno = 0;
559 r = strtol(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" :
566 "%s: out of range", s);
567
568 while (isspace((unsigned char)*p))
569 p++;
570
571 if (*p)
572 error("%s: bad number", s);
573
574 return (int) r;
575}
576
577/* atoi with error detection and 64 bit range */
578static intmax_t
579getq(const char *s)
580{
581 char *p;
582 intmax_t r;
583
584 errno = 0;
585 r = strtoimax(s, &p, 10);
586
587 if (s == p)
588 error("%s: bad number", s);
589
590 if (errno != 0)
591 error((errno == EINVAL) ? "%s: bad number" :
592 "%s: out of range", s);
593
594 while (isspace((unsigned char)*p))
595 p++;
596
597 if (*p)
598 error("%s: bad number", s);
599
600 return r;
601}
602
603static int
604intcmp (const char *s1, const char *s2)
605{
606 intmax_t q1, q2;
607
608
609 q1 = getq(s1);
610 q2 = getq(s2);
611
612 if (q1 > q2)
613 return 1;
614
615 if (q1 < q2)
616 return -1;
617
618 return 0;
619}
620
621static int
622newerf (const char *f1, const char *f2)
623{
624 struct stat b1, b2;
625
626 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
627 return 0;
628
629 if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec)
630 return 1;
631 if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec)
632 return 0;
633
634 return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec);
635}
636
637static int
638olderf (const char *f1, const char *f2)
639{
640 return (newerf(f2, f1));
641}
642
643static int
644equalf (const char *f1, const char *f2)
645{
646 struct stat b1, b2;
647
648 return (stat (f1, &b1) == 0 &&
649 stat (f2, &b2) == 0 &&
650 b1.st_dev == b2.st_dev &&
651 b1.st_ino == b2.st_ino);
652}