blob: bf1622cde717b3b1ae98523be983892ccf83cd65 [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>
Erik Andersen7ab9c7e2000-05-12 19:41:47 +000042#define BB_DECLARE_EXTERN
43#define bb_need_help
44#include "messages.c"
Erik Andersen13456d12000-03-16 08:09:57 +000045
46/* test(1) accepts the following grammar:
47 oexpr ::= aexpr | aexpr "-o" oexpr ;
48 aexpr ::= nexpr | nexpr "-a" aexpr ;
49 nexpr ::= primary | "!" primary
50 primary ::= unary-operator operand
51 | operand binary-operator operand
52 | operand
53 | "(" oexpr ")"
54 ;
55 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
56 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
57
58 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
59 "-nt"|"-ot"|"-ef";
60 operand ::= <any legal UNIX file name>
61*/
62
63enum token {
64 EOI,
65 FILRD,
66 FILWR,
67 FILEX,
68 FILEXIST,
69 FILREG,
70 FILDIR,
71 FILCDEV,
72 FILBDEV,
73 FILFIFO,
74 FILSOCK,
75 FILSYM,
76 FILGZ,
77 FILTT,
78 FILSUID,
79 FILSGID,
80 FILSTCK,
81 FILNT,
82 FILOT,
83 FILEQ,
84 FILUID,
85 FILGID,
86 STREZ,
87 STRNZ,
88 STREQ,
89 STRNE,
90 STRLT,
91 STRGT,
92 INTEQ,
93 INTNE,
94 INTGE,
95 INTGT,
96 INTLE,
97 INTLT,
98 UNOT,
99 BAND,
100 BOR,
101 LPAREN,
102 RPAREN,
103 OPERAND
104};
105
106enum token_types {
107 UNOP,
108 BINOP,
109 BUNOP,
110 BBINOP,
111 PAREN
112};
113
114struct t_op {
115 const char *op_text;
116 short op_num, op_type;
117} const ops [] = {
118 {"-r", FILRD, UNOP},
119 {"-w", FILWR, UNOP},
120 {"-x", FILEX, UNOP},
121 {"-e", FILEXIST,UNOP},
122 {"-f", FILREG, UNOP},
123 {"-d", FILDIR, UNOP},
124 {"-c", FILCDEV,UNOP},
125 {"-b", FILBDEV,UNOP},
126 {"-p", FILFIFO,UNOP},
127 {"-u", FILSUID,UNOP},
128 {"-g", FILSGID,UNOP},
129 {"-k", FILSTCK,UNOP},
130 {"-s", FILGZ, UNOP},
131 {"-t", FILTT, UNOP},
132 {"-z", STREZ, UNOP},
133 {"-n", STRNZ, UNOP},
134 {"-h", FILSYM, UNOP}, /* for backwards compat */
135 {"-O", FILUID, UNOP},
136 {"-G", FILGID, UNOP},
137 {"-L", FILSYM, UNOP},
138 {"-S", FILSOCK,UNOP},
139 {"=", STREQ, BINOP},
140 {"!=", STRNE, BINOP},
141 {"<", STRLT, BINOP},
142 {">", STRGT, BINOP},
143 {"-eq", INTEQ, BINOP},
144 {"-ne", INTNE, BINOP},
145 {"-ge", INTGE, BINOP},
146 {"-gt", INTGT, BINOP},
147 {"-le", INTLE, BINOP},
148 {"-lt", INTLT, BINOP},
149 {"-nt", FILNT, BINOP},
150 {"-ot", FILOT, BINOP},
151 {"-ef", FILEQ, BINOP},
152 {"!", UNOT, BUNOP},
153 {"-a", BAND, BBINOP},
154 {"-o", BOR, BBINOP},
155 {"(", LPAREN, PAREN},
156 {")", RPAREN, PAREN},
157 {0, 0, 0}
158};
159
160char **t_wp;
161struct t_op const *t_wp_op;
162static gid_t *group_array = NULL;
163static int ngroups;
164
165static enum token t_lex();
166static int oexpr();
167static int aexpr();
168static int nexpr();
169static int binop();
170static int primary();
171static int filstat();
172static int getn();
173static int newerf();
174static int olderf();
175static int equalf();
176static void syntax();
177static int test_eaccess();
178static int is_a_group_member();
179static void initialize_group_array();
180
Matt Kraai3bd8bd82000-07-14 23:28:47 +0000181const char test_usage[] =
182 "test EXPRESSION\n"
183 "or [ EXPRESSION ]\n"
184#ifndef BB_FEATURE_TRIVIAL_HELP
185 "\nChecks file types and compares values returning an exit\n"
186 "code determined by the value of EXPRESSION.\n"
187#endif
188 ;
189
Erik Andersen13456d12000-03-16 08:09:57 +0000190extern int
191test_main(int argc, char** argv)
192{
193 int res;
194
Matt Kraaie58771e2000-07-12 15:38:49 +0000195 if (strcmp(applet_name, "[") == 0) {
Erik Andersen13456d12000-03-16 08:09:57 +0000196 if (strcmp(argv[--argc], "]"))
Eric Andersen86ab8a32000-06-02 03:21:42 +0000197 fatalError("missing ]\n");
Erik Andersen13456d12000-03-16 08:09:57 +0000198 argv[argc] = NULL;
199 }
Matt Kraai3bd8bd82000-07-14 23:28:47 +0000200 if (strcmp(argv[1], dash_dash_help) == 0)
201 usage(test_usage);
Erik Andersen13456d12000-03-16 08:09:57 +0000202
203 /* Implement special cases from POSIX.2, section 4.62.4 */
204 switch (argc) {
205 case 1:
206 exit( 1);
207 case 2:
208 exit (*argv[1] == '\0');
209 case 3:
210 if (argv[1][0] == '!' && argv[1][1] == '\0') {
211 exit (!(*argv[2] == '\0'));
212 }
213 break;
214 case 4:
215 if (argv[1][0] != '!' || argv[1][1] != '\0') {
216 if (t_lex(argv[2]),
217 t_wp_op && t_wp_op->op_type == BINOP) {
218 t_wp = &argv[1];
219 exit (binop() == 0);
220 }
221 }
222 break;
223 case 5:
224 if (argv[1][0] == '!' && argv[1][1] == '\0') {
225 if (t_lex(argv[3]),
226 t_wp_op && t_wp_op->op_type == BINOP) {
227 t_wp = &argv[2];
228 exit (!(binop() == 0));
229 }
230 }
231 break;
232 }
233
234 t_wp = &argv[1];
235 res = !oexpr(t_lex(*t_wp));
236
237 if (*t_wp != NULL && *++t_wp != NULL)
238 syntax(*t_wp, "unknown operand");
239
Eric Andersenb6106152000-06-19 17:25:40 +0000240 return( res);
Erik Andersen13456d12000-03-16 08:09:57 +0000241}
242
243static void
244syntax(op, msg)
245 char *op;
246 char *msg;
247{
248 if (op && *op)
Eric Andersen86ab8a32000-06-02 03:21:42 +0000249 fatalError("%s: %s\n", op, msg);
Erik Andersen13456d12000-03-16 08:09:57 +0000250 else
Eric Andersen86ab8a32000-06-02 03:21:42 +0000251 fatalError("%s\n", msg);
Erik Andersen13456d12000-03-16 08:09:57 +0000252}
253
254static int
255oexpr(n)
256 enum token n;
257{
258 int res;
259
260 res = aexpr(n);
261 if (t_lex(*++t_wp) == BOR)
262 return oexpr(t_lex(*++t_wp)) || res;
263 t_wp--;
264 return res;
265}
266
267static int
268aexpr(n)
269 enum token n;
270{
271 int res;
272
273 res = nexpr(n);
274 if (t_lex(*++t_wp) == BAND)
275 return aexpr(t_lex(*++t_wp)) && res;
276 t_wp--;
277 return res;
278}
279
280static int
281nexpr(n)
282 enum token n; /* token */
283{
284 if (n == UNOT)
285 return !nexpr(t_lex(*++t_wp));
286 return primary(n);
287}
288
289static int
290primary(n)
291 enum token n;
292{
293 int res;
294
295 if (n == EOI)
296 syntax(NULL, "argument expected");
297 if (n == LPAREN) {
298 res = oexpr(t_lex(*++t_wp));
299 if (t_lex(*++t_wp) != RPAREN)
300 syntax(NULL, "closing paren expected");
301 return res;
302 }
303 if (t_wp_op && t_wp_op->op_type == UNOP) {
304 /* unary expression */
305 if (*++t_wp == NULL)
306 syntax(t_wp_op->op_text, "argument expected");
307 switch (n) {
308 case STREZ:
309 return strlen(*t_wp) == 0;
310 case STRNZ:
311 return strlen(*t_wp) != 0;
312 case FILTT:
313 return isatty(getn(*t_wp));
314 default:
315 return filstat(*t_wp, n);
316 }
317 }
318
319 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
320 return binop();
321 }
322
323 return strlen(*t_wp) > 0;
324}
325
326static int
327binop()
328{
329 const char *opnd1, *opnd2;
330 struct t_op const *op;
331
332 opnd1 = *t_wp;
333 (void) t_lex(*++t_wp);
334 op = t_wp_op;
335
336 if ((opnd2 = *++t_wp) == (char *)0)
337 syntax(op->op_text, "argument expected");
338
339 switch (op->op_num) {
340 case STREQ:
341 return strcmp(opnd1, opnd2) == 0;
342 case STRNE:
343 return strcmp(opnd1, opnd2) != 0;
344 case STRLT:
345 return strcmp(opnd1, opnd2) < 0;
346 case STRGT:
347 return strcmp(opnd1, opnd2) > 0;
348 case INTEQ:
349 return getn(opnd1) == getn(opnd2);
350 case INTNE:
351 return getn(opnd1) != getn(opnd2);
352 case INTGE:
353 return getn(opnd1) >= getn(opnd2);
354 case INTGT:
355 return getn(opnd1) > getn(opnd2);
356 case INTLE:
357 return getn(opnd1) <= getn(opnd2);
358 case INTLT:
359 return getn(opnd1) < getn(opnd2);
360 case FILNT:
361 return newerf (opnd1, opnd2);
362 case FILOT:
363 return olderf (opnd1, opnd2);
364 case FILEQ:
365 return equalf (opnd1, opnd2);
366 }
367 /* NOTREACHED */
368 return 1;
369}
370
371static int
372filstat(nm, mode)
373 char *nm;
374 enum token mode;
375{
376 struct stat s;
Eric Andersenfad04fd2000-07-14 06:49:52 +0000377 unsigned int i;
Erik Andersen13456d12000-03-16 08:09:57 +0000378
379 if (mode == FILSYM) {
380#ifdef S_IFLNK
381 if (lstat(nm, &s) == 0) {
382 i = S_IFLNK;
383 goto filetype;
384 }
385#endif
386 return 0;
387 }
388
389 if (stat(nm, &s) != 0)
390 return 0;
391
392 switch (mode) {
393 case FILRD:
394 return test_eaccess(nm, R_OK) == 0;
395 case FILWR:
396 return test_eaccess(nm, W_OK) == 0;
397 case FILEX:
398 return test_eaccess(nm, X_OK) == 0;
399 case FILEXIST:
400 return 1;
401 case FILREG:
402 i = S_IFREG;
403 goto filetype;
404 case FILDIR:
405 i = S_IFDIR;
406 goto filetype;
407 case FILCDEV:
408 i = S_IFCHR;
409 goto filetype;
410 case FILBDEV:
411 i = S_IFBLK;
412 goto filetype;
413 case FILFIFO:
414#ifdef S_IFIFO
415 i = S_IFIFO;
416 goto filetype;
417#else
418 return 0;
419#endif
420 case FILSOCK:
421#ifdef S_IFSOCK
422 i = S_IFSOCK;
423 goto filetype;
424#else
425 return 0;
426#endif
427 case FILSUID:
428 i = S_ISUID;
429 goto filebit;
430 case FILSGID:
431 i = S_ISGID;
432 goto filebit;
433 case FILSTCK:
434 i = S_ISVTX;
435 goto filebit;
436 case FILGZ:
437 return s.st_size > 0L;
438 case FILUID:
439 return s.st_uid == geteuid();
440 case FILGID:
441 return s.st_gid == getegid();
442 default:
443 return 1;
444 }
445
446filetype:
447 return ((s.st_mode & S_IFMT) == i);
448
449filebit:
450 return ((s.st_mode & i) != 0);
451}
452
453static enum token
454t_lex(s)
455 char *s;
456{
457 struct t_op const *op = ops;
458
459 if (s == 0) {
460 t_wp_op = (struct t_op *)0;
461 return EOI;
462 }
463 while (op->op_text) {
464 if (strcmp(s, op->op_text) == 0) {
465 t_wp_op = op;
466 return op->op_num;
467 }
468 op++;
469 }
470 t_wp_op = (struct t_op *)0;
471 return OPERAND;
472}
473
474/* atoi with error detection */
475static int
476getn(s)
477 char *s;
478{
479 char *p;
480 long r;
481
482 errno = 0;
483 r = strtol(s, &p, 10);
484
485 if (errno != 0)
Eric Andersen86ab8a32000-06-02 03:21:42 +0000486 fatalError("%s: out of range\n", s);
Erik Andersen13456d12000-03-16 08:09:57 +0000487
488 while (isspace(*p))
489 p++;
490
491 if (*p)
Eric Andersen86ab8a32000-06-02 03:21:42 +0000492 fatalError("%s: bad number\n", s);
Erik Andersen13456d12000-03-16 08:09:57 +0000493
494 return (int) r;
495}
496
497static int
498newerf (f1, f2)
499char *f1, *f2;
500{
501 struct stat b1, b2;
502
503 return (stat (f1, &b1) == 0 &&
504 stat (f2, &b2) == 0 &&
505 b1.st_mtime > b2.st_mtime);
506}
507
508static int
509olderf (f1, f2)
510char *f1, *f2;
511{
512 struct stat b1, b2;
513
514 return (stat (f1, &b1) == 0 &&
515 stat (f2, &b2) == 0 &&
516 b1.st_mtime < b2.st_mtime);
517}
518
519static int
520equalf (f1, f2)
521char *f1, *f2;
522{
523 struct stat b1, b2;
524
525 return (stat (f1, &b1) == 0 &&
526 stat (f2, &b2) == 0 &&
527 b1.st_dev == b2.st_dev &&
528 b1.st_ino == b2.st_ino);
529}
530
531/* Do the same thing access(2) does, but use the effective uid and gid,
532 and don't make the mistake of telling root that any file is
533 executable. */
534static int
535test_eaccess (path, mode)
536char *path;
537int mode;
538{
539 struct stat st;
Eric Andersenfad04fd2000-07-14 06:49:52 +0000540 unsigned int euid = geteuid();
Erik Andersen13456d12000-03-16 08:09:57 +0000541
542 if (stat (path, &st) < 0)
543 return (-1);
544
545 if (euid == 0) {
546 /* Root can read or write any file. */
547 if (mode != X_OK)
548 return (0);
549
550 /* Root can execute any file that has any one of the execute
551 bits set. */
552 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
553 return (0);
554 }
555
556 if (st.st_uid == euid) /* owner */
557 mode <<= 6;
558 else if (is_a_group_member (st.st_gid))
559 mode <<= 3;
560
561 if (st.st_mode & mode)
562 return (0);
563
564 return (-1);
565}
566
567static void
568initialize_group_array ()
569{
570 ngroups = getgroups(0, NULL);
571 if ((group_array = realloc(group_array, ngroups * sizeof(gid_t))) == NULL)
Eric Andersen86ab8a32000-06-02 03:21:42 +0000572 fatalError("Out of space\n");
Erik Andersen13456d12000-03-16 08:09:57 +0000573
574 getgroups(ngroups, group_array);
575}
576
577/* Return non-zero if GID is one that we have in our groups list. */
578static int
579is_a_group_member (gid)
580gid_t gid;
581{
582 register int i;
583
584 /* Short-circuit if possible, maybe saving a call to getgroups(). */
585 if (gid == getgid() || gid == getegid())
586 return (1);
587
588 if (ngroups == 0)
589 initialize_group_array ();
590
591 /* Search through the list looking for GID. */
592 for (i = 0; i < ngroups; i++)
593 if (gid == group_array[i])
594 return (1);
595
596 return (0);
597}