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