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 <andersen@codepoet.org> to be used
14  *     in busybox.
15  *     modified by Bernhard Reutner-Fischer to be useable (i.e. a bit less bloaty).
16  *
17  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
18  *
19  * Original copyright notice states:
20  *     "This program is in the Public Domain."
21  */
22 //config:config TEST
23 //config:	bool "test"
24 //config:	default y
25 //config:	help
26 //config:	  test is used to check file types and compare values,
27 //config:	  returning an appropriate exit code. The bash shell
28 //config:	  has test built in, ash can build it in optionally.
29 //config:
30 //config:config TEST1
31 //config:	bool "test as ["
32 //config:	default y
33 //config:	help
34 //config:	  Provide test command in the "[ EXPR ]" form
35 //config:
36 //config:config TEST2
37 //config:	bool "test as [["
38 //config:	default y
39 //config:	help
40 //config:	  Provide test command in the "[[ EXPR ]]" form
41 //config:
42 //config:config FEATURE_TEST_64
43 //config:	bool "Extend test to 64 bit"
44 //config:	default y
45 //config:	depends on TEST || TEST1 || TEST2 || ASH_BUILTIN_TEST || HUSH
46 //config:	help
47 //config:	  Enable 64-bit support in test.
48 
49 //applet:IF_TEST(APPLET_NOFORK(test, test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
50 //applet:IF_TEST1(APPLET_NOFORK([,  test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
51 //applet:IF_TEST2(APPLET_NOFORK([[, test, BB_DIR_USR_BIN, BB_SUID_DROP, test))
52 
53 //kbuild:lib-$(CONFIG_TEST) += test.o test_ptr_hack.o
54 //kbuild:lib-$(CONFIG_TEST1) += test.o test_ptr_hack.o
55 //kbuild:lib-$(CONFIG_TEST2) += test.o test_ptr_hack.o
56 //kbuild:lib-$(CONFIG_ASH_BUILTIN_TEST) += test.o test_ptr_hack.o
57 //kbuild:lib-$(CONFIG_HUSH) += test.o test_ptr_hack.o
58 //kbuild:lib-$(CONFIG_SH_IS_HUSH) += test.o test_ptr_hack.o
59 //kbuild:lib-$(CONFIG_BASH_IS_HUSH) += test.o test_ptr_hack.o
60 
61 /* "test --help" is special-cased to ignore --help */
62 //usage:#define test_trivial_usage NOUSAGE_STR
63 //usage:#define test_full_usage ""
64 //usage:
65 //usage:#define test_example_usage
66 //usage:       "$ test 1 -eq 2\n"
67 //usage:       "$ echo $?\n"
68 //usage:       "1\n"
69 //usage:       "$ test 1 -eq 1\n"
70 //usage:       "$ echo $?\n"
71 //usage:       "0\n"
72 //usage:       "$ [ -d /etc ]\n"
73 //usage:       "$ echo $?\n"
74 //usage:       "0\n"
75 //usage:       "$ [ -d /junk ]\n"
76 //usage:       "$ echo $?\n"
77 //usage:       "1\n"
78 
79 #include "libbb.h"
80 #include <setjmp.h>
81 
82 /* This is a NOFORK applet. Be very careful! */
83 
84 /* test_main() is called from shells, and we need to be extra careful here.
85  * This is true regardless of PREFER_APPLETS and SH_STANDALONE
86  * state. */
87 
88 /* test(1) accepts the following grammar:
89 	oexpr   ::= aexpr | aexpr "-o" oexpr ;
90 	aexpr   ::= nexpr | nexpr "-a" aexpr ;
91 	nexpr   ::= primary | "!" primary
92 	primary ::= unary-operator operand
93 		| operand binary-operator operand
94 		| operand
95 		| "(" oexpr ")"
96 		;
97 	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
98 		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
99 
100 	binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
101 			"-nt"|"-ot"|"-ef";
102 	operand ::= <any legal UNIX file name>
103 */
104 
105 /* TODO: handle [[ expr ]] bashism bash-compatibly.
106  * [[ ]] is meant to be a "better [ ]", with less weird syntax
107  * and without the risk of variables and quoted strings misinterpreted
108  * as operators.
109  * This will require support from shells - we need to know quote status
110  * of each parameter (see below).
111  *
112  * Word splitting and pathname expansion should NOT be performed:
113  *      # a="a b"; [[ $a = "a b" ]] && echo YES
114  *      YES
115  *      # [[ /bin/m* ]] && echo YES
116  *      YES
117  *
118  * =~ should do regexp match
119  * = and == should do pattern match against right side:
120  *      # [[ *a* == bab ]] && echo YES
121  *      # [[ bab == *a* ]] && echo YES
122  *      YES
123  * != does the negated == (i.e., also with pattern matching).
124  * Pattern matching is quotation-sensitive:
125  *      # [[ bab == "b"a* ]] && echo YES
126  *      YES
127  *      # [[ bab == b"a*" ]] && echo YES
128  *
129  * Conditional operators such as -f must be unquoted literals to be recognized:
130  *      # [[ -e /bin ]] && echo YES
131  *      YES
132  *      # [[ '-e' /bin ]] && echo YES
133  *      bash: conditional binary operator expected...
134  *      # A='-e'; [[ $A /bin ]] && echo YES
135  *      bash: conditional binary operator expected...
136  *
137  * || and && should work as -o and -a work in [ ]
138  * -a and -o aren't recognized (&& and || are to be used instead)
139  * ( and ) do not need to be quoted unlike in [ ]:
140  *      # [[ ( abc ) && '' ]] && echo YES
141  *      # [[ ( abc ) || '' ]] && echo YES
142  *      YES
143  *      # [[ ( abc ) -o '' ]] && echo YES
144  *      bash: syntax error in conditional expression...
145  *
146  * Apart from the above, [[ expr ]] should work as [ expr ]
147  */
148 
149 #define TEST_DEBUG 0
150 
151 enum token {
152 	EOI,
153 
154 	FILRD, /* file access */
155 	FILWR,
156 	FILEX,
157 
158 	FILEXIST,
159 
160 	FILREG, /* file type */
161 	FILDIR,
162 	FILCDEV,
163 	FILBDEV,
164 	FILFIFO,
165 	FILSOCK,
166 
167 	FILSYM,
168 	FILGZ,
169 	FILTT,
170 
171 	FILSUID, /* file bit */
172 	FILSGID,
173 	FILSTCK,
174 
175 	FILNT, /* file ops */
176 	FILOT,
177 	FILEQ,
178 
179 	FILUID,
180 	FILGID,
181 
182 	STREZ, /* str ops */
183 	STRNZ,
184 	STREQ,
185 	STRNE,
186 	STRLT,
187 	STRGT,
188 
189 	INTEQ, /* int ops */
190 	INTNE,
191 	INTGE,
192 	INTGT,
193 	INTLE,
194 	INTLT,
195 
196 	UNOT,
197 	BAND,
198 	BOR,
199 	LPAREN,
200 	RPAREN,
201 	OPERAND
202 };
203 #define is_int_op(a)      (((unsigned char)((a) - INTEQ)) <= 5)
204 #define is_str_op(a)      (((unsigned char)((a) - STREZ)) <= 5)
205 #define is_file_op(a)     (((unsigned char)((a) - FILNT)) <= 2)
206 #define is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2)
207 #define is_file_type(a)   (((unsigned char)((a) - FILREG)) <= 5)
208 #define is_file_bit(a)    (((unsigned char)((a) - FILSUID)) <= 2)
209 
210 #if TEST_DEBUG
211 int depth;
212 #define nest_msg(...) do { \
213 	depth++; \
214 	fprintf(stderr, "%*s", depth*2, ""); \
215 	fprintf(stderr, __VA_ARGS__); \
216 } while (0)
217 #define unnest_msg(...) do { \
218 	fprintf(stderr, "%*s", depth*2, ""); \
219 	fprintf(stderr, __VA_ARGS__); \
220 	depth--; \
221 } while (0)
222 #define dbg_msg(...) do { \
223 	fprintf(stderr, "%*s", depth*2, ""); \
224 	fprintf(stderr, __VA_ARGS__); \
225 } while (0)
226 #define unnest_msg_and_return(expr, ...) do { \
227 	number_t __res = (expr); \
228 	fprintf(stderr, "%*s", depth*2, ""); \
229 	fprintf(stderr, __VA_ARGS__, res); \
230 	depth--; \
231 	return __res; \
232 } while (0)
233 static const char *const TOKSTR[] = {
234 	"EOI",
235 	"FILRD",
236 	"FILWR",
237 	"FILEX",
238 	"FILEXIST",
239 	"FILREG",
240 	"FILDIR",
241 	"FILCDEV",
242 	"FILBDEV",
243 	"FILFIFO",
244 	"FILSOCK",
245 	"FILSYM",
246 	"FILGZ",
247 	"FILTT",
248 	"FILSUID",
249 	"FILSGID",
250 	"FILSTCK",
251 	"FILNT",
252 	"FILOT",
253 	"FILEQ",
254 	"FILUID",
255 	"FILGID",
256 	"STREZ",
257 	"STRNZ",
258 	"STREQ",
259 	"STRNE",
260 	"STRLT",
261 	"STRGT",
262 	"INTEQ",
263 	"INTNE",
264 	"INTGE",
265 	"INTGT",
266 	"INTLE",
267 	"INTLT",
268 	"UNOT",
269 	"BAND",
270 	"BOR",
271 	"LPAREN",
272 	"RPAREN",
273 	"OPERAND"
274 };
275 #else
276 #define nest_msg(...)   ((void)0)
277 #define unnest_msg(...) ((void)0)
278 #define dbg_msg(...)    ((void)0)
279 #define unnest_msg_and_return(expr, ...) return expr
280 #endif
281 
282 enum {
283 	UNOP,
284 	BINOP,
285 	BUNOP,
286 	BBINOP,
287 	PAREN
288 };
289 
290 struct operator_t {
291 	unsigned char op_num, op_type;
292 };
293 
294 static const struct operator_t ops_table[] = {
295 	{ /* "-r" */ FILRD   , UNOP   },
296 	{ /* "-w" */ FILWR   , UNOP   },
297 	{ /* "-x" */ FILEX   , UNOP   },
298 	{ /* "-e" */ FILEXIST, UNOP   },
299 	{ /* "-f" */ FILREG  , UNOP   },
300 	{ /* "-d" */ FILDIR  , UNOP   },
301 	{ /* "-c" */ FILCDEV , UNOP   },
302 	{ /* "-b" */ FILBDEV , UNOP   },
303 	{ /* "-p" */ FILFIFO , UNOP   },
304 	{ /* "-u" */ FILSUID , UNOP   },
305 	{ /* "-g" */ FILSGID , UNOP   },
306 	{ /* "-k" */ FILSTCK , UNOP   },
307 	{ /* "-s" */ FILGZ   , UNOP   },
308 	{ /* "-t" */ FILTT   , UNOP   },
309 	{ /* "-z" */ STREZ   , UNOP   },
310 	{ /* "-n" */ STRNZ   , UNOP   },
311 	{ /* "-h" */ FILSYM  , UNOP   },    /* for backwards compat */
312 
313 	{ /* "-O" */ FILUID  , UNOP   },
314 	{ /* "-G" */ FILGID  , UNOP   },
315 	{ /* "-L" */ FILSYM  , UNOP   },
316 	{ /* "-S" */ FILSOCK , UNOP   },
317 	{ /* "="  */ STREQ   , BINOP  },
318 	{ /* "==" */ STREQ   , BINOP  },
319 	{ /* "!=" */ STRNE   , BINOP  },
320 	{ /* "<"  */ STRLT   , BINOP  },
321 	{ /* ">"  */ STRGT   , BINOP  },
322 	{ /* "-eq"*/ INTEQ   , BINOP  },
323 	{ /* "-ne"*/ INTNE   , BINOP  },
324 	{ /* "-ge"*/ INTGE   , BINOP  },
325 	{ /* "-gt"*/ INTGT   , BINOP  },
326 	{ /* "-le"*/ INTLE   , BINOP  },
327 	{ /* "-lt"*/ INTLT   , BINOP  },
328 	{ /* "-nt"*/ FILNT   , BINOP  },
329 	{ /* "-ot"*/ FILOT   , BINOP  },
330 	{ /* "-ef"*/ FILEQ   , BINOP  },
331 	{ /* "!"  */ UNOT    , BUNOP  },
332 	{ /* "-a" */ BAND    , BBINOP },
333 	{ /* "-o" */ BOR     , BBINOP },
334 	{ /* "("  */ LPAREN  , PAREN  },
335 	{ /* ")"  */ RPAREN  , PAREN  },
336 };
337 /* Please keep these two tables in sync */
338 static const char ops_texts[] ALIGN1 =
339 	"-r"  "\0"
340 	"-w"  "\0"
341 	"-x"  "\0"
342 	"-e"  "\0"
343 	"-f"  "\0"
344 	"-d"  "\0"
345 	"-c"  "\0"
346 	"-b"  "\0"
347 	"-p"  "\0"
348 	"-u"  "\0"
349 	"-g"  "\0"
350 	"-k"  "\0"
351 	"-s"  "\0"
352 	"-t"  "\0"
353 	"-z"  "\0"
354 	"-n"  "\0"
355 	"-h"  "\0"
356 
357 	"-O"  "\0"
358 	"-G"  "\0"
359 	"-L"  "\0"
360 	"-S"  "\0"
361 	"="   "\0"
362 	"=="  "\0"
363 	"!="  "\0"
364 	"<"   "\0"
365 	">"   "\0"
366 	"-eq" "\0"
367 	"-ne" "\0"
368 	"-ge" "\0"
369 	"-gt" "\0"
370 	"-le" "\0"
371 	"-lt" "\0"
372 	"-nt" "\0"
373 	"-ot" "\0"
374 	"-ef" "\0"
375 	"!"   "\0"
376 	"-a"  "\0"
377 	"-o"  "\0"
378 	"("   "\0"
379 	")"   "\0"
380 ;
381 
382 
383 #if ENABLE_FEATURE_TEST_64
384 typedef int64_t number_t;
385 #else
386 typedef int number_t;
387 #endif
388 
389 
390 /* We try to minimize both static and stack usage. */
391 struct test_statics {
392 	char **args;
393 	/* set only by check_operator(), either to bogus struct
394 	 * or points to matching operator_t struct. Never NULL. */
395 	const struct operator_t *last_operator;
396 	gid_t *group_array;
397 	int ngroups;
398 	jmp_buf leaving;
399 };
400 
401 /* See test_ptr_hack.c */
402 extern struct test_statics *const test_ptr_to_statics;
403 
404 #define S (*test_ptr_to_statics)
405 #define args            (S.args         )
406 #define last_operator   (S.last_operator)
407 #define group_array     (S.group_array  )
408 #define ngroups         (S.ngroups      )
409 #define leaving         (S.leaving      )
410 
411 #define INIT_S() do { \
412 	(*(struct test_statics**)&test_ptr_to_statics) = xzalloc(sizeof(S)); \
413 	barrier(); \
414 } while (0)
415 #define DEINIT_S() do { \
416 	free(group_array); \
417 	free(test_ptr_to_statics); \
418 } while (0)
419 
420 static number_t primary(enum token n);
421 
422 static void syntax(const char *op, const char *msg) NORETURN;
syntax(const char * op,const char * msg)423 static void syntax(const char *op, const char *msg)
424 {
425 	if (op && *op) {
426 		bb_error_msg("%s: %s", op, msg);
427 	} else {
428 		bb_error_msg("%s: %s"+4, msg);
429 	}
430 	longjmp(leaving, 2);
431 }
432 
433 /* atoi with error detection */
434 //XXX: FIXME: duplicate of existing libbb function?
getn(const char * s)435 static number_t getn(const char *s)
436 {
437 	char *p;
438 #if ENABLE_FEATURE_TEST_64
439 	long long r;
440 #else
441 	long r;
442 #endif
443 
444 	errno = 0;
445 #if ENABLE_FEATURE_TEST_64
446 	r = strtoll(s, &p, 10);
447 #else
448 	r = strtol(s, &p, 10);
449 #endif
450 
451 	if (errno != 0)
452 		syntax(s, "out of range");
453 
454 	if (p == s || *(skip_whitespace(p)) != '\0')
455 		syntax(s, "bad number");
456 
457 	return r;
458 }
459 
460 /* UNUSED
461 static int newerf(const char *f1, const char *f2)
462 {
463 	struct stat b1, b2;
464 
465 	return (stat(f1, &b1) == 0 &&
466 			stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime);
467 }
468 
469 static int olderf(const char *f1, const char *f2)
470 {
471 	struct stat b1, b2;
472 
473 	return (stat(f1, &b1) == 0 &&
474 			stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime);
475 }
476 
477 static int equalf(const char *f1, const char *f2)
478 {
479 	struct stat b1, b2;
480 
481 	return (stat(f1, &b1) == 0 &&
482 			stat(f2, &b2) == 0 &&
483 			b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
484 }
485 */
486 
487 
check_operator(const char * s)488 static enum token check_operator(const char *s)
489 {
490 	static const struct operator_t no_op = {
491 		.op_num = -1,
492 		.op_type = -1
493 	};
494 	int n;
495 
496 	last_operator = &no_op;
497 	if (s == NULL)
498 		return EOI;
499 	n = index_in_strings(ops_texts, s);
500 	if (n < 0)
501 		return OPERAND;
502 	last_operator = &ops_table[n];
503 	return ops_table[n].op_num;
504 }
505 
506 
binop(void)507 static int binop(void)
508 {
509 	const char *opnd1, *opnd2;
510 	const struct operator_t *op;
511 	number_t val1, val2;
512 
513 	opnd1 = *args;
514 	check_operator(*++args);
515 	op = last_operator;
516 
517 	opnd2 = *++args;
518 	if (opnd2 == NULL)
519 		syntax(args[-1], "argument expected");
520 
521 	if (is_int_op(op->op_num)) {
522 		val1 = getn(opnd1);
523 		val2 = getn(opnd2);
524 		if (op->op_num == INTEQ)
525 			return val1 == val2;
526 		if (op->op_num == INTNE)
527 			return val1 != val2;
528 		if (op->op_num == INTGE)
529 			return val1 >= val2;
530 		if (op->op_num == INTGT)
531 			return val1 >  val2;
532 		if (op->op_num == INTLE)
533 			return val1 <= val2;
534 		/*if (op->op_num == INTLT)*/
535 		return val1 <  val2;
536 	}
537 	if (is_str_op(op->op_num)) {
538 		val1 = strcmp(opnd1, opnd2);
539 		if (op->op_num == STREQ)
540 			return val1 == 0;
541 		if (op->op_num == STRNE)
542 			return val1 != 0;
543 		if (op->op_num == STRLT)
544 			return val1 < 0;
545 		/*if (op->op_num == STRGT)*/
546 		return val1 > 0;
547 	}
548 	/* We are sure that these three are by now the only binops we didn't check
549 	 * yet, so we do not check if the class is correct:
550 	 */
551 /*	if (is_file_op(op->op_num)) */
552 	{
553 		struct stat b1, b2;
554 
555 		if (stat(opnd1, &b1) || stat(opnd2, &b2))
556 			return 0; /* false, since at least one stat failed */
557 		if (op->op_num == FILNT)
558 			return b1.st_mtime > b2.st_mtime;
559 		if (op->op_num == FILOT)
560 			return b1.st_mtime < b2.st_mtime;
561 		/*if (op->op_num == FILEQ)*/
562 		return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
563 	}
564 	/*return 1; - NOTREACHED */
565 }
566 
567 
initialize_group_array(void)568 static void initialize_group_array(void)
569 {
570 	int n;
571 
572 	/* getgroups may be expensive, try to use it only once */
573 	ngroups = 32;
574 	do {
575 		/* FIXME: ash tries so hard to not die on OOM,
576 		 * and we spoil it with just one xrealloc here */
577 		/* We realloc, because test_main can be entered repeatedly by shell.
578 		 * Testcase (ash): 'while true; do test -x some_file; done'
579 		 * and watch top. (some_file must have owner != you) */
580 		n = ngroups;
581 		group_array = xrealloc(group_array, n * sizeof(gid_t));
582 		ngroups = getgroups(n, group_array);
583 	} while (ngroups > n);
584 }
585 
586 
587 /* Return non-zero if GID is one that we have in our groups list. */
588 //XXX: FIXME: duplicate of existing libbb function?
589 // see toplevel TODO file:
590 // possible code duplication ingroup() and is_a_group_member()
is_a_group_member(gid_t gid)591 static int is_a_group_member(gid_t gid)
592 {
593 	int i;
594 
595 	/* Short-circuit if possible, maybe saving a call to getgroups(). */
596 	if (gid == getgid() || gid == getegid())
597 		return 1;
598 
599 	if (ngroups == 0)
600 		initialize_group_array();
601 
602 	/* Search through the list looking for GID. */
603 	for (i = 0; i < ngroups; i++)
604 		if (gid == group_array[i])
605 			return 1;
606 
607 	return 0;
608 }
609 
610 
611 /* Do the same thing access(2) does, but use the effective uid and gid,
612    and don't make the mistake of telling root that any file is
613    executable. */
test_eaccess(char * path,int mode)614 static int test_eaccess(char *path, int mode)
615 {
616 	struct stat st;
617 	unsigned int euid = geteuid();
618 
619 	if (stat(path, &st) < 0)
620 		return -1;
621 
622 	if (euid == 0) {
623 		/* Root can read or write any file. */
624 		if (mode != X_OK)
625 			return 0;
626 
627 		/* Root can execute any file that has any one of the execute
628 		 * bits set. */
629 		if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
630 			return 0;
631 	}
632 
633 	if (st.st_uid == euid)  /* owner */
634 		mode <<= 6;
635 	else if (is_a_group_member(st.st_gid))
636 		mode <<= 3;
637 
638 	if (st.st_mode & mode)
639 		return 0;
640 
641 	return -1;
642 }
643 
644 
filstat(char * nm,enum token mode)645 static int filstat(char *nm, enum token mode)
646 {
647 	struct stat s;
648 	unsigned i = i; /* gcc 3.x thinks it can be used uninitialized */
649 
650 	if (mode == FILSYM) {
651 #ifdef S_IFLNK
652 		if (lstat(nm, &s) == 0) {
653 			i = S_IFLNK;
654 			goto filetype;
655 		}
656 #endif
657 		return 0;
658 	}
659 
660 	if (stat(nm, &s) != 0)
661 		return 0;
662 	if (mode == FILEXIST)
663 		return 1;
664 	if (is_file_access(mode)) {
665 		if (mode == FILRD)
666 			i = R_OK;
667 		if (mode == FILWR)
668 			i = W_OK;
669 		if (mode == FILEX)
670 			i = X_OK;
671 		return test_eaccess(nm, i) == 0;
672 	}
673 	if (is_file_type(mode)) {
674 		if (mode == FILREG)
675 			i = S_IFREG;
676 		if (mode == FILDIR)
677 			i = S_IFDIR;
678 		if (mode == FILCDEV)
679 			i = S_IFCHR;
680 		if (mode == FILBDEV)
681 			i = S_IFBLK;
682 		if (mode == FILFIFO) {
683 #ifdef S_IFIFO
684 			i = S_IFIFO;
685 #else
686 			return 0;
687 #endif
688 		}
689 		if (mode == FILSOCK) {
690 #ifdef S_IFSOCK
691 			i = S_IFSOCK;
692 #else
693 			return 0;
694 #endif
695 		}
696  filetype:
697 		return ((s.st_mode & S_IFMT) == i);
698 	}
699 	if (is_file_bit(mode)) {
700 		if (mode == FILSUID)
701 			i = S_ISUID;
702 		if (mode == FILSGID)
703 			i = S_ISGID;
704 		if (mode == FILSTCK)
705 			i = S_ISVTX;
706 		return ((s.st_mode & i) != 0);
707 	}
708 	if (mode == FILGZ)
709 		return s.st_size > 0L;
710 	if (mode == FILUID)
711 		return s.st_uid == geteuid();
712 	if (mode == FILGID)
713 		return s.st_gid == getegid();
714 	return 1; /* NOTREACHED */
715 }
716 
717 
nexpr(enum token n)718 static number_t nexpr(enum token n)
719 {
720 	number_t res;
721 
722 	nest_msg(">nexpr(%s)\n", TOKSTR[n]);
723 	if (n == UNOT) {
724 		n = check_operator(*++args);
725 		if (n == EOI) {
726 			/* special case: [ ! ], [ a -a ! ] are valid */
727 			/* IOW, "! ARG" may miss ARG */
728 			args--;
729 			unnest_msg("<nexpr:1 (!EOI), args:%s(%p)\n", args[0], &args[0]);
730 			return 1;
731 		}
732 		res = !nexpr(n);
733 		unnest_msg("<nexpr:%lld\n", res);
734 		return res;
735 	}
736 	res = primary(n);
737 	unnest_msg("<nexpr:%lld\n", res);
738 	return res;
739 }
740 
741 
aexpr(enum token n)742 static number_t aexpr(enum token n)
743 {
744 	number_t res;
745 
746 	nest_msg(">aexpr(%s)\n", TOKSTR[n]);
747 	res = nexpr(n);
748 	dbg_msg("aexpr: nexpr:%lld, next args:%s(%p)\n", res, args[1], &args[1]);
749 	if (check_operator(*++args) == BAND) {
750 		dbg_msg("aexpr: arg is AND, next args:%s(%p)\n", args[1], &args[1]);
751 		res = aexpr(check_operator(*++args)) && res;
752 		unnest_msg("<aexpr:%lld\n", res);
753 		return res;
754 	}
755 	args--;
756 	unnest_msg("<aexpr:%lld, args:%s(%p)\n", res, args[0], &args[0]);
757 	return res;
758 }
759 
760 
oexpr(enum token n)761 static number_t oexpr(enum token n)
762 {
763 	number_t res;
764 
765 	nest_msg(">oexpr(%s)\n", TOKSTR[n]);
766 	res = aexpr(n);
767 	dbg_msg("oexpr: aexpr:%lld, next args:%s(%p)\n", res, args[1], &args[1]);
768 	if (check_operator(*++args) == BOR) {
769 		dbg_msg("oexpr: next arg is OR, next args:%s(%p)\n", args[1], &args[1]);
770 		res = oexpr(check_operator(*++args)) || res;
771 		unnest_msg("<oexpr:%lld\n", res);
772 		return res;
773 	}
774 	args--;
775 	unnest_msg("<oexpr:%lld, args:%s(%p)\n", res, args[0], &args[0]);
776 	return res;
777 }
778 
779 
primary(enum token n)780 static number_t primary(enum token n)
781 {
782 #if TEST_DEBUG
783 	number_t res = res; /* for compiler */
784 #else
785 	number_t res;
786 #endif
787 	const struct operator_t *args0_op;
788 
789 	nest_msg(">primary(%s)\n", TOKSTR[n]);
790 	if (n == EOI) {
791 		syntax(NULL, "argument expected");
792 	}
793 	if (n == LPAREN) {
794 		res = oexpr(check_operator(*++args));
795 		if (check_operator(*++args) != RPAREN)
796 			syntax(NULL, "closing paren expected");
797 		unnest_msg("<primary:%lld\n", res);
798 		return res;
799 	}
800 
801 	/* coreutils 6.9 checks "is args[1] binop and args[2] exist?" first,
802 	 * do the same */
803 	args0_op = last_operator;
804 	/* last_operator = operator at args[1] */
805 	if (check_operator(args[1]) != EOI) { /* if args[1] != NULL */
806 		if (args[2]) {
807 			// coreutils also does this:
808 			// if (args[3] && args[0]="-l" && args[2] is BINOP)
809 			//	return binop(1 /* prepended by -l */);
810 			if (last_operator->op_type == BINOP)
811 				unnest_msg_and_return(binop(), "<primary: binop:%lld\n");
812 		}
813 	}
814 	/* check "is args[0] unop?" second */
815 	if (args0_op->op_type == UNOP) {
816 		/* unary expression */
817 		if (args[1] == NULL)
818 //			syntax(args0_op->op_text, "argument expected");
819 			goto check_emptiness;
820 		args++;
821 		if (n == STREZ)
822 			unnest_msg_and_return(args[0][0] == '\0', "<primary:%lld\n");
823 		if (n == STRNZ)
824 			unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
825 		if (n == FILTT)
826 			unnest_msg_and_return(isatty(getn(*args)), "<primary: isatty(%s)%lld\n", *args);
827 		unnest_msg_and_return(filstat(*args, n), "<primary: filstat(%s):%lld\n", *args);
828 	}
829 
830 	/*check_operator(args[1]); - already done */
831 	if (last_operator->op_type == BINOP) {
832 		/* args[2] is known to be NULL, isn't it bound to fail? */
833 		unnest_msg_and_return(binop(), "<primary:%lld\n");
834 	}
835  check_emptiness:
836 	unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
837 }
838 
839 
test_main(int argc,char ** argv)840 int test_main(int argc, char **argv)
841 {
842 	int res;
843 	const char *arg0;
844 
845 	arg0 = bb_basename(argv[0]);
846 	if ((ENABLE_TEST1 || ENABLE_TEST2 || ENABLE_ASH_BUILTIN_TEST || ENABLE_HUSH)
847 	 && (arg0[0] == '[')
848 	) {
849 		--argc;
850 		if (!arg0[1]) { /* "[" ? */
851 			if (NOT_LONE_CHAR(argv[argc], ']')) {
852 				bb_error_msg("missing ]");
853 				return 2;
854 			}
855 		} else { /* assuming "[[" */
856 			if (strcmp(argv[argc], "]]") != 0) {
857 				bb_error_msg("missing ]]");
858 				return 2;
859 			}
860 		}
861 		argv[argc] = NULL;
862 	}
863 	/* argc is unused after this point */
864 
865 	/* We must do DEINIT_S() prior to returning */
866 	INIT_S();
867 
868 	res = setjmp(leaving);
869 	if (res)
870 		goto ret;
871 
872 	/* resetting ngroups is probably unnecessary.  it will
873 	 * force a new call to getgroups(), which prevents using
874 	 * group data fetched during a previous call.  but the
875 	 * only way the group data could be stale is if there's
876 	 * been an intervening call to setgroups(), and this
877 	 * isn't likely in the case of a shell.  paranoia
878 	 * prevails...
879 	 */
880 	/*ngroups = 0; - done by INIT_S() */
881 
882 	argv++;
883 	args = argv;
884 
885 	/* Implement special cases from POSIX.2, section 4.62.4.
886 	 * Testcase: "test '(' = '('"
887 	 * The general parser would misinterpret '(' as group start.
888 	 */
889 	if (1) {
890 		int negate = 0;
891  again:
892 		if (!argv[0]) {
893 			/* "test" */
894 			res = 1;
895 			goto ret_special;
896 		}
897 		if (!argv[1]) {
898 			/* "test [!] arg" */
899 			res = (argv[0][0] == '\0');
900 			goto ret_special;
901 		}
902 		if (argv[2] && !argv[3]) {
903 			check_operator(argv[1]);
904 			if (last_operator->op_type == BINOP) {
905 				/* "test [!] arg1 <binary_op> arg2" */
906 				args = argv;
907 				res = (binop() == 0);
908  ret_special:
909 				/* If there was leading "!" op... */
910 				res ^= negate;
911 				goto ret;
912 			}
913 		}
914 		if (LONE_CHAR(argv[0], '!')) {
915 			argv++;
916 			negate ^= 1;
917 			goto again;
918 		}
919 	}
920 
921 	res = !oexpr(check_operator(*args));
922 
923 	if (*args != NULL && *++args != NULL) {
924 		/* Examples:
925 		 * test 3 -lt 5 6
926 		 * test -t 1 2
927 		 */
928 		bb_error_msg("%s: unknown operand", *args);
929 		res = 2;
930 	}
931  ret:
932 	DEINIT_S();
933 	return res;
934 }
935