find: improve usage text (Natanael Copa <natanael.copa@gmail.com>)
[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 /* UNUSED
172 static int newerf(const char *f1, const char *f2);
173 static int olderf(const char *f1, const char *f2);
174 static int equalf(const char *f1, const char *f2);
175 */
176 static int test_eaccess(char *path, int mode);
177 static int is_a_group_member(gid_t gid);
178 static void initialize_group_array(void);
179
180 static jmp_buf leaving;
181
182 int bb_test(int argc, char **argv)
183 {
184         int res;
185
186         if (LONE_CHAR(argv[0], '[')) {
187                 --argc;
188                 if (NOT_LONE_CHAR(argv[argc], ']')) {
189                         bb_error_msg("missing ]");
190                         return 2;
191                 }
192                 argv[argc] = NULL;
193         } else if (strcmp(argv[0], "[[") == 0) {
194                 --argc;
195                 if (strcmp(argv[argc], "]]")) {
196                         bb_error_msg("missing ]]");
197                         return 2;
198                 }
199                 argv[argc] = NULL;
200         }
201
202         res = setjmp(leaving);
203         if (res)
204                 return res;
205
206         /* resetting ngroups is probably unnecessary.  it will
207          * force a new call to getgroups(), which prevents using
208          * group data fetched during a previous call.  but the
209          * only way the group data could be stale is if there's
210          * been an intervening call to setgroups(), and this
211          * isn't likely in the case of a shell.  paranoia
212          * prevails...
213          */
214          ngroups = 0;
215
216         /* Implement special cases from POSIX.2, section 4.62.4 */
217         if (argc == 1)
218                 return 1;
219         if (argc == 2)
220                 return *argv[1] == '\0';
221 //assert(argc);
222         if (LONE_CHAR(argv[1], '!')) {
223                 bool _off;
224                 if (argc == 3)
225                         return *argv[2] != '\0';
226                 _off = argc - 4;
227                 if (t_lex(argv[2+_off]), t_wp_op && t_wp_op->op_type == BINOP) {
228                         t_wp = &argv[1+_off];
229                         return binop() == 0;
230                 }
231         }
232         t_wp = &argv[1];
233         res = !oexpr(t_lex(*t_wp));
234
235         if (*t_wp != NULL && *++t_wp != NULL) {
236                 bb_error_msg("%s: unknown operand", *t_wp);
237                 return 2;
238         }
239         return res;
240 }
241
242 static void syntax(const char *op, const char *msg)
243 {
244         if (op && *op) {
245                 bb_error_msg("%s: %s", op, msg);
246         } else {
247                 bb_error_msg("%s", msg);
248         }
249         longjmp(leaving, 2);
250 }
251
252 static arith_t oexpr(enum token n)
253 {
254         arith_t res;
255
256         res = aexpr(n);
257         if (t_lex(*++t_wp) == BOR) {
258                 return oexpr(t_lex(*++t_wp)) || res;
259         }
260         t_wp--;
261         return res;
262 }
263
264 static arith_t aexpr(enum token n)
265 {
266         arith_t res;
267
268         res = nexpr(n);
269         if (t_lex(*++t_wp) == BAND)
270                 return aexpr(t_lex(*++t_wp)) && res;
271         t_wp--;
272         return res;
273 }
274
275 static arith_t nexpr(enum token n)
276 {
277         if (n == UNOT)
278                 return !nexpr(t_lex(*++t_wp));
279         return primary(n);
280 }
281
282 static arith_t primary(enum token n)
283 {
284         arith_t res;
285
286         if (n == EOI) {
287                 syntax(NULL, "argument expected");
288         }
289         if (n == LPAREN) {
290                 res = oexpr(t_lex(*++t_wp));
291                 if (t_lex(*++t_wp) != RPAREN)
292                         syntax(NULL, "closing paren expected");
293                 return res;
294         }
295         if (t_wp_op && t_wp_op->op_type == UNOP) {
296                 /* unary expression */
297                 if (*++t_wp == NULL)
298                         syntax(t_wp_op->op_text, "argument expected");
299                 if (n == STREZ)
300                         return strlen(*t_wp) == 0;
301                 else if (n == STRNZ)
302                         return strlen(*t_wp) != 0;
303                 else if (n == FILTT)
304                         return isatty(getn(*t_wp));
305                 else
306                         return filstat(*t_wp, n);
307         }
308
309         if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
310                 return binop();
311         }
312
313         return strlen(*t_wp) > 0;
314 }
315
316 static int binop(void)
317 {
318         const char *opnd1, *opnd2;
319         struct t_op const *op;
320         smallint val1, val2;
321
322         opnd1 = *t_wp;
323         (void) t_lex(*++t_wp);
324         op = t_wp_op;
325
326         if ((opnd2 = *++t_wp) == (char *) 0)
327                 syntax(op->op_text, "argument expected");
328
329         if (__is_int_op(op->op_num)) {
330                 val1 = getn(opnd1);
331                 val2 = getn(opnd2);
332                 if (op->op_num == INTEQ)
333                         return val1 == val2;
334                 if (op->op_num == INTNE)
335                         return val1 != val2;
336                 if (op->op_num == INTGE)
337                         return val1 >= val2;
338                 if (op->op_num == INTGT)
339                         return val1 >  val2;
340                 if (op->op_num == INTLE)
341                         return val1 <= val2;
342                 if (op->op_num == INTLT)
343                         return val1 <  val2;
344         }
345         if (__is_str_op(op->op_num)) {
346                 val1 = strcmp(opnd1, opnd2);
347                 if (op->op_num == STREQ)
348                         return val1 == 0;
349                 if (op->op_num == STRNE)
350                         return val1 != 0;
351                 if (op->op_num == STRLT)
352                         return val1 < 0;
353                 if (op->op_num == STRGT)
354                         return val1 > 0;
355         }
356         /* We are sure that these three are by now the only binops we didn't check
357          * yet, so we do not check if the class is correct:
358          */
359 /*      if (__is_file_op(op->op_num)) */
360         {
361                 struct stat b1, b2;
362
363                 if (!(!stat(opnd1, &b1) && !stat(opnd2, &b2)))
364                         return 0; /* false, since stat failed */
365                 if (op->op_num == FILNT)
366                         return b1.st_mtime > b2.st_mtime;
367                 if (op->op_num == FILOT)
368                         return b1.st_mtime < b2.st_mtime;
369                 if (op->op_num == FILEQ)
370                         return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
371         }
372         return 1; /* NOTREACHED */
373 }
374
375 static int filstat(char *nm, enum token mode)
376 {
377         struct stat s;
378         int i;
379
380         if (mode == FILSYM) {
381 #ifdef S_IFLNK
382                 if (lstat(nm, &s) == 0) {
383                         i = S_IFLNK;
384                         goto filetype;
385                 }
386 #endif
387                 return 0;
388         }
389
390         if (stat(nm, &s) != 0)
391                 return 0;
392         if (mode == FILEXIST)
393                 return 1;
394         else if (__is_file_access(mode)) {
395                 if (mode == FILRD)
396                         i = R_OK;
397                 if (mode == FILWR)
398                         i = W_OK;
399                 if (mode == FILEX)
400                         i = X_OK;
401                 return test_eaccess(nm, i) == 0;
402         }
403         else if (__is_file_type(mode)) {
404                 if (mode == FILREG)
405                         i = S_IFREG;
406                 if (mode == FILDIR)
407                         i = S_IFDIR;
408                 if (mode == FILCDEV)
409                         i = S_IFCHR;
410                 if (mode == FILBDEV)
411                         i = S_IFBLK;
412                 if (mode == FILFIFO) {
413 #ifdef S_IFIFO
414                         i = S_IFIFO;
415 #else
416                         return 0;
417 #endif
418                 }
419                 if (mode == FILSOCK) {
420 #ifdef S_IFSOCK
421                         i = S_IFSOCK;
422 #else
423                         return 0;
424 #endif
425                 }
426 filetype:
427                 return ((s.st_mode & S_IFMT) == i);
428         }
429         else if (__is_file_bit(mode)) {
430                 if (mode == FILSUID)
431                         i = S_ISUID;
432                 if (mode == FILSGID)
433                         i = S_ISGID;
434                 if (mode == FILSTCK)
435                         i = S_ISVTX;
436                 return ((s.st_mode & i) != 0);
437         }
438         else if (mode == FILGZ)
439                 return s.st_size > 0L;
440         else if (mode == FILUID)
441                 return s.st_uid == geteuid();
442         else if (mode == FILGID)
443                 return s.st_gid == getegid();
444         else
445                 return 1; /* NOTREACHED */
446
447 }
448
449 static enum token t_lex(char *s)
450 {
451         struct t_op const *op = ops;
452
453         if (s == 0) {
454                 t_wp_op = (struct t_op *) 0;
455                 return EOI;
456         }
457         while (op->op_text) {
458                 if (strcmp(s, op->op_text) == 0) {
459                         t_wp_op = op;
460                         return op->op_num;
461                 }
462                 op++;
463         }
464         t_wp_op = (struct t_op *) 0;
465         return OPERAND;
466 }
467
468 /* atoi with error detection */
469 //XXX: FIXME: duplicate of existing libbb function?
470 static arith_t getn(const char *s)
471 {
472         char *p;
473 #ifdef CONFIG_FEATURE_TEST_64
474         long long r;
475 #else
476         long r;
477 #endif
478
479         errno = 0;
480 #ifdef CONFIG_FEATURE_TEST_64
481         r = strtoll(s, &p, 10);
482 #else
483         r = strtol(s, &p, 10);
484 #endif
485
486         if (errno != 0)
487                 syntax(s, "out of range");
488
489         if (*(skip_whitespace(p)))
490                 syntax(s, "bad number");
491
492         return r;
493 }
494
495 /* UNUSED
496 static int newerf(const char *f1, const char *f2)
497 {
498         struct stat b1, b2;
499
500         return (stat(f1, &b1) == 0 &&
501                         stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime);
502 }
503
504 static int olderf(const char *f1, const char *f2)
505 {
506         struct stat b1, b2;
507
508         return (stat(f1, &b1) == 0 &&
509                         stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime);
510 }
511
512 static int equalf(const char *f1, const char *f2)
513 {
514         struct stat b1, b2;
515
516         return (stat(f1, &b1) == 0 &&
517                         stat(f2, &b2) == 0 &&
518                         b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
519 }
520 */
521
522 /* Do the same thing access(2) does, but use the effective uid and gid,
523    and don't make the mistake of telling root that any file is
524    executable. */
525 static int test_eaccess(char *path, int mode)
526 {
527         struct stat st;
528         unsigned int euid = geteuid();
529
530         if (stat(path, &st) < 0)
531                 return -1;
532
533         if (euid == 0) {
534                 /* Root can read or write any file. */
535                 if (mode != X_OK)
536                         return 0;
537
538                 /* Root can execute any file that has any one of the execute
539                    bits set. */
540                 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
541                         return 0;
542         }
543
544         if (st.st_uid == euid)  /* owner */
545                 mode <<= 6;
546         else if (is_a_group_member(st.st_gid))
547                 mode <<= 3;
548
549         if (st.st_mode & mode)
550                 return 0;
551
552         return -1;
553 }
554
555 static void initialize_group_array(void)
556 {
557         ngroups = getgroups(0, NULL);
558         if (ngroups > 0) {
559                 group_array = xmalloc(ngroups * sizeof(gid_t));
560                 getgroups(ngroups, group_array);
561         }
562 }
563
564 /* Return non-zero if GID is one that we have in our groups list. */
565 //XXX: FIXME: duplicate of existing libbb function?
566 // see toplevel TODO file:
567 // possible code duplication ingroup() and is_a_group_member()
568 static int is_a_group_member(gid_t gid)
569 {
570         int i;
571
572         /* Short-circuit if possible, maybe saving a call to getgroups(). */
573         if (gid == getgid() || gid == getegid())
574                 return 1;
575
576         if (ngroups == 0)
577                 initialize_group_array();
578
579         /* Search through the list looking for GID. */
580         for (i = 0; i < ngroups; i++)
581                 if (gid == group_array[i])
582                         return 1;
583
584         return 0;
585 }
586
587
588 /* applet entry point */
589
590 int test_main(int argc, char **argv);
591 int test_main(int argc, char **argv)
592 {
593         return bb_test(argc, argv);
594 }
595