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