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