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