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