Add suffix stripping support to basename
[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 <andersee@debian.org> to be used 
14  *      in busybox.
15  *
16  * This program is free software; you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 2 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24  * General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, write to the Free Software
28  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29  *
30  * Original copyright notice states:
31  *      "This program is in the Public Domain."
32  */
33
34 #include "internal.h"
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38 #include <ctype.h>
39 #include <errno.h>
40 #include <stdlib.h>
41 #include <string.h>
42
43 /* test(1) accepts the following grammar:
44         oexpr   ::= aexpr | aexpr "-o" oexpr ;
45         aexpr   ::= nexpr | nexpr "-a" aexpr ;
46         nexpr   ::= primary | "!" primary
47         primary ::= unary-operator operand
48                 | operand binary-operator operand
49                 | operand
50                 | "(" oexpr ")"
51                 ;
52         unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
53                 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
54
55         binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
56                         "-nt"|"-ot"|"-ef";
57         operand ::= <any legal UNIX file name>
58 */
59
60 enum token {
61         EOI,
62         FILRD,
63         FILWR,
64         FILEX,
65         FILEXIST,
66         FILREG,
67         FILDIR,
68         FILCDEV,
69         FILBDEV,
70         FILFIFO,
71         FILSOCK,
72         FILSYM,
73         FILGZ,
74         FILTT,
75         FILSUID,
76         FILSGID,
77         FILSTCK,
78         FILNT,
79         FILOT,
80         FILEQ,
81         FILUID,
82         FILGID,
83         STREZ,
84         STRNZ,
85         STREQ,
86         STRNE,
87         STRLT,
88         STRGT,
89         INTEQ,
90         INTNE,
91         INTGE,
92         INTGT,
93         INTLE,
94         INTLT,
95         UNOT,
96         BAND,
97         BOR,
98         LPAREN,
99         RPAREN,
100         OPERAND
101 };
102
103 enum token_types {
104         UNOP,
105         BINOP,
106         BUNOP,
107         BBINOP,
108         PAREN
109 };
110
111 struct t_op {
112         const char *op_text;
113         short op_num, op_type;
114 } const ops [] = {
115         {"-r",  FILRD,  UNOP},
116         {"-w",  FILWR,  UNOP},
117         {"-x",  FILEX,  UNOP},
118         {"-e",  FILEXIST,UNOP},
119         {"-f",  FILREG, UNOP},
120         {"-d",  FILDIR, UNOP},
121         {"-c",  FILCDEV,UNOP},
122         {"-b",  FILBDEV,UNOP},
123         {"-p",  FILFIFO,UNOP},
124         {"-u",  FILSUID,UNOP},
125         {"-g",  FILSGID,UNOP},
126         {"-k",  FILSTCK,UNOP},
127         {"-s",  FILGZ,  UNOP},
128         {"-t",  FILTT,  UNOP},
129         {"-z",  STREZ,  UNOP},
130         {"-n",  STRNZ,  UNOP},
131         {"-h",  FILSYM, UNOP},          /* for backwards compat */
132         {"-O",  FILUID, UNOP},
133         {"-G",  FILGID, UNOP},
134         {"-L",  FILSYM, UNOP},
135         {"-S",  FILSOCK,UNOP},
136         {"=",   STREQ,  BINOP},
137         {"!=",  STRNE,  BINOP},
138         {"<",   STRLT,  BINOP},
139         {">",   STRGT,  BINOP},
140         {"-eq", INTEQ,  BINOP},
141         {"-ne", INTNE,  BINOP},
142         {"-ge", INTGE,  BINOP},
143         {"-gt", INTGT,  BINOP},
144         {"-le", INTLE,  BINOP},
145         {"-lt", INTLT,  BINOP},
146         {"-nt", FILNT,  BINOP},
147         {"-ot", FILOT,  BINOP},
148         {"-ef", FILEQ,  BINOP},
149         {"!",   UNOT,   BUNOP},
150         {"-a",  BAND,   BBINOP},
151         {"-o",  BOR,    BBINOP},
152         {"(",   LPAREN, PAREN},
153         {")",   RPAREN, PAREN},
154         {0,     0,      0}
155 };
156
157 char **t_wp;
158 struct t_op const *t_wp_op;
159 static gid_t *group_array = NULL;
160 static int ngroups;
161
162 static enum token t_lex();
163 static int oexpr();
164 static int aexpr();
165 static int nexpr();
166 static int binop();
167 static int primary();
168 static int filstat();
169 static int getn();
170 static int newerf();
171 static int olderf();
172 static int equalf();
173 static void syntax();
174 static int test_eaccess();
175 static int is_a_group_member();
176 static void initialize_group_array();
177
178 extern int
179 test_main(int argc, char** argv)
180 {
181         int     res;
182
183         if (strcmp(argv[0], "[") == 0) {
184                 if (strcmp(argv[--argc], "]"))
185                         fatalError("missing ]");
186                 argv[argc] = NULL;
187         }
188         if (strcmp(argv[1], "--help") == 0) {
189                 usage("test EXPRESSION\n"
190                           "or   [ EXPRESSION ]\n\n"
191                                 "Checks file types and compares values returning an exit\n"
192                                 "code determined by the value of EXPRESSION.\n");
193         }
194
195         /* Implement special cases from POSIX.2, section 4.62.4 */
196         switch (argc) {
197         case 1:
198                 exit( 1);
199         case 2:
200                 exit (*argv[1] == '\0');
201         case 3:
202                 if (argv[1][0] == '!' && argv[1][1] == '\0') {
203                         exit (!(*argv[2] == '\0'));
204                 }
205                 break;
206         case 4:
207                 if (argv[1][0] != '!' || argv[1][1] != '\0') {
208                         if (t_lex(argv[2]), 
209                             t_wp_op && t_wp_op->op_type == BINOP) {
210                                 t_wp = &argv[1];
211                                 exit (binop() == 0);
212                         }
213                 }
214                 break;
215         case 5:
216                 if (argv[1][0] == '!' && argv[1][1] == '\0') {
217                         if (t_lex(argv[3]), 
218                             t_wp_op && t_wp_op->op_type == BINOP) {
219                                 t_wp = &argv[2];
220                                 exit (!(binop() == 0));
221                         }
222                 }
223                 break;
224         }
225
226         t_wp = &argv[1];
227         res = !oexpr(t_lex(*t_wp));
228
229         if (*t_wp != NULL && *++t_wp != NULL)
230                 syntax(*t_wp, "unknown operand");
231
232         exit( res);
233 }
234
235 static void
236 syntax(op, msg)
237         char    *op;
238         char    *msg;
239 {
240         if (op && *op)
241                 fatalError("%s: %s", op, msg);
242         else
243                 fatalError("%s", msg);
244 }
245
246 static int
247 oexpr(n)
248         enum token n;
249 {
250         int res;
251
252         res = aexpr(n);
253         if (t_lex(*++t_wp) == BOR)
254                 return oexpr(t_lex(*++t_wp)) || res;
255         t_wp--;
256         return res;
257 }
258
259 static int
260 aexpr(n)
261         enum token n;
262 {
263         int res;
264
265         res = nexpr(n);
266         if (t_lex(*++t_wp) == BAND)
267                 return aexpr(t_lex(*++t_wp)) && res;
268         t_wp--;
269         return res;
270 }
271
272 static int
273 nexpr(n)
274         enum token n;                   /* token */
275 {
276         if (n == UNOT)
277                 return !nexpr(t_lex(*++t_wp));
278         return primary(n);
279 }
280
281 static int
282 primary(n)
283         enum token n;
284 {
285         int res;
286
287         if (n == EOI)
288                 syntax(NULL, "argument expected");
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                 switch (n) {
300                 case STREZ:
301                         return strlen(*t_wp) == 0;
302                 case STRNZ:
303                         return strlen(*t_wp) != 0;
304                 case FILTT:
305                         return isatty(getn(*t_wp));
306                 default:
307                         return filstat(*t_wp, n);
308                 }
309         }
310
311         if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
312                 return binop();
313         }         
314
315         return strlen(*t_wp) > 0;
316 }
317
318 static int
319 binop()
320 {
321         const char *opnd1, *opnd2;
322         struct t_op const *op;
323
324         opnd1 = *t_wp;
325         (void) t_lex(*++t_wp);
326         op = t_wp_op;
327
328         if ((opnd2 = *++t_wp) == (char *)0)
329                 syntax(op->op_text, "argument expected");
330                 
331         switch (op->op_num) {
332         case STREQ:
333                 return strcmp(opnd1, opnd2) == 0;
334         case STRNE:
335                 return strcmp(opnd1, opnd2) != 0;
336         case STRLT:
337                 return strcmp(opnd1, opnd2) < 0;
338         case STRGT:
339                 return strcmp(opnd1, opnd2) > 0;
340         case INTEQ:
341                 return getn(opnd1) == getn(opnd2);
342         case INTNE:
343                 return getn(opnd1) != getn(opnd2);
344         case INTGE:
345                 return getn(opnd1) >= getn(opnd2);
346         case INTGT:
347                 return getn(opnd1) > getn(opnd2);
348         case INTLE:
349                 return getn(opnd1) <= getn(opnd2);
350         case INTLT:
351                 return getn(opnd1) < getn(opnd2);
352         case FILNT:
353                 return newerf (opnd1, opnd2);
354         case FILOT:
355                 return olderf (opnd1, opnd2);
356         case FILEQ:
357                 return equalf (opnd1, opnd2);
358         }
359         /* NOTREACHED */
360         return 1;
361 }
362
363 static int
364 filstat(nm, mode)
365         char *nm;
366         enum token mode;
367 {
368         struct stat s;
369         int i;
370
371         if (mode == FILSYM) {
372 #ifdef S_IFLNK
373                 if (lstat(nm, &s) == 0) {
374                         i = S_IFLNK;
375                         goto filetype;
376                 }
377 #endif
378                 return 0;
379         }
380
381         if (stat(nm, &s) != 0) 
382                 return 0;
383
384         switch (mode) {
385         case FILRD:
386                 return test_eaccess(nm, R_OK) == 0;
387         case FILWR:
388                 return test_eaccess(nm, W_OK) == 0;
389         case FILEX:
390                 return test_eaccess(nm, X_OK) == 0;
391         case FILEXIST:
392                 return 1;
393         case FILREG:
394                 i = S_IFREG;
395                 goto filetype;
396         case FILDIR:
397                 i = S_IFDIR;
398                 goto filetype;
399         case FILCDEV:
400                 i = S_IFCHR;
401                 goto filetype;
402         case FILBDEV:
403                 i = S_IFBLK;
404                 goto filetype;
405         case FILFIFO:
406 #ifdef S_IFIFO
407                 i = S_IFIFO;
408                 goto filetype;
409 #else
410                 return 0;
411 #endif
412         case FILSOCK:
413 #ifdef S_IFSOCK
414                 i = S_IFSOCK;
415                 goto filetype;
416 #else
417                 return 0;
418 #endif
419         case FILSUID:
420                 i = S_ISUID;
421                 goto filebit;
422         case FILSGID:
423                 i = S_ISGID;
424                 goto filebit;
425         case FILSTCK:
426                 i = S_ISVTX;
427                 goto filebit;
428         case FILGZ:
429                 return s.st_size > 0L;
430         case FILUID:
431                 return s.st_uid == geteuid();
432         case FILGID:
433                 return s.st_gid == getegid();
434         default:
435                 return 1;
436         }
437
438 filetype:
439         return ((s.st_mode & S_IFMT) == i);
440
441 filebit:
442         return ((s.st_mode & i) != 0);
443 }
444
445 static enum token
446 t_lex(s)
447         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 static int
468 getn(s)
469         char *s;
470 {
471         char *p;
472         long r;
473
474         errno = 0;
475         r = strtol(s, &p, 10);
476
477         if (errno != 0)
478           fatalError("%s: out of range", s);
479
480         while (isspace(*p))
481           p++;
482         
483         if (*p)
484           fatalError("%s: bad number", s);
485
486         return (int) r;
487 }
488
489 static int
490 newerf (f1, f2)
491 char *f1, *f2;
492 {
493         struct stat b1, b2;
494
495         return (stat (f1, &b1) == 0 &&
496                 stat (f2, &b2) == 0 &&
497                 b1.st_mtime > b2.st_mtime);
498 }
499
500 static int
501 olderf (f1, f2)
502 char *f1, *f2;
503 {
504         struct stat b1, b2;
505
506         return (stat (f1, &b1) == 0 &&
507                 stat (f2, &b2) == 0 &&
508                 b1.st_mtime < b2.st_mtime);
509 }
510
511 static int
512 equalf (f1, f2)
513 char *f1, *f2;
514 {
515         struct stat b1, b2;
516
517         return (stat (f1, &b1) == 0 &&
518                 stat (f2, &b2) == 0 &&
519                 b1.st_dev == b2.st_dev &&
520                 b1.st_ino == b2.st_ino);
521 }
522
523 /* Do the same thing access(2) does, but use the effective uid and gid,
524    and don't make the mistake of telling root that any file is
525    executable. */
526 static int
527 test_eaccess (path, mode)
528 char *path;
529 int mode;
530 {
531         struct stat st;
532         int euid = geteuid();
533
534         if (stat (path, &st) < 0)
535                 return (-1);
536
537         if (euid == 0) {
538                 /* Root can read or write any file. */
539                 if (mode != X_OK)
540                 return (0);
541
542                 /* Root can execute any file that has any one of the execute
543                    bits set. */
544                 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
545                         return (0);
546         }
547
548         if (st.st_uid == euid)          /* owner */
549                 mode <<= 6;
550         else if (is_a_group_member (st.st_gid))
551                 mode <<= 3;
552
553         if (st.st_mode & mode)
554                 return (0);
555
556         return (-1);
557 }
558
559 static void
560 initialize_group_array ()
561 {
562         ngroups = getgroups(0, NULL);
563         if ((group_array = realloc(group_array, ngroups * sizeof(gid_t))) == NULL)
564                 fatalError("Out of space");
565
566         getgroups(ngroups, group_array);
567 }
568
569 /* Return non-zero if GID is one that we have in our groups list. */
570 static int
571 is_a_group_member (gid)
572 gid_t gid;
573 {
574         register int i;
575
576         /* Short-circuit if possible, maybe saving a call to getgroups(). */
577         if (gid == getgid() || gid == getegid())
578                 return (1);
579
580         if (ngroups == 0)
581                 initialize_group_array ();
582
583         /* Search through the list looking for GID. */
584         for (i = 0; i < ngroups; i++)
585                 if (gid == group_array[i])
586                         return (1);
587
588         return (0);
589 }