aboutsummaryrefslogtreecommitdiff
path: root/busybox/coreutils/test.c
diff options
context:
space:
mode:
Diffstat (limited to 'busybox/coreutils/test.c')
-rw-r--r--busybox/coreutils/test.c559
1 files changed, 559 insertions, 0 deletions
diff --git a/busybox/coreutils/test.c b/busybox/coreutils/test.c
new file mode 100644
index 000000000..8fa6d166f
--- /dev/null
+++ b/busybox/coreutils/test.c
@@ -0,0 +1,559 @@
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 *
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
34#include <sys/types.h>
35#include <unistd.h>
36#include <ctype.h>
37#include <errno.h>
38#include <stdlib.h>
39#include <string.h>
40#include "busybox.h"
41
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
110static const struct t_op {
111 const char *op_text;
112 short op_num, op_type;
113} ops[] = {
114 {
115 "-r", FILRD, UNOP}, {
116 "-w", FILWR, UNOP}, {
117 "-x", FILEX, UNOP}, {
118 "-e", FILEXIST, UNOP}, {
119 "-f", FILREG, UNOP}, {
120 "-d", FILDIR, UNOP}, {
121 "-c", FILCDEV, UNOP}, {
122 "-b", FILBDEV, UNOP}, {
123 "-p", FILFIFO, UNOP}, {
124 "-u", FILSUID, UNOP}, {
125 "-g", FILSGID, UNOP}, {
126 "-k", FILSTCK, UNOP}, {
127 "-s", FILGZ, UNOP}, {
128 "-t", FILTT, UNOP}, {
129 "-z", STREZ, UNOP}, {
130 "-n", STRNZ, UNOP}, {
131 "-h", FILSYM, UNOP}, /* for backwards compat */
132 {
133 "-O", FILUID, UNOP}, {
134 "-G", FILGID, UNOP}, {
135 "-L", FILSYM, UNOP}, {
136 "-S", FILSOCK, UNOP}, {
137 "=", STREQ, BINOP}, {
138 "!=", STRNE, BINOP}, {
139 "<", STRLT, BINOP}, {
140 ">", STRGT, BINOP}, {
141 "-eq", INTEQ, BINOP}, {
142 "-ne", INTNE, BINOP}, {
143 "-ge", INTGE, BINOP}, {
144 "-gt", INTGT, BINOP}, {
145 "-le", INTLE, BINOP}, {
146 "-lt", INTLT, BINOP}, {
147 "-nt", FILNT, BINOP}, {
148 "-ot", FILOT, BINOP}, {
149 "-ef", FILEQ, BINOP}, {
150 "!", UNOT, BUNOP}, {
151 "-a", BAND, BBINOP}, {
152 "-o", BOR, BBINOP}, {
153 "(", LPAREN, PAREN}, {
154 ")", RPAREN, PAREN}, {
155 0, 0, 0}
156};
157
158#ifdef CONFIG_FEATURE_TEST_64
159typedef int64_t arith_t;
160#else
161typedef int arith_t;
162#endif
163
164static char **t_wp;
165static struct t_op const *t_wp_op;
166static gid_t *group_array = NULL;
167static int ngroups;
168
169static enum token t_lex(char *s);
170static arith_t oexpr(enum token n);
171static arith_t aexpr(enum token n);
172static arith_t nexpr(enum token n);
173static int binop(void);
174static arith_t primary(enum token n);
175static int filstat(char *nm, enum token mode);
176static arith_t getn(const char *s);
177static int newerf(const char *f1, const char *f2);
178static int olderf(const char *f1, const char *f2);
179static int equalf(const char *f1, const char *f2);
180static void syntax(const char *op, const char *msg);
181static int test_eaccess(char *path, int mode);
182static int is_a_group_member(gid_t gid);
183static void initialize_group_array(void);
184
185extern int test_main(int argc, char **argv)
186{
187 int res;
188
189 if (strcmp(bb_applet_name, "[") == 0) {
190 if (strcmp(argv[--argc], "]"))
191 bb_error_msg_and_die("missing ]");
192 argv[argc] = NULL;
193 }
194 /* Implement special cases from POSIX.2, section 4.62.4 */
195 switch (argc) {
196 case 1:
197 exit(1);
198 case 2:
199 exit(*argv[1] == '\0');
200 case 3:
201 if (argv[1][0] == '!' && argv[1][1] == '\0') {
202 exit(!(*argv[2] == '\0'));
203 }
204 break;
205 case 4:
206 if (argv[1][0] != '!' || argv[1][1] != '\0') {
207 if (t_lex(argv[2]), t_wp_op && t_wp_op->op_type == BINOP) {
208 t_wp = &argv[1];
209 exit(binop() == 0);
210 }
211 }
212 break;
213 case 5:
214 if (argv[1][0] == '!' && argv[1][1] == '\0') {
215 if (t_lex(argv[3]), t_wp_op && t_wp_op->op_type == BINOP) {
216 t_wp = &argv[2];
217 exit(!(binop() == 0));
218 }
219 }
220 break;
221 }
222
223 t_wp = &argv[1];
224 res = !oexpr(t_lex(*t_wp));
225
226 if (*t_wp != NULL && *++t_wp != NULL)
227 syntax(*t_wp, "unknown operand");
228
229 return (res);
230}
231
232static void syntax(const char *op, const char *msg)
233{
234 if (op && *op) {
235 bb_error_msg_and_die("%s: %s", op, msg);
236 } else {
237 bb_error_msg_and_die("%s", msg);
238 }
239}
240
241static arith_t oexpr(enum token n)
242{
243 arith_t res;
244
245 res = aexpr(n);
246 if (t_lex(*++t_wp) == BOR) {
247 return oexpr(t_lex(*++t_wp)) || res;
248 }
249 t_wp--;
250 return res;
251}
252
253static arith_t aexpr(enum token n)
254{
255 arith_t 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 arith_t nexpr(enum token n)
265{
266 if (n == UNOT)
267 return !nexpr(t_lex(*++t_wp));
268 return primary(n);
269}
270
271static arith_t primary(enum token n)
272{
273 arith_t res;
274
275 if (n == EOI) {
276 syntax(NULL, "argument expected");
277 }
278 if (n == LPAREN) {
279 res = oexpr(t_lex(*++t_wp));
280 if (t_lex(*++t_wp) != RPAREN)
281 syntax(NULL, "closing paren expected");
282 return res;
283 }
284 if (t_wp_op && t_wp_op->op_type == UNOP) {
285 /* unary expression */
286 if (*++t_wp == NULL)
287 syntax(t_wp_op->op_text, "argument expected");
288 switch (n) {
289 case STREZ:
290 return strlen(*t_wp) == 0;
291 case STRNZ:
292 return strlen(*t_wp) != 0;
293 case FILTT:
294 return isatty(getn(*t_wp));
295 default:
296 return filstat(*t_wp, n);
297 }
298 }
299
300 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
301 return binop();
302 }
303
304 return strlen(*t_wp) > 0;
305}
306
307static int binop()
308{
309 const char *opnd1, *opnd2;
310 struct t_op const *op;
311
312 opnd1 = *t_wp;
313 (void) t_lex(*++t_wp);
314 op = t_wp_op;
315
316 if ((opnd2 = *++t_wp) == (char *) 0)
317 syntax(op->op_text, "argument expected");
318
319 switch (op->op_num) {
320 case STREQ:
321 return strcmp(opnd1, opnd2) == 0;
322 case STRNE:
323 return strcmp(opnd1, opnd2) != 0;
324 case STRLT:
325 return strcmp(opnd1, opnd2) < 0;
326 case STRGT:
327 return strcmp(opnd1, opnd2) > 0;
328 case INTEQ:
329 return getn(opnd1) == getn(opnd2);
330 case INTNE:
331 return getn(opnd1) != getn(opnd2);
332 case INTGE:
333 return getn(opnd1) >= getn(opnd2);
334 case INTGT:
335 return getn(opnd1) > getn(opnd2);
336 case INTLE:
337 return getn(opnd1) <= getn(opnd2);
338 case INTLT:
339 return getn(opnd1) < getn(opnd2);
340 case FILNT:
341 return newerf(opnd1, opnd2);
342 case FILOT:
343 return olderf(opnd1, opnd2);
344 case FILEQ:
345 return equalf(opnd1, opnd2);
346 }
347 /* NOTREACHED */
348 return 1;
349}
350
351static int filstat(char *nm, enum token mode)
352{
353 struct stat s;
354 unsigned int i;
355
356 if (mode == FILSYM) {
357#ifdef S_IFLNK
358 if (lstat(nm, &s) == 0) {
359 i = S_IFLNK;
360 goto filetype;
361 }
362#endif
363 return 0;
364 }
365
366 if (stat(nm, &s) != 0)
367 return 0;
368
369 switch (mode) {
370 case FILRD:
371 return test_eaccess(nm, R_OK) == 0;
372 case FILWR:
373 return test_eaccess(nm, W_OK) == 0;
374 case FILEX:
375 return test_eaccess(nm, X_OK) == 0;
376 case FILEXIST:
377 return 1;
378 case FILREG:
379 i = S_IFREG;
380 goto filetype;
381 case FILDIR:
382 i = S_IFDIR;
383 goto filetype;
384 case FILCDEV:
385 i = S_IFCHR;
386 goto filetype;
387 case FILBDEV:
388 i = S_IFBLK;
389 goto filetype;
390 case FILFIFO:
391#ifdef S_IFIFO
392 i = S_IFIFO;
393 goto filetype;
394#else
395 return 0;
396#endif
397 case FILSOCK:
398#ifdef S_IFSOCK
399 i = S_IFSOCK;
400 goto filetype;
401#else
402 return 0;
403#endif
404 case FILSUID:
405 i = S_ISUID;
406 goto filebit;
407 case FILSGID:
408 i = S_ISGID;
409 goto filebit;
410 case FILSTCK:
411 i = S_ISVTX;
412 goto filebit;
413 case FILGZ:
414 return s.st_size > 0L;
415 case FILUID:
416 return s.st_uid == geteuid();
417 case FILGID:
418 return s.st_gid == getegid();
419 default:
420 return 1;
421 }
422
423 filetype:
424 return ((s.st_mode & S_IFMT) == i);
425
426 filebit:
427 return ((s.st_mode & i) != 0);
428}
429
430static enum token t_lex(char *s)
431{
432 struct t_op const *op = ops;
433
434 if (s == 0) {
435 t_wp_op = (struct t_op *) 0;
436 return EOI;
437 }
438 while (op->op_text) {
439 if (strcmp(s, op->op_text) == 0) {
440 t_wp_op = op;
441 return op->op_num;
442 }
443 op++;
444 }
445 t_wp_op = (struct t_op *) 0;
446 return OPERAND;
447}
448
449/* atoi with error detection */
450static arith_t getn(const char *s)
451{
452 char *p;
453#ifdef CONFIG_FEATURE_TEST_64
454 long long r;
455#else
456 long r;
457#endif
458
459 errno = 0;
460#ifdef CONFIG_FEATURE_TEST_64
461 r = strtoll(s, &p, 10);
462#else
463 r = strtol(s, &p, 10);
464#endif
465
466 if (errno != 0)
467 bb_error_msg_and_die("%s: out of range", s);
468
469 /* p = bb_skip_whitespace(p); avoid const warning */
470 if (*(bb_skip_whitespace(p)))
471 bb_error_msg_and_die("%s: bad number", s);
472
473 return r;
474}
475
476static int newerf(const char *f1, const char *f2)
477{
478 struct stat b1, b2;
479
480 return (stat(f1, &b1) == 0 &&
481 stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime);
482}
483
484static int olderf(const char *f1, const char *f2)
485{
486 struct stat b1, b2;
487
488 return (stat(f1, &b1) == 0 &&
489 stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime);
490}
491
492static int equalf(const char *f1, const char *f2)
493{
494 struct stat b1, b2;
495
496 return (stat(f1, &b1) == 0 &&
497 stat(f2, &b2) == 0 &&
498 b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
499}
500
501/* Do the same thing access(2) does, but use the effective uid and gid,
502 and don't make the mistake of telling root that any file is
503 executable. */
504static int test_eaccess(char *path, int mode)
505{
506 struct stat st;
507 unsigned int euid = geteuid();
508
509 if (stat(path, &st) < 0)
510 return (-1);
511
512 if (euid == 0) {
513 /* Root can read or write any file. */
514 if (mode != X_OK)
515 return (0);
516
517 /* Root can execute any file that has any one of the execute
518 bits set. */
519 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
520 return (0);
521 }
522
523 if (st.st_uid == euid) /* owner */
524 mode <<= 6;
525 else if (is_a_group_member(st.st_gid))
526 mode <<= 3;
527
528 if (st.st_mode & mode)
529 return (0);
530
531 return (-1);
532}
533
534static void initialize_group_array()
535{
536 ngroups = getgroups(0, NULL);
537 group_array = xrealloc(group_array, ngroups * sizeof(gid_t));
538 getgroups(ngroups, group_array);
539}
540
541/* Return non-zero if GID is one that we have in our groups list. */
542static int is_a_group_member(gid_t gid)
543{
544 register int i;
545
546 /* Short-circuit if possible, maybe saving a call to getgroups(). */
547 if (gid == getgid() || gid == getegid())
548 return (1);
549
550 if (ngroups == 0)
551 initialize_group_array();
552
553 /* Search through the list looking for GID. */
554 for (i = 0; i < ngroups; i++)
555 if (gid == group_array[i])
556 return (1);
557
558 return (0);
559}