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