12 |
* modified by Herbert Xu to be used as built-in in ash. |
* modified by Herbert Xu to be used as built-in in ash. |
13 |
* modified by Erik Andersen <andersen@codepoet.org> to be used |
* modified by Erik Andersen <andersen@codepoet.org> to be used |
14 |
* in busybox. |
* 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 tarball for details. |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
18 |
* |
* |
20 |
* "This program is in the Public Domain." |
* "This program is in the Public Domain." |
21 |
*/ |
*/ |
22 |
|
|
23 |
#include "busybox.h" |
#include "libbb.h" |
|
#include <unistd.h> |
|
|
#include <ctype.h> |
|
|
#include <errno.h> |
|
|
#include <string.h> |
|
24 |
#include <setjmp.h> |
#include <setjmp.h> |
25 |
|
|
26 |
|
/* This is a NOFORK applet. Be very careful! */ |
27 |
|
|
28 |
|
/* test_main() is called from shells, and we need to be extra careful here. |
29 |
|
* This is true regardless of PREFER_APPLETS and STANDALONE_SHELL |
30 |
|
* state. */ |
31 |
|
|
32 |
|
|
33 |
/* test(1) accepts the following grammar: |
/* test(1) accepts the following grammar: |
34 |
oexpr ::= aexpr | aexpr "-o" oexpr ; |
oexpr ::= aexpr | aexpr "-o" oexpr ; |
35 |
aexpr ::= nexpr | nexpr "-a" aexpr ; |
aexpr ::= nexpr | nexpr "-a" aexpr ; |
47 |
operand ::= <any legal UNIX file name> |
operand ::= <any legal UNIX file name> |
48 |
*/ |
*/ |
49 |
|
|
50 |
|
#define TEST_DEBUG 0 |
51 |
|
|
52 |
enum token { |
enum token { |
53 |
EOI, |
EOI, |
54 |
FILRD, |
FILRD, |
91 |
RPAREN, |
RPAREN, |
92 |
OPERAND |
OPERAND |
93 |
}; |
}; |
94 |
|
#define is_int_op(a) (((unsigned char)((a) - INTEQ)) <= 5) |
95 |
|
#define is_str_op(a) (((unsigned char)((a) - STREZ)) <= 5) |
96 |
|
#define is_file_op(a) (((unsigned char)((a) - FILNT)) <= 2) |
97 |
|
#define is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2) |
98 |
|
#define is_file_type(a) (((unsigned char)((a) - FILREG)) <= 5) |
99 |
|
#define is_file_bit(a) (((unsigned char)((a) - FILSUID)) <= 2) |
100 |
|
|
101 |
|
#if TEST_DEBUG |
102 |
|
int depth; |
103 |
|
#define nest_msg(...) do { \ |
104 |
|
depth++; \ |
105 |
|
fprintf(stderr, "%*s", depth*2, ""); \ |
106 |
|
fprintf(stderr, __VA_ARGS__); \ |
107 |
|
} while (0) |
108 |
|
#define unnest_msg(...) do { \ |
109 |
|
fprintf(stderr, "%*s", depth*2, ""); \ |
110 |
|
fprintf(stderr, __VA_ARGS__); \ |
111 |
|
depth--; \ |
112 |
|
} while (0) |
113 |
|
#define dbg_msg(...) do { \ |
114 |
|
fprintf(stderr, "%*s", depth*2, ""); \ |
115 |
|
fprintf(stderr, __VA_ARGS__); \ |
116 |
|
} while (0) |
117 |
|
#define unnest_msg_and_return(expr, ...) do { \ |
118 |
|
number_t __res = (expr); \ |
119 |
|
fprintf(stderr, "%*s", depth*2, ""); \ |
120 |
|
fprintf(stderr, __VA_ARGS__, res); \ |
121 |
|
depth--; \ |
122 |
|
return __res; \ |
123 |
|
} while (0) |
124 |
|
static const char *const TOKSTR[] = { |
125 |
|
"EOI", |
126 |
|
"FILRD", |
127 |
|
"FILWR", |
128 |
|
"FILEX", |
129 |
|
"FILEXIST", |
130 |
|
"FILREG", |
131 |
|
"FILDIR", |
132 |
|
"FILCDEV", |
133 |
|
"FILBDEV", |
134 |
|
"FILFIFO", |
135 |
|
"FILSOCK", |
136 |
|
"FILSYM", |
137 |
|
"FILGZ", |
138 |
|
"FILTT", |
139 |
|
"FILSUID", |
140 |
|
"FILSGID", |
141 |
|
"FILSTCK", |
142 |
|
"FILNT", |
143 |
|
"FILOT", |
144 |
|
"FILEQ", |
145 |
|
"FILUID", |
146 |
|
"FILGID", |
147 |
|
"STREZ", |
148 |
|
"STRNZ", |
149 |
|
"STREQ", |
150 |
|
"STRNE", |
151 |
|
"STRLT", |
152 |
|
"STRGT", |
153 |
|
"INTEQ", |
154 |
|
"INTNE", |
155 |
|
"INTGE", |
156 |
|
"INTGT", |
157 |
|
"INTLE", |
158 |
|
"INTLT", |
159 |
|
"UNOT", |
160 |
|
"BAND", |
161 |
|
"BOR", |
162 |
|
"LPAREN", |
163 |
|
"RPAREN", |
164 |
|
"OPERAND" |
165 |
|
}; |
166 |
|
#else |
167 |
|
#define nest_msg(...) ((void)0) |
168 |
|
#define unnest_msg(...) ((void)0) |
169 |
|
#define dbg_msg(...) ((void)0) |
170 |
|
#define unnest_msg_and_return(expr, ...) return expr |
171 |
|
#endif |
172 |
|
|
173 |
enum token_types { |
enum token_types { |
174 |
UNOP, |
UNOP, |
178 |
PAREN |
PAREN |
179 |
}; |
}; |
180 |
|
|
181 |
static const struct t_op { |
struct operator_t { |
182 |
const char *op_text; |
char op_text[4]; |
183 |
short op_num, op_type; |
unsigned char op_num, op_type; |
184 |
} ops[] = { |
}; |
185 |
{ |
|
186 |
"-r", FILRD, UNOP}, { |
static const struct operator_t ops[] = { |
187 |
"-w", FILWR, UNOP}, { |
{ "-r", FILRD , UNOP }, |
188 |
"-x", FILEX, UNOP}, { |
{ "-w", FILWR , UNOP }, |
189 |
"-e", FILEXIST, UNOP}, { |
{ "-x", FILEX , UNOP }, |
190 |
"-f", FILREG, UNOP}, { |
{ "-e", FILEXIST, UNOP }, |
191 |
"-d", FILDIR, UNOP}, { |
{ "-f", FILREG , UNOP }, |
192 |
"-c", FILCDEV, UNOP}, { |
{ "-d", FILDIR , UNOP }, |
193 |
"-b", FILBDEV, UNOP}, { |
{ "-c", FILCDEV , UNOP }, |
194 |
"-p", FILFIFO, UNOP}, { |
{ "-b", FILBDEV , UNOP }, |
195 |
"-u", FILSUID, UNOP}, { |
{ "-p", FILFIFO , UNOP }, |
196 |
"-g", FILSGID, UNOP}, { |
{ "-u", FILSUID , UNOP }, |
197 |
"-k", FILSTCK, UNOP}, { |
{ "-g", FILSGID , UNOP }, |
198 |
"-s", FILGZ, UNOP}, { |
{ "-k", FILSTCK , UNOP }, |
199 |
"-t", FILTT, UNOP}, { |
{ "-s", FILGZ , UNOP }, |
200 |
"-z", STREZ, UNOP}, { |
{ "-t", FILTT , UNOP }, |
201 |
"-n", STRNZ, UNOP}, { |
{ "-z", STREZ , UNOP }, |
202 |
"-h", FILSYM, UNOP}, /* for backwards compat */ |
{ "-n", STRNZ , UNOP }, |
203 |
{ |
{ "-h", FILSYM , UNOP }, /* for backwards compat */ |
204 |
"-O", FILUID, UNOP}, { |
|
205 |
"-G", FILGID, UNOP}, { |
{ "-O" , FILUID , UNOP }, |
206 |
"-L", FILSYM, UNOP}, { |
{ "-G" , FILGID , UNOP }, |
207 |
"-S", FILSOCK, UNOP}, { |
{ "-L" , FILSYM , UNOP }, |
208 |
"=", STREQ, BINOP}, { |
{ "-S" , FILSOCK, UNOP }, |
209 |
"==", STREQ, BINOP}, { |
{ "=" , STREQ , BINOP }, |
210 |
"!=", STRNE, BINOP}, { |
{ "==" , STREQ , BINOP }, |
211 |
"<", STRLT, BINOP}, { |
{ "!=" , STRNE , BINOP }, |
212 |
">", STRGT, BINOP}, { |
{ "<" , STRLT , BINOP }, |
213 |
"-eq", INTEQ, BINOP}, { |
{ ">" , STRGT , BINOP }, |
214 |
"-ne", INTNE, BINOP}, { |
{ "-eq", INTEQ , BINOP }, |
215 |
"-ge", INTGE, BINOP}, { |
{ "-ne", INTNE , BINOP }, |
216 |
"-gt", INTGT, BINOP}, { |
{ "-ge", INTGE , BINOP }, |
217 |
"-le", INTLE, BINOP}, { |
{ "-gt", INTGT , BINOP }, |
218 |
"-lt", INTLT, BINOP}, { |
{ "-le", INTLE , BINOP }, |
219 |
"-nt", FILNT, BINOP}, { |
{ "-lt", INTLT , BINOP }, |
220 |
"-ot", FILOT, BINOP}, { |
{ "-nt", FILNT , BINOP }, |
221 |
"-ef", FILEQ, BINOP}, { |
{ "-ot", FILOT , BINOP }, |
222 |
"!", UNOT, BUNOP}, { |
{ "-ef", FILEQ , BINOP }, |
223 |
"-a", BAND, BBINOP}, { |
{ "!" , UNOT , BUNOP }, |
224 |
"-o", BOR, BBINOP}, { |
{ "-a" , BAND , BBINOP }, |
225 |
"(", LPAREN, PAREN}, { |
{ "-o" , BOR , BBINOP }, |
226 |
")", RPAREN, PAREN}, { |
{ "(" , LPAREN , PAREN }, |
227 |
0, 0, 0} |
{ ")" , RPAREN , PAREN }, |
228 |
}; |
}; |
229 |
|
|
230 |
#ifdef CONFIG_FEATURE_TEST_64 |
|
231 |
typedef int64_t arith_t; |
#if ENABLE_FEATURE_TEST_64 |
232 |
|
typedef int64_t number_t; |
233 |
#else |
#else |
234 |
typedef int arith_t; |
typedef int number_t; |
235 |
#endif |
#endif |
236 |
|
|
|
static char **t_wp; |
|
|
static struct t_op const *t_wp_op; |
|
|
static gid_t *group_array; |
|
|
static int ngroups; |
|
|
|
|
|
static enum token t_lex(char *s); |
|
|
static arith_t oexpr(enum token n); |
|
|
static arith_t aexpr(enum token n); |
|
|
static arith_t nexpr(enum token n); |
|
|
static int binop(void); |
|
|
static arith_t primary(enum token n); |
|
|
static int filstat(char *nm, enum token mode); |
|
|
static arith_t getn(const char *s); |
|
|
static int newerf(const char *f1, const char *f2); |
|
|
static int olderf(const char *f1, const char *f2); |
|
|
static int equalf(const char *f1, const char *f2); |
|
|
static int test_eaccess(char *path, int mode); |
|
|
static int is_a_group_member(gid_t gid); |
|
|
static void initialize_group_array(void); |
|
|
|
|
|
static jmp_buf leaving; |
|
237 |
|
|
238 |
int bb_test(int argc, char **argv) |
/* We try to minimize both static and stack usage. */ |
239 |
{ |
struct test_statics { |
240 |
int res; |
char **args; |
241 |
|
/* set only by check_operator(), either to bogus struct |
242 |
|
* or points to matching operator_t struct. Never NULL. */ |
243 |
|
const struct operator_t *last_operator; |
244 |
|
gid_t *group_array; |
245 |
|
int ngroups; |
246 |
|
jmp_buf leaving; |
247 |
|
}; |
248 |
|
|
249 |
if (LONE_CHAR(argv[0], '[')) { |
/* See test_ptr_hack.c */ |
250 |
--argc; |
extern struct test_statics *const test_ptr_to_statics; |
|
if (NOT_LONE_CHAR(argv[argc], ']')) { |
|
|
bb_error_msg("missing ]"); |
|
|
return 2; |
|
|
} |
|
|
argv[argc] = NULL; |
|
|
} else if (strcmp(argv[0], "[[") == 0) { |
|
|
--argc; |
|
|
if (strcmp(argv[argc], "]]")) { |
|
|
bb_error_msg("missing ]]"); |
|
|
return 2; |
|
|
} |
|
|
argv[argc] = NULL; |
|
|
} |
|
251 |
|
|
252 |
res = setjmp(leaving); |
#define S (*test_ptr_to_statics) |
253 |
if (res) |
#define args (S.args ) |
254 |
return res; |
#define last_operator (S.last_operator) |
255 |
|
#define group_array (S.group_array ) |
256 |
|
#define ngroups (S.ngroups ) |
257 |
|
#define leaving (S.leaving ) |
258 |
|
|
259 |
|
#define INIT_S() do { \ |
260 |
|
(*(struct test_statics**)&test_ptr_to_statics) = xzalloc(sizeof(S)); \ |
261 |
|
barrier(); \ |
262 |
|
} while (0) |
263 |
|
#define DEINIT_S() do { \ |
264 |
|
free(test_ptr_to_statics); \ |
265 |
|
} while (0) |
266 |
|
|
267 |
/* resetting ngroups is probably unnecessary. it will |
static number_t primary(enum token n); |
|
* force a new call to getgroups(), which prevents using |
|
|
* group data fetched during a previous call. but the |
|
|
* only way the group data could be stale is if there's |
|
|
* been an intervening call to setgroups(), and this |
|
|
* isn't likely in the case of a shell. paranoia |
|
|
* prevails... |
|
|
*/ |
|
|
ngroups = 0; |
|
|
|
|
|
/* Implement special cases from POSIX.2, section 4.62.4 */ |
|
|
switch (argc) { |
|
|
case 1: |
|
|
return 1; |
|
|
case 2: |
|
|
return *argv[1] == '\0'; |
|
|
case 3: |
|
|
if (argv[1][0] == '!' && argv[1][1] == '\0') { |
|
|
return *argv[2] != '\0'; |
|
|
} |
|
|
break; |
|
|
case 4: |
|
|
if (argv[1][0] != '!' || argv[1][1] != '\0') { |
|
|
if (t_lex(argv[2]), t_wp_op && t_wp_op->op_type == BINOP) { |
|
|
t_wp = &argv[1]; |
|
|
return binop() == 0; |
|
|
} |
|
|
} |
|
|
break; |
|
|
case 5: |
|
|
if (argv[1][0] == '!' && argv[1][1] == '\0') { |
|
|
if (t_lex(argv[3]), t_wp_op && t_wp_op->op_type == BINOP) { |
|
|
t_wp = &argv[2]; |
|
|
return binop() != 0; |
|
|
} |
|
|
} |
|
|
break; |
|
|
} |
|
|
|
|
|
t_wp = &argv[1]; |
|
|
res = !oexpr(t_lex(*t_wp)); |
|
|
|
|
|
if (*t_wp != NULL && *++t_wp != NULL) { |
|
|
bb_error_msg("%s: unknown operand", *t_wp); |
|
|
return 2; |
|
|
} |
|
|
return res; |
|
|
} |
|
268 |
|
|
269 |
|
static void syntax(const char *op, const char *msg) NORETURN; |
270 |
static void syntax(const char *op, const char *msg) |
static void syntax(const char *op, const char *msg) |
271 |
{ |
{ |
272 |
if (op && *op) { |
if (op && *op) { |
273 |
bb_error_msg("%s: %s", op, msg); |
bb_error_msg("%s: %s", op, msg); |
274 |
} else { |
} else { |
275 |
bb_error_msg("%s", msg); |
bb_error_msg("%s: %s"+4, msg); |
276 |
} |
} |
277 |
longjmp(leaving, 2); |
longjmp(leaving, 2); |
278 |
} |
} |
279 |
|
|
|
static arith_t oexpr(enum token n) |
|
|
{ |
|
|
arith_t res; |
|
|
|
|
|
res = aexpr(n); |
|
|
if (t_lex(*++t_wp) == BOR) { |
|
|
return oexpr(t_lex(*++t_wp)) || res; |
|
|
} |
|
|
t_wp--; |
|
|
return res; |
|
|
} |
|
|
|
|
|
static arith_t aexpr(enum token n) |
|
|
{ |
|
|
arith_t res; |
|
|
|
|
|
res = nexpr(n); |
|
|
if (t_lex(*++t_wp) == BAND) |
|
|
return aexpr(t_lex(*++t_wp)) && res; |
|
|
t_wp--; |
|
|
return res; |
|
|
} |
|
|
|
|
|
static arith_t nexpr(enum token n) |
|
|
{ |
|
|
if (n == UNOT) |
|
|
return !nexpr(t_lex(*++t_wp)); |
|
|
return primary(n); |
|
|
} |
|
|
|
|
|
static arith_t primary(enum token n) |
|
|
{ |
|
|
arith_t res; |
|
|
|
|
|
if (n == EOI) { |
|
|
syntax(NULL, "argument expected"); |
|
|
} |
|
|
if (n == LPAREN) { |
|
|
res = oexpr(t_lex(*++t_wp)); |
|
|
if (t_lex(*++t_wp) != RPAREN) |
|
|
syntax(NULL, "closing paren expected"); |
|
|
return res; |
|
|
} |
|
|
if (t_wp_op && t_wp_op->op_type == UNOP) { |
|
|
/* unary expression */ |
|
|
if (*++t_wp == NULL) |
|
|
syntax(t_wp_op->op_text, "argument expected"); |
|
|
switch (n) { |
|
|
case STREZ: |
|
|
return strlen(*t_wp) == 0; |
|
|
case STRNZ: |
|
|
return strlen(*t_wp) != 0; |
|
|
case FILTT: |
|
|
return isatty(getn(*t_wp)); |
|
|
default: |
|
|
return filstat(*t_wp, n); |
|
|
} |
|
|
} |
|
|
|
|
|
if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { |
|
|
return binop(); |
|
|
} |
|
|
|
|
|
return strlen(*t_wp) > 0; |
|
|
} |
|
|
|
|
|
static int binop(void) |
|
|
{ |
|
|
const char *opnd1, *opnd2; |
|
|
struct t_op const *op; |
|
|
|
|
|
opnd1 = *t_wp; |
|
|
(void) t_lex(*++t_wp); |
|
|
op = t_wp_op; |
|
|
|
|
|
if ((opnd2 = *++t_wp) == (char *) 0) |
|
|
syntax(op->op_text, "argument expected"); |
|
|
|
|
|
switch (op->op_num) { |
|
|
case STREQ: |
|
|
return strcmp(opnd1, opnd2) == 0; |
|
|
case STRNE: |
|
|
return strcmp(opnd1, opnd2) != 0; |
|
|
case STRLT: |
|
|
return strcmp(opnd1, opnd2) < 0; |
|
|
case STRGT: |
|
|
return strcmp(opnd1, opnd2) > 0; |
|
|
case INTEQ: |
|
|
return getn(opnd1) == getn(opnd2); |
|
|
case INTNE: |
|
|
return getn(opnd1) != getn(opnd2); |
|
|
case INTGE: |
|
|
return getn(opnd1) >= getn(opnd2); |
|
|
case INTGT: |
|
|
return getn(opnd1) > getn(opnd2); |
|
|
case INTLE: |
|
|
return getn(opnd1) <= getn(opnd2); |
|
|
case INTLT: |
|
|
return getn(opnd1) < getn(opnd2); |
|
|
case FILNT: |
|
|
return newerf(opnd1, opnd2); |
|
|
case FILOT: |
|
|
return olderf(opnd1, opnd2); |
|
|
case FILEQ: |
|
|
return equalf(opnd1, opnd2); |
|
|
} |
|
|
/* NOTREACHED */ |
|
|
return 1; |
|
|
} |
|
|
|
|
|
static int filstat(char *nm, enum token mode) |
|
|
{ |
|
|
struct stat s; |
|
|
unsigned int i; |
|
|
|
|
|
if (mode == FILSYM) { |
|
|
#ifdef S_IFLNK |
|
|
if (lstat(nm, &s) == 0) { |
|
|
i = S_IFLNK; |
|
|
goto filetype; |
|
|
} |
|
|
#endif |
|
|
return 0; |
|
|
} |
|
|
|
|
|
if (stat(nm, &s) != 0) |
|
|
return 0; |
|
|
|
|
|
switch (mode) { |
|
|
case FILRD: |
|
|
return test_eaccess(nm, R_OK) == 0; |
|
|
case FILWR: |
|
|
return test_eaccess(nm, W_OK) == 0; |
|
|
case FILEX: |
|
|
return test_eaccess(nm, X_OK) == 0; |
|
|
case FILEXIST: |
|
|
return 1; |
|
|
case FILREG: |
|
|
i = S_IFREG; |
|
|
goto filetype; |
|
|
case FILDIR: |
|
|
i = S_IFDIR; |
|
|
goto filetype; |
|
|
case FILCDEV: |
|
|
i = S_IFCHR; |
|
|
goto filetype; |
|
|
case FILBDEV: |
|
|
i = S_IFBLK; |
|
|
goto filetype; |
|
|
case FILFIFO: |
|
|
#ifdef S_IFIFO |
|
|
i = S_IFIFO; |
|
|
goto filetype; |
|
|
#else |
|
|
return 0; |
|
|
#endif |
|
|
case FILSOCK: |
|
|
#ifdef S_IFSOCK |
|
|
i = S_IFSOCK; |
|
|
goto filetype; |
|
|
#else |
|
|
return 0; |
|
|
#endif |
|
|
case FILSUID: |
|
|
i = S_ISUID; |
|
|
goto filebit; |
|
|
case FILSGID: |
|
|
i = S_ISGID; |
|
|
goto filebit; |
|
|
case FILSTCK: |
|
|
i = S_ISVTX; |
|
|
goto filebit; |
|
|
case FILGZ: |
|
|
return s.st_size > 0L; |
|
|
case FILUID: |
|
|
return s.st_uid == geteuid(); |
|
|
case FILGID: |
|
|
return s.st_gid == getegid(); |
|
|
default: |
|
|
return 1; |
|
|
} |
|
|
|
|
|
filetype: |
|
|
return ((s.st_mode & S_IFMT) == i); |
|
|
|
|
|
filebit: |
|
|
return ((s.st_mode & i) != 0); |
|
|
} |
|
|
|
|
|
static enum token t_lex(char *s) |
|
|
{ |
|
|
struct t_op const *op = ops; |
|
|
|
|
|
if (s == 0) { |
|
|
t_wp_op = (struct t_op *) 0; |
|
|
return EOI; |
|
|
} |
|
|
while (op->op_text) { |
|
|
if (strcmp(s, op->op_text) == 0) { |
|
|
t_wp_op = op; |
|
|
return op->op_num; |
|
|
} |
|
|
op++; |
|
|
} |
|
|
t_wp_op = (struct t_op *) 0; |
|
|
return OPERAND; |
|
|
} |
|
|
|
|
280 |
/* atoi with error detection */ |
/* atoi with error detection */ |
281 |
static arith_t getn(const char *s) |
//XXX: FIXME: duplicate of existing libbb function? |
282 |
|
static number_t getn(const char *s) |
283 |
{ |
{ |
284 |
char *p; |
char *p; |
285 |
#ifdef CONFIG_FEATURE_TEST_64 |
#if ENABLE_FEATURE_TEST_64 |
286 |
long long r; |
long long r; |
287 |
#else |
#else |
288 |
long r; |
long r; |
289 |
#endif |
#endif |
290 |
|
|
291 |
errno = 0; |
errno = 0; |
292 |
#ifdef CONFIG_FEATURE_TEST_64 |
#if ENABLE_FEATURE_TEST_64 |
293 |
r = strtoll(s, &p, 10); |
r = strtoll(s, &p, 10); |
294 |
#else |
#else |
295 |
r = strtol(s, &p, 10); |
r = strtol(s, &p, 10); |
304 |
return r; |
return r; |
305 |
} |
} |
306 |
|
|
307 |
|
/* UNUSED |
308 |
static int newerf(const char *f1, const char *f2) |
static int newerf(const char *f1, const char *f2) |
309 |
{ |
{ |
310 |
struct stat b1, b2; |
struct stat b1, b2; |
329 |
stat(f2, &b2) == 0 && |
stat(f2, &b2) == 0 && |
330 |
b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); |
b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); |
331 |
} |
} |
332 |
|
*/ |
333 |
|
|
334 |
|
|
335 |
|
static enum token check_operator(char *s) |
336 |
|
{ |
337 |
|
static const struct operator_t no_op = { |
338 |
|
.op_num = -1, |
339 |
|
.op_type = -1 |
340 |
|
}; |
341 |
|
const struct operator_t *op; |
342 |
|
|
343 |
|
last_operator = &no_op; |
344 |
|
if (s == NULL) { |
345 |
|
return EOI; |
346 |
|
} |
347 |
|
|
348 |
|
op = ops; |
349 |
|
do { |
350 |
|
if (strcmp(s, op->op_text) == 0) { |
351 |
|
last_operator = op; |
352 |
|
return op->op_num; |
353 |
|
} |
354 |
|
op++; |
355 |
|
} while (op < ops + ARRAY_SIZE(ops)); |
356 |
|
|
357 |
|
return OPERAND; |
358 |
|
} |
359 |
|
|
360 |
|
|
361 |
|
static int binop(void) |
362 |
|
{ |
363 |
|
const char *opnd1, *opnd2; |
364 |
|
const struct operator_t *op; |
365 |
|
number_t val1, val2; |
366 |
|
|
367 |
|
opnd1 = *args; |
368 |
|
check_operator(*++args); |
369 |
|
op = last_operator; |
370 |
|
|
371 |
|
opnd2 = *++args; |
372 |
|
if (opnd2 == NULL) |
373 |
|
syntax(op->op_text, "argument expected"); |
374 |
|
|
375 |
|
if (is_int_op(op->op_num)) { |
376 |
|
val1 = getn(opnd1); |
377 |
|
val2 = getn(opnd2); |
378 |
|
if (op->op_num == INTEQ) |
379 |
|
return val1 == val2; |
380 |
|
if (op->op_num == INTNE) |
381 |
|
return val1 != val2; |
382 |
|
if (op->op_num == INTGE) |
383 |
|
return val1 >= val2; |
384 |
|
if (op->op_num == INTGT) |
385 |
|
return val1 > val2; |
386 |
|
if (op->op_num == INTLE) |
387 |
|
return val1 <= val2; |
388 |
|
if (op->op_num == INTLT) |
389 |
|
return val1 < val2; |
390 |
|
} |
391 |
|
if (is_str_op(op->op_num)) { |
392 |
|
val1 = strcmp(opnd1, opnd2); |
393 |
|
if (op->op_num == STREQ) |
394 |
|
return val1 == 0; |
395 |
|
if (op->op_num == STRNE) |
396 |
|
return val1 != 0; |
397 |
|
if (op->op_num == STRLT) |
398 |
|
return val1 < 0; |
399 |
|
if (op->op_num == STRGT) |
400 |
|
return val1 > 0; |
401 |
|
} |
402 |
|
/* We are sure that these three are by now the only binops we didn't check |
403 |
|
* yet, so we do not check if the class is correct: |
404 |
|
*/ |
405 |
|
/* if (is_file_op(op->op_num)) */ |
406 |
|
{ |
407 |
|
struct stat b1, b2; |
408 |
|
|
409 |
|
if (stat(opnd1, &b1) || stat(opnd2, &b2)) |
410 |
|
return 0; /* false, since at least one stat failed */ |
411 |
|
if (op->op_num == FILNT) |
412 |
|
return b1.st_mtime > b2.st_mtime; |
413 |
|
if (op->op_num == FILOT) |
414 |
|
return b1.st_mtime < b2.st_mtime; |
415 |
|
if (op->op_num == FILEQ) |
416 |
|
return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino; |
417 |
|
} |
418 |
|
return 1; /* NOTREACHED */ |
419 |
|
} |
420 |
|
|
421 |
|
|
422 |
|
static void initialize_group_array(void) |
423 |
|
{ |
424 |
|
ngroups = getgroups(0, NULL); |
425 |
|
if (ngroups > 0) { |
426 |
|
/* FIXME: ash tries so hard to not die on OOM, |
427 |
|
* and we spoil it with just one xrealloc here */ |
428 |
|
/* We realloc, because test_main can be entered repeatedly by shell. |
429 |
|
* Testcase (ash): 'while true; do test -x some_file; done' |
430 |
|
* and watch top. (some_file must have owner != you) */ |
431 |
|
group_array = xrealloc(group_array, ngroups * sizeof(gid_t)); |
432 |
|
getgroups(ngroups, group_array); |
433 |
|
} |
434 |
|
} |
435 |
|
|
436 |
|
|
437 |
|
/* Return non-zero if GID is one that we have in our groups list. */ |
438 |
|
//XXX: FIXME: duplicate of existing libbb function? |
439 |
|
// see toplevel TODO file: |
440 |
|
// possible code duplication ingroup() and is_a_group_member() |
441 |
|
static int is_a_group_member(gid_t gid) |
442 |
|
{ |
443 |
|
int i; |
444 |
|
|
445 |
|
/* Short-circuit if possible, maybe saving a call to getgroups(). */ |
446 |
|
if (gid == getgid() || gid == getegid()) |
447 |
|
return 1; |
448 |
|
|
449 |
|
if (ngroups == 0) |
450 |
|
initialize_group_array(); |
451 |
|
|
452 |
|
/* Search through the list looking for GID. */ |
453 |
|
for (i = 0; i < ngroups; i++) |
454 |
|
if (gid == group_array[i]) |
455 |
|
return 1; |
456 |
|
|
457 |
|
return 0; |
458 |
|
} |
459 |
|
|
460 |
|
|
461 |
/* Do the same thing access(2) does, but use the effective uid and gid, |
/* Do the same thing access(2) does, but use the effective uid and gid, |
462 |
and don't make the mistake of telling root that any file is |
and don't make the mistake of telling root that any file is |
491 |
return -1; |
return -1; |
492 |
} |
} |
493 |
|
|
494 |
static void initialize_group_array(void) |
|
495 |
|
static int filstat(char *nm, enum token mode) |
496 |
{ |
{ |
497 |
ngroups = getgroups(0, NULL); |
struct stat s; |
498 |
if (ngroups > 0) { |
unsigned i = i; /* gcc 3.x thinks it can be used uninitialized */ |
499 |
group_array = xmalloc(ngroups * sizeof(gid_t)); |
|
500 |
getgroups(ngroups, group_array); |
if (mode == FILSYM) { |
501 |
|
#ifdef S_IFLNK |
502 |
|
if (lstat(nm, &s) == 0) { |
503 |
|
i = S_IFLNK; |
504 |
|
goto filetype; |
505 |
|
} |
506 |
|
#endif |
507 |
|
return 0; |
508 |
} |
} |
509 |
|
|
510 |
|
if (stat(nm, &s) != 0) |
511 |
|
return 0; |
512 |
|
if (mode == FILEXIST) |
513 |
|
return 1; |
514 |
|
if (is_file_access(mode)) { |
515 |
|
if (mode == FILRD) |
516 |
|
i = R_OK; |
517 |
|
if (mode == FILWR) |
518 |
|
i = W_OK; |
519 |
|
if (mode == FILEX) |
520 |
|
i = X_OK; |
521 |
|
return test_eaccess(nm, i) == 0; |
522 |
|
} |
523 |
|
if (is_file_type(mode)) { |
524 |
|
if (mode == FILREG) |
525 |
|
i = S_IFREG; |
526 |
|
if (mode == FILDIR) |
527 |
|
i = S_IFDIR; |
528 |
|
if (mode == FILCDEV) |
529 |
|
i = S_IFCHR; |
530 |
|
if (mode == FILBDEV) |
531 |
|
i = S_IFBLK; |
532 |
|
if (mode == FILFIFO) { |
533 |
|
#ifdef S_IFIFO |
534 |
|
i = S_IFIFO; |
535 |
|
#else |
536 |
|
return 0; |
537 |
|
#endif |
538 |
|
} |
539 |
|
if (mode == FILSOCK) { |
540 |
|
#ifdef S_IFSOCK |
541 |
|
i = S_IFSOCK; |
542 |
|
#else |
543 |
|
return 0; |
544 |
|
#endif |
545 |
|
} |
546 |
|
filetype: |
547 |
|
return ((s.st_mode & S_IFMT) == i); |
548 |
|
} |
549 |
|
if (is_file_bit(mode)) { |
550 |
|
if (mode == FILSUID) |
551 |
|
i = S_ISUID; |
552 |
|
if (mode == FILSGID) |
553 |
|
i = S_ISGID; |
554 |
|
if (mode == FILSTCK) |
555 |
|
i = S_ISVTX; |
556 |
|
return ((s.st_mode & i) != 0); |
557 |
|
} |
558 |
|
if (mode == FILGZ) |
559 |
|
return s.st_size > 0L; |
560 |
|
if (mode == FILUID) |
561 |
|
return s.st_uid == geteuid(); |
562 |
|
if (mode == FILGID) |
563 |
|
return s.st_gid == getegid(); |
564 |
|
return 1; /* NOTREACHED */ |
565 |
} |
} |
566 |
|
|
567 |
/* Return non-zero if GID is one that we have in our groups list. */ |
|
568 |
static int is_a_group_member(gid_t gid) |
static number_t nexpr(enum token n) |
569 |
{ |
{ |
570 |
int i; |
number_t res; |
571 |
|
|
572 |
/* Short-circuit if possible, maybe saving a call to getgroups(). */ |
nest_msg(">nexpr(%s)\n", TOKSTR[n]); |
573 |
if (gid == getgid() || gid == getegid()) |
if (n == UNOT) { |
574 |
return 1; |
res = !nexpr(check_operator(*++args)); |
575 |
|
unnest_msg("<nexpr:%lld\n", res); |
576 |
|
return res; |
577 |
|
} |
578 |
|
res = primary(n); |
579 |
|
unnest_msg("<nexpr:%lld\n", res); |
580 |
|
return res; |
581 |
|
} |
582 |
|
|
|
if (ngroups == 0) |
|
|
initialize_group_array(); |
|
583 |
|
|
584 |
/* Search through the list looking for GID. */ |
static number_t aexpr(enum token n) |
585 |
for (i = 0; i < ngroups; i++) |
{ |
586 |
if (gid == group_array[i]) |
number_t res; |
|
return 1; |
|
587 |
|
|
588 |
return 0; |
nest_msg(">aexpr(%s)\n", TOKSTR[n]); |
589 |
|
res = nexpr(n); |
590 |
|
dbg_msg("aexpr: nexpr:%lld, next args:%s\n", res, args[1]); |
591 |
|
if (check_operator(*++args) == BAND) { |
592 |
|
dbg_msg("aexpr: arg is AND, next args:%s\n", args[1]); |
593 |
|
res = aexpr(check_operator(*++args)) && res; |
594 |
|
unnest_msg("<aexpr:%lld\n", res); |
595 |
|
return res; |
596 |
|
} |
597 |
|
args--; |
598 |
|
unnest_msg("<aexpr:%lld, args:%s\n", res, args[0]); |
599 |
|
return res; |
600 |
} |
} |
601 |
|
|
602 |
|
|
603 |
/* applet entry point */ |
static number_t oexpr(enum token n) |
604 |
|
{ |
605 |
|
number_t res; |
606 |
|
|
607 |
int test_main(int argc, char **argv) |
nest_msg(">oexpr(%s)\n", TOKSTR[n]); |
608 |
|
res = aexpr(n); |
609 |
|
dbg_msg("oexpr: aexpr:%lld, next args:%s\n", res, args[1]); |
610 |
|
if (check_operator(*++args) == BOR) { |
611 |
|
dbg_msg("oexpr: next arg is OR, next args:%s\n", args[1]); |
612 |
|
res = oexpr(check_operator(*++args)) || res; |
613 |
|
unnest_msg("<oexpr:%lld\n", res); |
614 |
|
return res; |
615 |
|
} |
616 |
|
args--; |
617 |
|
unnest_msg("<oexpr:%lld, args:%s\n", res, args[0]); |
618 |
|
return res; |
619 |
|
} |
620 |
|
|
621 |
|
|
622 |
|
static number_t primary(enum token n) |
623 |
{ |
{ |
624 |
return bb_test(argc, argv); |
#if TEST_DEBUG |
625 |
|
number_t res = res; /* for compiler */ |
626 |
|
#else |
627 |
|
number_t res; |
628 |
|
#endif |
629 |
|
const struct operator_t *args0_op; |
630 |
|
|
631 |
|
nest_msg(">primary(%s)\n", TOKSTR[n]); |
632 |
|
if (n == EOI) { |
633 |
|
syntax(NULL, "argument expected"); |
634 |
|
} |
635 |
|
if (n == LPAREN) { |
636 |
|
res = oexpr(check_operator(*++args)); |
637 |
|
if (check_operator(*++args) != RPAREN) |
638 |
|
syntax(NULL, "closing paren expected"); |
639 |
|
unnest_msg("<primary:%lld\n", res); |
640 |
|
return res; |
641 |
|
} |
642 |
|
|
643 |
|
/* coreutils 6.9 checks "is args[1] binop and args[2] exist?" first, |
644 |
|
* do the same */ |
645 |
|
args0_op = last_operator; |
646 |
|
/* last_operator = operator at args[1] */ |
647 |
|
if (check_operator(args[1]) != EOI) { /* if args[1] != NULL */ |
648 |
|
if (args[2]) { |
649 |
|
// coreutils also does this: |
650 |
|
// if (args[3] && args[0]="-l" && args[2] is BINOP) |
651 |
|
// return binop(1 /* prepended by -l */); |
652 |
|
if (last_operator->op_type == BINOP) |
653 |
|
unnest_msg_and_return(binop(), "<primary: binop:%lld\n"); |
654 |
|
} |
655 |
|
} |
656 |
|
/* check "is args[0] unop?" second */ |
657 |
|
if (args0_op->op_type == UNOP) { |
658 |
|
/* unary expression */ |
659 |
|
if (args[1] == NULL) |
660 |
|
// syntax(args0_op->op_text, "argument expected"); |
661 |
|
goto check_emptiness; |
662 |
|
args++; |
663 |
|
if (n == STREZ) |
664 |
|
unnest_msg_and_return(args[0][0] == '\0', "<primary:%lld\n"); |
665 |
|
if (n == STRNZ) |
666 |
|
unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n"); |
667 |
|
if (n == FILTT) |
668 |
|
unnest_msg_and_return(isatty(getn(*args)), "<primary: isatty(%s)%lld\n", *args); |
669 |
|
unnest_msg_and_return(filstat(*args, n), "<primary: filstat(%s):%lld\n", *args); |
670 |
|
} |
671 |
|
|
672 |
|
/*check_operator(args[1]); - already done */ |
673 |
|
if (last_operator->op_type == BINOP) { |
674 |
|
/* args[2] is known to be NULL, isn't it bound to fail? */ |
675 |
|
unnest_msg_and_return(binop(), "<primary:%lld\n"); |
676 |
|
} |
677 |
|
check_emptiness: |
678 |
|
unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n"); |
679 |
} |
} |
680 |
|
|
681 |
|
|
682 |
|
int test_main(int argc, char **argv) |
683 |
|
{ |
684 |
|
int res; |
685 |
|
const char *arg0; |
686 |
|
// bool negate = 0; |
687 |
|
|
688 |
|
arg0 = bb_basename(argv[0]); |
689 |
|
if (arg0[0] == '[') { |
690 |
|
--argc; |
691 |
|
if (!arg0[1]) { /* "[" ? */ |
692 |
|
if (NOT_LONE_CHAR(argv[argc], ']')) { |
693 |
|
bb_error_msg("missing ]"); |
694 |
|
return 2; |
695 |
|
} |
696 |
|
} else { /* assuming "[[" */ |
697 |
|
if (strcmp(argv[argc], "]]") != 0) { |
698 |
|
bb_error_msg("missing ]]"); |
699 |
|
return 2; |
700 |
|
} |
701 |
|
} |
702 |
|
argv[argc] = NULL; |
703 |
|
} |
704 |
|
|
705 |
|
/* We must do DEINIT_S() prior to returning */ |
706 |
|
INIT_S(); |
707 |
|
|
708 |
|
res = setjmp(leaving); |
709 |
|
if (res) |
710 |
|
goto ret; |
711 |
|
|
712 |
|
/* resetting ngroups is probably unnecessary. it will |
713 |
|
* force a new call to getgroups(), which prevents using |
714 |
|
* group data fetched during a previous call. but the |
715 |
|
* only way the group data could be stale is if there's |
716 |
|
* been an intervening call to setgroups(), and this |
717 |
|
* isn't likely in the case of a shell. paranoia |
718 |
|
* prevails... |
719 |
|
*/ |
720 |
|
ngroups = 0; |
721 |
|
|
722 |
|
//argc--; |
723 |
|
argv++; |
724 |
|
|
725 |
|
/* Implement special cases from POSIX.2, section 4.62.4 */ |
726 |
|
if (!argv[0]) { /* "test" */ |
727 |
|
res = 1; |
728 |
|
goto ret; |
729 |
|
} |
730 |
|
#if 0 |
731 |
|
// Now it's fixed in the parser and should not be needed |
732 |
|
if (LONE_CHAR(argv[0], '!') && argv[1]) { |
733 |
|
negate = 1; |
734 |
|
//argc--; |
735 |
|
argv++; |
736 |
|
} |
737 |
|
if (!argv[1]) { /* "test [!] arg" */ |
738 |
|
res = (*argv[0] == '\0'); |
739 |
|
goto ret; |
740 |
|
} |
741 |
|
if (argv[2] && !argv[3]) { |
742 |
|
check_operator(argv[1]); |
743 |
|
if (last_operator->op_type == BINOP) { |
744 |
|
/* "test [!] arg1 <binary_op> arg2" */ |
745 |
|
args = &argv[0]; |
746 |
|
res = (binop() == 0); |
747 |
|
goto ret; |
748 |
|
} |
749 |
|
} |
750 |
|
|
751 |
|
/* Some complex expression. Undo '!' removal */ |
752 |
|
if (negate) { |
753 |
|
negate = 0; |
754 |
|
//argc++; |
755 |
|
argv--; |
756 |
|
} |
757 |
|
#endif |
758 |
|
args = &argv[0]; |
759 |
|
res = !oexpr(check_operator(*args)); |
760 |
|
|
761 |
|
if (*args != NULL && *++args != NULL) { |
762 |
|
/* TODO: example when this happens? */ |
763 |
|
bb_error_msg("%s: unknown operand", *args); |
764 |
|
res = 2; |
765 |
|
} |
766 |
|
ret: |
767 |
|
DEINIT_S(); |
768 |
|
// return negate ? !res : res; |
769 |
|
return res; |
770 |
|
} |