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