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