Merge from vendor branch READLINE:
[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.6 2004/08/30 19:27:21 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 #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 long long getll (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(int argc, char **argv)
194 {
195         gid_t   egid, gid;
196         uid_t   euid, uid;
197         int     res;
198         char    *p;
199
200         if ((p = strrchr(argv[0], '/')) == NULL)
201                 p = argv[0];
202         else
203                 p++;
204         if (strcmp(p, "[") == 0) {
205                 if (strcmp(argv[--argc], "]") != 0)
206                         error("missing ]");
207                 argv[argc] = NULL;
208         }
209
210         /* no expression => false */
211         if (--argc <= 0)
212                 return 1;
213
214         /* XXX work around the absence of an eaccess(2) syscall */
215         egid = getegid();
216         euid = geteuid();
217         gid = getgid();
218         uid = getuid();
219         (void)setregid(egid, gid);
220         (void)setreuid(euid, uid);
221
222         nargc = argc;
223         t_wp = &argv[1];
224         res = !oexpr(t_lex(*t_wp));
225
226         if (--nargc > 0)
227                 syntax(*t_wp, "unexpected operator");
228         (void)setregid(gid, egid);
229         (void)setreuid(uid, euid);
230
231         return res;
232 }
233
234 static void
235 syntax(const char *op, const char *msg)
236 {
237
238         if (op && *op)
239                 error("%s: %s", op, msg);
240         else
241                 error("%s", msg);
242 }
243
244 static int
245 oexpr(enum token n)
246 {
247         int res;
248
249         res = aexpr(n);
250         if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR)
251                 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ||
252                     res;
253         t_wp--;
254         nargc++;
255         return res;
256 }
257
258 static int
259 aexpr(enum token n)
260 {
261         int res;
262
263         res = nexpr(n);
264         if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND)
265                 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) &&
266                     res;
267         t_wp--;
268         nargc++;
269         return res;
270 }
271
272 static int
273 nexpr(enum token n)
274 {
275         if (n == UNOT)
276                 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL));
277         return primary(n);
278 }
279
280 static int
281 primary(enum token n)
282 {
283         enum token nn;
284         int res;
285
286         if (n == EOI)
287                 return 0;               /* missing expression */
288         if (n == LPAREN) {
289                 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ==
290                     RPAREN)
291                         return 0;       /* missing expression */
292                 res = oexpr(nn);
293                 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
294                         syntax(NULL, "closing paren expected");
295                 return res;
296         }
297         if (t_wp_op && t_wp_op->op_type == UNOP) {
298                 /* unary expression */
299                 if (--nargc == 0)
300                         syntax(t_wp_op->op_text, "argument expected");
301                 switch (n) {
302                 case STREZ:
303                         return strlen(*++t_wp) == 0;
304                 case STRNZ:
305                         return strlen(*++t_wp) != 0;
306                 case FILTT:
307                         return isatty(getn(*++t_wp));
308                 default:
309                         return filstat(*++t_wp, n);
310                 }
311         }
312
313         if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type ==
314             BINOP) {
315                 return binop();
316         }
317
318         return strlen(*t_wp) > 0;
319 }
320
321 static int
322 binop(void)
323 {
324         const char *opnd1, *opnd2;
325         struct t_op const *op;
326
327         opnd1 = *t_wp;
328         (void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL);
329         op = t_wp_op;
330
331         if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
332                 syntax(op->op_text, "argument expected");
333
334         switch (op->op_num) {
335         case STREQ:
336                 return strcmp(opnd1, opnd2) == 0;
337         case STRNE:
338                 return strcmp(opnd1, opnd2) != 0;
339         case STRLT:
340                 return strcmp(opnd1, opnd2) < 0;
341         case STRGT:
342                 return strcmp(opnd1, opnd2) > 0;
343         case INTEQ:
344                 return intcmp(opnd1, opnd2) == 0;
345         case INTNE:
346                 return intcmp(opnd1, opnd2) != 0;
347         case INTGE:
348                 return intcmp(opnd1, opnd2) >= 0;
349         case INTGT:
350                 return intcmp(opnd1, opnd2) > 0;
351         case INTLE:
352                 return intcmp(opnd1, opnd2) <= 0;
353         case INTLT:
354                 return intcmp(opnd1, opnd2) < 0;
355         case FILNT:
356                 return newerf (opnd1, opnd2);
357         case FILOT:
358                 return olderf (opnd1, opnd2);
359         case FILEQ:
360                 return equalf (opnd1, opnd2);
361         default:
362                 abort();
363                 /* NOTREACHED */
364         }
365 }
366
367 static int
368 filstat(char *nm, enum token mode)
369 {
370         struct stat s;
371
372         if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
373                 return 0;
374
375         switch (mode) {
376         case FILRD:
377                 return access(nm, R_OK) == 0;
378         case FILWR:
379                 return access(nm, W_OK) == 0;
380         case FILEX:
381                 /* XXX work around access(2) false positives for superuser */
382                 if (access(nm, X_OK) != 0)
383                         return 0;
384                 if (S_ISDIR(s.st_mode) || getuid() != 0)
385                         return 1;
386                 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
387         case FILEXIST:
388                 return access(nm, F_OK) == 0;
389         case FILREG:
390                 return S_ISREG(s.st_mode);
391         case FILDIR:
392                 return S_ISDIR(s.st_mode);
393         case FILCDEV:
394                 return S_ISCHR(s.st_mode);
395         case FILBDEV:
396                 return S_ISBLK(s.st_mode);
397         case FILFIFO:
398                 return S_ISFIFO(s.st_mode);
399         case FILSOCK:
400                 return S_ISSOCK(s.st_mode);
401         case FILSYM:
402                 return S_ISLNK(s.st_mode);
403         case FILSUID:
404                 return (s.st_mode & S_ISUID) != 0;
405         case FILSGID:
406                 return (s.st_mode & S_ISGID) != 0;
407         case FILSTCK:
408                 return (s.st_mode & S_ISVTX) != 0;
409         case FILGZ:
410                 return s.st_size > (off_t)0;
411         case FILUID:
412                 return s.st_uid == geteuid();
413         case FILGID:
414                 return s.st_gid == getegid();
415         default:
416                 return 1;
417         }
418 }
419
420 static enum token
421 t_lex(char *s)
422 {
423         struct t_op const *op = ops;
424
425         if (s == 0) {
426                 t_wp_op = NULL;
427                 return EOI;
428         }
429         while (op->op_text) {
430                 if (strcmp(s, op->op_text) == 0) {
431                         if ((op->op_type == UNOP && isoperand()) ||
432                             (op->op_num == LPAREN && nargc == 1))
433                                 break;
434                         t_wp_op = op;
435                         return op->op_num;
436                 }
437                 op++;
438         }
439         t_wp_op = NULL;
440         return OPERAND;
441 }
442
443 static int
444 isoperand(void)
445 {
446         struct t_op const *op = ops;
447         char *s;
448         char *t;
449
450         if (nargc == 1)
451                 return 1;
452         if (nargc == 2)
453                 return 0;
454         s = *(t_wp + 1);
455         t = *(t_wp + 2);
456         while (op->op_text) {
457                 if (strcmp(s, op->op_text) == 0)
458                         return op->op_type == BINOP &&
459                             (t[0] != ')' || t[1] != '\0');
460                 op++;
461         }
462         return 0;
463 }
464
465 /* atoi with error detection */
466 static int
467 getn(const char *s)
468 {
469         char *p;
470         long r;
471
472         errno = 0;
473         r = strtol(s, &p, 10);
474
475         if (errno != 0)
476                 error((errno == EINVAL) ? "%s: bad number" :
477                                           "%s: out of range", s);
478
479         while (isspace((unsigned char)*p))
480                 p++;
481
482         if (*p)
483                 error("%s: bad number", s);
484
485         return (int) r;
486 }
487
488 /* atoi with error detection and 64 bit range */
489 static long long
490 getll(const char *s)
491 {
492         char *p;
493         long long r;
494
495         errno = 0;
496         r = strtoll(s, &p, 10);
497
498         if (errno != 0)
499                 error((errno == EINVAL) ? "%s: bad number" :
500                                           "%s: out of range", s);
501
502         while (isspace((unsigned char)*p))
503                 p++;
504
505         if (*p)
506                 error("%s: bad number", s);
507
508         return r;
509 }
510
511 static int
512 intcmp (const char *s1, const char *s2)
513 {
514         long long q1, q2;
515
516
517         q1 = getll(s1);
518         q2 = getll(s2);
519
520         if (q1 > q2)
521                 return 1;
522
523         if (q1 < q2)
524                 return -1;
525
526         return 0;
527 }
528
529 static int
530 newerf (const char *f1, const char *f2)
531 {
532         struct stat b1, b2;
533
534         if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
535                 return 0;
536
537         if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec)
538                 return 1;
539         if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec)
540                 return 0;
541
542         return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec);
543 }
544
545 static int
546 olderf (const char *f1, const char *f2)
547 {
548         return (newerf(f2, f1));
549 }
550
551 static int
552 equalf (const char *f1, const char *f2)
553 {
554         struct stat b1, b2;
555
556         return (stat (f1, &b1) == 0 &&
557                 stat (f2, &b2) == 0 &&
558                 b1.st_dev == b2.st_dev &&
559                 b1.st_ino == b2.st_ino);
560 }