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