blob: 0ed777194463130fb9e8cc92ec40513aaa2ef661 [file] [log] [blame]
Erik Andersen13456d12000-03-16 08:09:57 +00001/* vi: set sw=4 ts=4: */
2/*
Erik Andersen5e1189e2000-04-15 16:34:54 +00003 * test implementation for busybox
Erik Andersen13456d12000-03-16 08:09:57 +00004 *
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
60enum 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
103enum token_types {
104 UNOP,
105 BINOP,
106 BUNOP,
107 BBINOP,
108 PAREN
109};
110
111struct 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
157char **t_wp;
158struct t_op const *t_wp_op;
159static gid_t *group_array = NULL;
160static int ngroups;
161
162static enum token t_lex();
163static int oexpr();
164static int aexpr();
165static int nexpr();
166static int binop();
167static int primary();
168static int filstat();
169static int getn();
170static int newerf();
171static int olderf();
172static int equalf();
173static void syntax();
174static int test_eaccess();
175static int is_a_group_member();
176static void initialize_group_array();
177
178extern int
179test_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 }
Erik Andersen5e1189e2000-04-15 16:34:54 +0000188 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 }
Erik Andersen13456d12000-03-16 08:09:57 +0000194
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
235static void
236syntax(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
246static int
247oexpr(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
259static int
260aexpr(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
272static int
273nexpr(n)
274 enum token n; /* token */
275{
276 if (n == UNOT)
277 return !nexpr(t_lex(*++t_wp));
278 return primary(n);
279}
280
281static int
282primary(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
318static int
319binop()
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
363static int
364filstat(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
438filetype:
439 return ((s.st_mode & S_IFMT) == i);
440
441filebit:
442 return ((s.st_mode & i) != 0);
443}
444
445static enum token
446t_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 */
467static int
468getn(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
489static int
490newerf (f1, f2)
491char *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
500static int
501olderf (f1, f2)
502char *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
511static int
512equalf (f1, f2)
513char *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. */
526static int
527test_eaccess (path, mode)
528char *path;
529int 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
559static void
560initialize_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. */
570static int
571is_a_group_member (gid)
572gid_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}