1 /* vi: set sw=4 ts=4: */
3 * test implementation for busybox
5 * Copyright (c) by a whole pile of folks:
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
15 * modified by Bernhard Fischer to be useable (i.e. a bit less bloaty).
17 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
19 * Original copyright notice states:
20 * "This program is in the Public Domain."
26 /* This is a NOEXEC applet. Be very careful! */
29 /* test(1) accepts the following grammar:
30 oexpr ::= aexpr | aexpr "-o" oexpr ;
31 aexpr ::= nexpr | nexpr "-a" aexpr ;
32 nexpr ::= primary | "!" primary
33 primary ::= unary-operator operand
34 | operand binary-operator operand
38 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
39 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
41 binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
43 operand ::= <any legal UNIX file name>
88 #define is_int_op(a) (((unsigned char)((a) - INTEQ)) <= 5)
89 #define is_str_op(a) (((unsigned char)((a) - STREZ)) <= 5)
90 #define is_file_op(a) (((unsigned char)((a) - FILNT)) <= 2)
91 #define is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2)
92 #define is_file_type(a) (((unsigned char)((a) - FILREG)) <= 5)
93 #define is_file_bit(a) (((unsigned char)((a) - FILSUID)) <= 2)
102 static const struct t_op
{
104 unsigned char op_num
, op_type
;
106 { "-r", FILRD
, UNOP
},
107 { "-w", FILWR
, UNOP
},
108 { "-x", FILEX
, UNOP
},
109 { "-e", FILEXIST
, UNOP
},
110 { "-f", FILREG
, UNOP
},
111 { "-d", FILDIR
, UNOP
},
112 { "-c", FILCDEV
, UNOP
},
113 { "-b", FILBDEV
, UNOP
},
114 { "-p", FILFIFO
, UNOP
},
115 { "-u", FILSUID
, UNOP
},
116 { "-g", FILSGID
, UNOP
},
117 { "-k", FILSTCK
, UNOP
},
118 { "-s", FILGZ
, UNOP
},
119 { "-t", FILTT
, UNOP
},
120 { "-z", STREZ
, UNOP
},
121 { "-n", STRNZ
, UNOP
},
122 { "-h", FILSYM
, UNOP
}, /* for backwards compat */
124 { "-O" , FILUID
, UNOP
},
125 { "-G" , FILGID
, UNOP
},
126 { "-L" , FILSYM
, UNOP
},
127 { "-S" , FILSOCK
, UNOP
},
128 { "=" , STREQ
, BINOP
},
129 { "==" , STREQ
, BINOP
},
130 { "!=" , STRNE
, BINOP
},
131 { "<" , STRLT
, BINOP
},
132 { ">" , STRGT
, BINOP
},
133 { "-eq", INTEQ
, BINOP
},
134 { "-ne", INTNE
, BINOP
},
135 { "-ge", INTGE
, BINOP
},
136 { "-gt", INTGT
, BINOP
},
137 { "-le", INTLE
, BINOP
},
138 { "-lt", INTLT
, BINOP
},
139 { "-nt", FILNT
, BINOP
},
140 { "-ot", FILOT
, BINOP
},
141 { "-ef", FILEQ
, BINOP
},
142 { "!" , UNOT
, BUNOP
},
143 { "-a" , BAND
, BBINOP
},
144 { "-o" , BOR
, BBINOP
},
145 { "(" , LPAREN
, PAREN
},
146 { ")" , RPAREN
, PAREN
},
149 enum { NUM_OPS
= sizeof(ops
) / sizeof(ops
[0]) };
151 #if ENABLE_FEATURE_TEST_64
152 typedef int64_t arith_t
;
157 /* Cannot eliminate these static data (do the G trick)
158 * because of bb_test usage from other applets */
160 static struct t_op
const *t_wp_op
;
161 static gid_t
*group_array
;
163 static jmp_buf leaving
;
165 static enum token
t_lex(char *s
);
166 static arith_t
oexpr(enum token n
);
167 static arith_t
aexpr(enum token n
);
168 static arith_t
nexpr(enum token n
);
169 static int binop(void);
170 static arith_t
primary(enum token n
);
171 static int filstat(char *nm
, enum token mode
);
172 static arith_t
getn(const char *s
);
174 static int newerf(const char *f1, const char *f2);
175 static int olderf(const char *f1, const char *f2);
176 static int equalf(const char *f1, const char *f2);
178 static int test_eaccess(char *path
, int mode
);
179 static int is_a_group_member(gid_t gid
);
180 static void initialize_group_array(void);
182 int bb_test(int argc
, char **argv
)
188 arg0
= strrchr(argv
[0], '/');
189 if (!arg0
++) arg0
= argv
[0];
190 if (arg0
[0] == '[') {
192 if (!arg0
[1]) { /* "[" ? */
193 if (NOT_LONE_CHAR(argv
[argc
], ']')) {
194 bb_error_msg("missing ]");
197 } else { /* assuming "[[" */
198 if (strcmp(argv
[argc
], "]]") != 0) {
199 bb_error_msg("missing ]]");
206 res
= setjmp(leaving
);
210 /* resetting ngroups is probably unnecessary. it will
211 * force a new call to getgroups(), which prevents using
212 * group data fetched during a previous call. but the
213 * only way the group data could be stale is if there's
214 * been an intervening call to setgroups(), and this
215 * isn't likely in the case of a shell. paranoia
220 /* Implement special cases from POSIX.2, section 4.62.4 */
224 return *argv
[1] == '\0';
226 /* remember if we saw argc==4 which wants *no* '!' test */
229 (LONE_CHAR(argv
[1], '!'))
230 : (argv
[1][0] != '!' || argv
[1][1] != '\0'))
233 return *argv
[2] != '\0';
235 t_lex(argv
[2 + _off
]);
236 if (t_wp_op
&& t_wp_op
->op_type
== BINOP
) {
237 t_wp
= &argv
[1 + _off
];
238 return binop() == _off
;
242 res
= !oexpr(t_lex(*t_wp
));
244 if (*t_wp
!= NULL
&& *++t_wp
!= NULL
) {
245 bb_error_msg("%s: unknown operand", *t_wp
);
251 static void syntax(const char *op
, const char *msg
) ATTRIBUTE_NORETURN
;
252 static void syntax(const char *op
, const char *msg
)
255 bb_error_msg("%s: %s", op
, msg
);
257 bb_error_msg("%s: %s"+4, msg
);
262 static arith_t
oexpr(enum token n
)
267 if (t_lex(*++t_wp
) == BOR
) {
268 return oexpr(t_lex(*++t_wp
)) || res
;
274 static arith_t
aexpr(enum token n
)
279 if (t_lex(*++t_wp
) == BAND
)
280 return aexpr(t_lex(*++t_wp
)) && res
;
285 static arith_t
nexpr(enum token n
)
288 return !nexpr(t_lex(*++t_wp
));
292 static arith_t
primary(enum token n
)
297 syntax(NULL
, "argument expected");
300 res
= oexpr(t_lex(*++t_wp
));
301 if (t_lex(*++t_wp
) != RPAREN
)
302 syntax(NULL
, "closing paren expected");
305 if (t_wp_op
&& t_wp_op
->op_type
== UNOP
) {
306 /* unary expression */
308 syntax(t_wp_op
->op_text
, "argument expected");
310 return t_wp
[0][0] == '\0';
312 return t_wp
[0][0] != '\0';
314 return isatty(getn(*t_wp
));
315 return filstat(*t_wp
, n
);
319 if (t_wp_op
&& t_wp_op
->op_type
== BINOP
) {
323 return t_wp
[0][0] != '\0';
326 static int binop(void)
328 const char *opnd1
, *opnd2
;
329 struct t_op
const *op
;
333 (void) t_lex(*++t_wp
);
338 syntax(op
->op_text
, "argument expected");
340 if (is_int_op(op
->op_num
)) {
343 if (op
->op_num
== INTEQ
)
345 if (op
->op_num
== INTNE
)
347 if (op
->op_num
== INTGE
)
349 if (op
->op_num
== INTGT
)
351 if (op
->op_num
== INTLE
)
353 if (op
->op_num
== INTLT
)
356 if (is_str_op(op
->op_num
)) {
357 val1
= strcmp(opnd1
, opnd2
);
358 if (op
->op_num
== STREQ
)
360 if (op
->op_num
== STRNE
)
362 if (op
->op_num
== STRLT
)
364 if (op
->op_num
== STRGT
)
367 /* We are sure that these three are by now the only binops we didn't check
368 * yet, so we do not check if the class is correct:
370 /* if (is_file_op(op->op_num)) */
374 if (stat(opnd1
, &b1
) || stat(opnd2
, &b2
))
375 return 0; /* false, since at least one stat failed */
376 if (op
->op_num
== FILNT
)
377 return b1
.st_mtime
> b2
.st_mtime
;
378 if (op
->op_num
== FILOT
)
379 return b1
.st_mtime
< b2
.st_mtime
;
380 if (op
->op_num
== FILEQ
)
381 return b1
.st_dev
== b2
.st_dev
&& b1
.st_ino
== b2
.st_ino
;
383 return 1; /* NOTREACHED */
386 static int filstat(char *nm
, enum token mode
)
389 int i
= i
; /* gcc 3.x thinks it can be used uninitialized */
391 if (mode
== FILSYM
) {
393 if (lstat(nm
, &s
) == 0) {
403 int len
= strlen(nm
), ret
;
405 if (len
>= 4 && !strcmp(nm
+len
-4,".exe"))
408 exepath
= malloc(len
+5);
409 memcpy(exepath
, nm
, len
);
410 memcpy(exepath
+len
, ".exe", 5);
412 ret
= stat(exepath
, &s
);
419 if (stat(nm
, &s
) != 0)
421 if (mode
== FILEXIST
)
423 if (is_file_access(mode
)) {
430 return test_eaccess(nm
, i
) == 0;
432 if (is_file_type(mode
)) {
441 if (mode
== FILFIFO
) {
448 if (mode
== FILSOCK
) {
456 return ((s
.st_mode
& S_IFMT
) == i
);
458 if (is_file_bit(mode
)) {
465 return ((s
.st_mode
& i
) != 0);
468 return s
.st_size
> 0L;
471 return s
.st_uid
== geteuid();
473 return s
.st_gid
== getegid();
475 return 1; /* NOTREACHED */
478 static enum token
t_lex(char *s
)
480 const struct t_op
*op
;
489 if (strcmp(s
, op
->op_text
) == 0) {
494 } while (op
< ops
+ NUM_OPS
);
499 /* atoi with error detection */
500 //XXX: FIXME: duplicate of existing libbb function?
501 static arith_t
getn(const char *s
)
504 #if ENABLE_FEATURE_TEST_64
511 #if ENABLE_FEATURE_TEST_64
512 r
= strtoll(s
, &p
, 10);
514 r
= strtol(s
, &p
, 10);
518 syntax(s
, "out of range");
520 if (*(skip_whitespace(p
)))
521 syntax(s
, "bad number");
527 static int newerf(const char *f1, const char *f2)
531 return (stat(f1, &b1) == 0 &&
532 stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime);
535 static int olderf(const char *f1, const char *f2)
539 return (stat(f1, &b1) == 0 &&
540 stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime);
543 static int equalf(const char *f1, const char *f2)
547 return (stat(f1, &b1) == 0 &&
548 stat(f2, &b2) == 0 &&
549 b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
553 /* Do the same thing access(2) does, but use the effective uid and gid,
554 and don't make the mistake of telling root that any file is
556 static int test_eaccess(char *path
, int mode
)
560 unsigned int euid
= geteuid();
563 if (stat(path
, &st
) < 0)
568 /* Root can read or write any file. */
572 /* Root can execute any file that has any one of the execute
574 if (st
.st_mode
& (S_IXUSR
| S_IXGRP
| S_IXOTH
))
578 if (st
.st_uid
== euid
) /* owner */
580 else if (is_a_group_member(st
.st_gid
))
584 if (st
.st_mode
& mode
)
591 static void initialize_group_array(void)
593 ngroups
= getgroups(0, NULL
);
595 /* FIXME: ash tries so hard to not die on OOM,
596 * and we spoil it with just one xrealloc here */
597 /* We realloc, because bb_test can be entered repeatedly by shell.
598 * Testcase (ash): 'while true; do test -x some_file; done'
599 * and watch top. (some_file must have owner != you) */
600 group_array
= xrealloc(group_array
, ngroups
* sizeof(gid_t
));
601 getgroups(ngroups
, group_array
);
605 /* Return non-zero if GID is one that we have in our groups list. */
606 //XXX: FIXME: duplicate of existing libbb function?
607 // see toplevel TODO file:
608 // possible code duplication ingroup() and is_a_group_member()
609 static int is_a_group_member(gid_t gid
)
613 /* Short-circuit if possible, maybe saving a call to getgroups(). */
614 if (gid
== getgid() || gid
== getegid())
618 initialize_group_array();
620 /* Search through the list looking for GID. */
621 for (i
= 0; i
< ngroups
; i
++)
622 if (gid
== group_array
[i
])
630 /* applet entry point */
632 int test_main(int argc
, char **argv
);
633 int test_main(int argc
, char **argv
)
635 return bb_test(argc
, argv
);