813cde47c5190c116c437cc535e1140ae83e42bb
[oweals/busybox.git] / coreutils / test.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * test implementation for busybox
4  *
5  * Copyright (c) by a whole pile of folks:
6  *
7  *     test(1); version 7-like  --  author Erik Baalbergen
8  *     modified by Eric Gisin to be used as built-in.
9  *     modified by Arnold Robbins to add SVR3 compatibility
10  *     (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
11  *     modified by J.T. Conklin for NetBSD.
12  *     modified by Herbert Xu to be used as built-in in ash.
13  *     modified by Erik Andersen <andersen@codepoet.org> to be used
14  *     in busybox.
15  *     modified by Bernhard Fischer to be useable (i.e. a bit less bloaty).
16  *
17  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
18  *
19  * Original copyright notice states:
20  *     "This program is in the Public Domain."
21  */
22
23 #include "busybox.h"
24 #include <unistd.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <setjmp.h>
29
30 /* test(1) accepts the following grammar:
31         oexpr   ::= aexpr | aexpr "-o" oexpr ;
32         aexpr   ::= nexpr | nexpr "-a" aexpr ;
33         nexpr   ::= primary | "!" primary
34         primary ::= unary-operator operand
35                 | operand binary-operator operand
36                 | operand
37                 | "(" oexpr ")"
38                 ;
39         unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
40                 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
41
42         binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
43                         "-nt"|"-ot"|"-ef";
44         operand ::= <any legal UNIX file name>
45 */
46
47 enum token {
48         EOI,
49         FILRD,
50         FILWR,
51         FILEX,
52         FILEXIST,
53         FILREG,
54         FILDIR,
55         FILCDEV,
56         FILBDEV,
57         FILFIFO,
58         FILSOCK,
59         FILSYM,
60         FILGZ,
61         FILTT,
62         FILSUID,
63         FILSGID,
64         FILSTCK,
65         FILNT,
66         FILOT,
67         FILEQ,
68         FILUID,
69         FILGID,
70         STREZ,
71         STRNZ,
72         STREQ,
73         STRNE,
74         STRLT,
75         STRGT,
76         INTEQ,
77         INTNE,
78         INTGE,
79         INTGT,
80         INTLE,
81         INTLT,
82         UNOT,
83         BAND,
84         BOR,
85         LPAREN,
86         RPAREN,
87         OPERAND
88 };
89 #define __is_int_op(a) (((unsigned char)((a) - INTEQ)) <= 5)
90 #define __is_str_op(a) (((unsigned char)((a) - STREZ)) <= 5)
91 #define __is_file_op(a) (((unsigned char)((a) - FILNT)) <= 2)
92 #define __is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2)
93 #define __is_file_type(a) (((unsigned char)((a) - FILREG)) <= 5)
94 #define __is_file_bit(a) (((unsigned char)((a) - FILSUID)) <= 2)
95 enum token_types {
96         UNOP,
97         BINOP,
98         BUNOP,
99         BBINOP,
100         PAREN
101 };
102
103 static const struct t_op {
104         const char * const op_text;
105         unsigned char op_num, op_type;
106 } ops[] = {
107         {
108         "-r", FILRD, UNOP}, {
109         "-w", FILWR, UNOP}, {
110         "-x", FILEX, UNOP}, {
111         "-e", FILEXIST, UNOP}, {
112         "-f", FILREG, UNOP}, {
113         "-d", FILDIR, UNOP}, {
114         "-c", FILCDEV, UNOP}, {
115         "-b", FILBDEV, UNOP}, {
116         "-p", FILFIFO, UNOP}, {
117         "-u", FILSUID, UNOP}, {
118         "-g", FILSGID, UNOP}, {
119         "-k", FILSTCK, UNOP}, {
120         "-s", FILGZ, UNOP}, {
121         "-t", FILTT, UNOP}, {
122         "-z", STREZ, UNOP}, {
123         "-n", STRNZ, UNOP}, {
124         "-h", FILSYM, UNOP},    /* for backwards compat */
125         {
126         "-O", FILUID, UNOP}, {
127         "-G", FILGID, UNOP}, {
128         "-L", FILSYM, UNOP}, {
129         "-S", FILSOCK, UNOP}, {
130         "=", STREQ, BINOP}, {
131         "==", STREQ, BINOP}, {
132         "!=", STRNE, BINOP}, {
133         "<", STRLT, BINOP}, {
134         ">", STRGT, BINOP}, {
135         "-eq", INTEQ, BINOP}, {
136         "-ne", INTNE, BINOP}, {
137         "-ge", INTGE, BINOP}, {
138         "-gt", INTGT, BINOP}, {
139         "-le", INTLE, BINOP}, {
140         "-lt", INTLT, BINOP}, {
141         "-nt", FILNT, BINOP}, {
142         "-ot", FILOT, BINOP}, {
143         "-ef", FILEQ, BINOP}, {
144         "!", UNOT, BUNOP}, {
145         "-a", BAND, BBINOP}, {
146         "-o", BOR, BBINOP}, {
147         "(", LPAREN, PAREN}, {
148         ")", RPAREN, PAREN}, {
149         0, 0, 0}
150 };
151
152 #ifdef CONFIG_FEATURE_TEST_64
153 typedef int64_t arith_t;
154 #else
155 typedef int arith_t;
156 #endif
157
158 static char **t_wp;
159 static struct t_op const *t_wp_op;
160 static gid_t *group_array;
161 static int ngroups;
162
163 static enum token t_lex(char *s);
164 static arith_t oexpr(enum token n);
165 static arith_t aexpr(enum token n);
166 static arith_t nexpr(enum token n);
167 static int binop(void);
168 static arith_t primary(enum token n);
169 static int filstat(char *nm, enum token mode);
170 static arith_t getn(const char *s);
171 static int newerf(const char *f1, const char *f2);
172 static int olderf(const char *f1, const char *f2);
173 static int equalf(const char *f1, const char *f2);
174 static int test_eaccess(char *path, int mode);
175 static int is_a_group_member(gid_t gid);
176 static void initialize_group_array(void);
177
178 static jmp_buf leaving;
179
180 int bb_test(int argc, char **argv)
181 {
182         int res;
183
184         if (LONE_CHAR(argv[0], '[')) {
185                 --argc;
186                 if (NOT_LONE_CHAR(argv[argc], ']')) {
187                         bb_error_msg("missing ]");
188                         return 2;
189                 }
190                 argv[argc] = NULL;
191         } else if (strcmp(argv[0], "[[") == 0) {
192                 --argc;
193                 if (strcmp(argv[argc], "]]")) {
194                         bb_error_msg("missing ]]");
195                         return 2;
196                 }
197                 argv[argc] = NULL;
198         }
199
200         res = setjmp(leaving);
201         if (res)
202                 return res;
203
204         /* resetting ngroups is probably unnecessary.  it will
205          * force a new call to getgroups(), which prevents using
206          * group data fetched during a previous call.  but the
207          * only way the group data could be stale is if there's
208          * been an intervening call to setgroups(), and this
209          * isn't likely in the case of a shell.  paranoia
210          * prevails...
211          */
212          ngroups = 0;
213
214         /* Implement special cases from POSIX.2, section 4.62.4 */
215         if (argc == 1)
216                 return 1;
217         if (argc == 2)
218                 return *argv[1] == '\0';
219 //assert(argc);
220         if (LONE_CHAR(argv[1], '!')) {
221                 bool _off;
222                 if (argc == 3)
223                         return *argv[2] != '\0';
224                 _off = argc - 4;
225                 if (t_lex(argv[2+_off]), t_wp_op && t_wp_op->op_type == BINOP) {
226                         t_wp = &argv[1+_off];
227                         return binop() == 0;
228                 }
229         }
230         t_wp = &argv[1];
231         res = !oexpr(t_lex(*t_wp));
232
233         if (*t_wp != NULL && *++t_wp != NULL) {
234                 bb_error_msg("%s: unknown operand", *t_wp);
235                 return 2;
236         }
237         return res;
238 }
239
240 static void syntax(const char *op, const char *msg)
241 {
242         if (op && *op) {
243                 bb_error_msg("%s: %s", op, msg);
244         } else {
245                 bb_error_msg("%s", msg);
246         }
247         longjmp(leaving, 2);
248 }
249
250 static arith_t oexpr(enum token n)
251 {
252         arith_t res;
253
254         res = aexpr(n);
255         if (t_lex(*++t_wp) == BOR) {
256                 return oexpr(t_lex(*++t_wp)) || res;
257         }
258         t_wp--;
259         return res;
260 }
261
262 static arith_t aexpr(enum token n)
263 {
264         arith_t res;
265
266         res = nexpr(n);
267         if (t_lex(*++t_wp) == BAND)
268                 return aexpr(t_lex(*++t_wp)) && res;
269         t_wp--;
270         return res;
271 }
272
273 static arith_t nexpr(enum token n)
274 {
275         if (n == UNOT)
276                 return !nexpr(t_lex(*++t_wp));
277         return primary(n);
278 }
279
280 static arith_t primary(enum token n)
281 {
282         arith_t res;
283
284         if (n == EOI) {
285                 syntax(NULL, "argument expected");
286         }
287         if (n == LPAREN) {
288                 res = oexpr(t_lex(*++t_wp));
289                 if (t_lex(*++t_wp) != RPAREN)
290                         syntax(NULL, "closing paren expected");
291                 return res;
292         }
293         if (t_wp_op && t_wp_op->op_type == UNOP) {
294                 /* unary expression */
295                 if (*++t_wp == NULL)
296                         syntax(t_wp_op->op_text, "argument expected");
297                 if (n == STREZ)
298                         return strlen(*t_wp) == 0;
299                 else if (n == STRNZ)
300                         return strlen(*t_wp) != 0;
301                 else if (n == FILTT)
302                         return isatty(getn(*t_wp));
303                 else
304                         return filstat(*t_wp, n);
305         }
306
307         if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
308                 return binop();
309         }
310
311         return strlen(*t_wp) > 0;
312 }
313
314 static int binop(void)
315 {
316         const char *opnd1, *opnd2;
317         struct t_op const *op;
318         smallint val1, val2;
319
320         opnd1 = *t_wp;
321         (void) t_lex(*++t_wp);
322         op = t_wp_op;
323
324         if ((opnd2 = *++t_wp) == (char *) 0)
325                 syntax(op->op_text, "argument expected");
326
327         if (__is_int_op(op->op_num)) {
328                 val1 = getn(opnd1);
329                 val2 = getn(opnd2);
330                 if (op->op_num == INTEQ)
331                         return val1 == val2;
332                 if (op->op_num == INTNE)
333                         return val1 != val2;
334                 if (op->op_num == INTGE)
335                         return val1 >= val2;
336                 if (op->op_num == INTGT)
337                         return val1 >  val2;
338                 if (op->op_num == INTLE)
339                         return val1 <= val2;
340                 if (op->op_num == INTLT)
341                         return val1 <  val2;
342         }
343         if (__is_str_op(op->op_num)) {
344                 val1 = strcmp(opnd1, opnd2);
345                 if (op->op_num == STREQ)
346                         return val1 == 0;
347                 if (op->op_num == STRNE)
348                         return val1 != 0;
349                 if (op->op_num == STRLT)
350                         return val1 < 0;
351                 if (op->op_num == STRGT)
352                         return val1 > 0;
353         }
354         /* We are sure that these three are by now the only binops we didn't check
355          * yet, so we do not check if the class is correct:
356          */
357 /*      if (__is_file_op(op->op_num)) */
358         {
359                 struct stat b1, b2;
360
361                 if (!(!stat(opnd1, &b1) && !stat(opnd2, &b2)))
362                         return 0; /* false, since stat failed */
363                 if (op->op_num == FILNT)
364                         return b1.st_mtime > b2.st_mtime;
365                 if (op->op_num == FILOT)
366                         return b1.st_mtime < b2.st_mtime;
367                 if (op->op_num == FILEQ)
368                         return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
369         }
370         return 1; /* NOTREACHED */
371 }
372
373 static int filstat(char *nm, enum token mode)
374 {
375         struct stat s;
376         int i;
377
378         if (mode == FILSYM) {
379 #ifdef S_IFLNK
380                 if (lstat(nm, &s) == 0) {
381                         i = S_IFLNK;
382                         goto filetype;
383                 }
384 #endif
385                 return 0;
386         }
387
388         if (stat(nm, &s) != 0)
389                 return 0;
390         if (mode == FILEXIST)
391                 return 1;
392         else if (__is_file_access(mode)) {
393                 if (mode == FILRD)
394                         i = R_OK;
395                 if (mode == FILWR)
396                         i = W_OK;
397                 if (mode == FILEX)
398                         i = X_OK;
399                 return test_eaccess(nm, i) == 0;
400         }
401         else if (__is_file_type(mode)) {
402                 if (mode == FILREG)
403                         i = S_IFREG;
404                 if (mode == FILDIR)
405                         i = S_IFDIR;
406                 if (mode == FILCDEV)
407                         i = S_IFCHR;
408                 if (mode == FILBDEV)
409                         i = S_IFBLK;
410                 if (mode == FILFIFO) {
411 #ifdef S_IFIFO
412                         i = S_IFIFO;
413 #else
414                         return 0;
415 #endif
416                 }
417                 if (mode == FILSOCK) {
418 #ifdef S_IFSOCK
419                         i = S_IFSOCK;
420 #else
421                         return 0;
422 #endif
423                 }
424 filetype:
425                 return ((s.st_mode & S_IFMT) == i);
426         }
427         else if (__is_file_bit(mode)) {
428                 if (mode == FILSUID)
429                         i = S_ISUID;
430                 if (mode == FILSGID)
431                         i = S_ISGID;
432                 if (mode == FILSTCK)
433                         i = S_ISVTX;
434                 return ((s.st_mode & i) != 0);
435         }
436         else if (mode == FILGZ)
437                 return s.st_size > 0L;
438         else if (mode == FILUID)
439                 return s.st_uid == geteuid();
440         else if (mode == FILGID)
441                 return s.st_gid == getegid();
442         else
443                 return 1; /* NOTREACHED */
444
445 }
446
447 static enum token t_lex(char *s)
448 {
449         struct t_op const *op = ops;
450
451         if (s == 0) {
452                 t_wp_op = (struct t_op *) 0;
453                 return EOI;
454         }
455         while (op->op_text) {
456                 if (strcmp(s, op->op_text) == 0) {
457                         t_wp_op = op;
458                         return op->op_num;
459                 }
460                 op++;
461         }
462         t_wp_op = (struct t_op *) 0;
463         return OPERAND;
464 }
465
466 /* atoi with error detection */
467 //XXX: FIXME: duplicate of existing libbb function?
468 static arith_t getn(const char *s)
469 {
470         char *p;
471 #ifdef CONFIG_FEATURE_TEST_64
472         long long r;
473 #else
474         long r;
475 #endif
476
477         errno = 0;
478 #ifdef CONFIG_FEATURE_TEST_64
479         r = strtoll(s, &p, 10);
480 #else
481         r = strtol(s, &p, 10);
482 #endif
483
484         if (errno != 0)
485                 syntax(s, "out of range");
486
487         if (*(skip_whitespace(p)))
488                 syntax(s, "bad number");
489
490         return r;
491 }
492
493 static int newerf(const char *f1, const char *f2)
494 {
495         struct stat b1, b2;
496
497         return (stat(f1, &b1) == 0 &&
498                         stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime);
499 }
500
501 static int olderf(const char *f1, const char *f2)
502 {
503         struct stat b1, b2;
504
505         return (stat(f1, &b1) == 0 &&
506                         stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime);
507 }
508
509 static int equalf(const char *f1, const char *f2)
510 {
511         struct stat b1, b2;
512
513         return (stat(f1, &b1) == 0 &&
514                         stat(f2, &b2) == 0 &&
515                         b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
516 }
517
518 /* Do the same thing access(2) does, but use the effective uid and gid,
519    and don't make the mistake of telling root that any file is
520    executable. */
521 static int test_eaccess(char *path, int mode)
522 {
523         struct stat st;
524         unsigned int euid = geteuid();
525
526         if (stat(path, &st) < 0)
527                 return -1;
528
529         if (euid == 0) {
530                 /* Root can read or write any file. */
531                 if (mode != X_OK)
532                         return 0;
533
534                 /* Root can execute any file that has any one of the execute
535                    bits set. */
536                 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
537                         return 0;
538         }
539
540         if (st.st_uid == euid)  /* owner */
541                 mode <<= 6;
542         else if (is_a_group_member(st.st_gid))
543                 mode <<= 3;
544
545         if (st.st_mode & mode)
546                 return 0;
547
548         return -1;
549 }
550
551 static void initialize_group_array(void)
552 {
553         ngroups = getgroups(0, NULL);
554         if (ngroups > 0) {
555                 group_array = xmalloc(ngroups * sizeof(gid_t));
556                 getgroups(ngroups, group_array);
557         }
558 }
559
560 /* Return non-zero if GID is one that we have in our groups list. */
561 //XXX: FIXME: duplicate of existing libbb function?
562 // see toplevel TODO file:
563 // possible code duplication ingroup() and is_a_group_member()
564 static int is_a_group_member(gid_t gid)
565 {
566         int i;
567
568         /* Short-circuit if possible, maybe saving a call to getgroups(). */
569         if (gid == getgid() || gid == getegid())
570                 return 1;
571
572         if (ngroups == 0)
573                 initialize_group_array();
574
575         /* Search through the list looking for GID. */
576         for (i = 0; i < ngroups; i++)
577                 if (gid == group_array[i])
578                         return 1;
579
580         return 0;
581 }
582
583
584 /* applet entry point */
585
586 int test_main(int argc, char **argv);
587 int test_main(int argc, char **argv)
588 {
589         return bb_test(argc, argv);
590 }
591