inetd: comment tweak. no code changes
[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 token_types {
226         UNOP,
227         BINOP,
228         BUNOP,
229         BBINOP,
230         PAREN
231 };
232
233 struct operator_t {
234         char op_text[4];
235         unsigned char op_num, op_type;
236 };
237
238 static const struct operator_t ops[] = {
239         { "-r", FILRD   , UNOP   },
240         { "-w", FILWR   , UNOP   },
241         { "-x", FILEX   , UNOP   },
242         { "-e", FILEXIST, UNOP   },
243         { "-f", FILREG  , UNOP   },
244         { "-d", FILDIR  , UNOP   },
245         { "-c", FILCDEV , UNOP   },
246         { "-b", FILBDEV , UNOP   },
247         { "-p", FILFIFO , UNOP   },
248         { "-u", FILSUID , UNOP   },
249         { "-g", FILSGID , UNOP   },
250         { "-k", FILSTCK , UNOP   },
251         { "-s", FILGZ   , UNOP   },
252         { "-t", FILTT   , UNOP   },
253         { "-z", STREZ   , UNOP   },
254         { "-n", STRNZ   , UNOP   },
255         { "-h", FILSYM  , UNOP   },    /* for backwards compat */
256
257         { "-O" , FILUID , UNOP   },
258         { "-G" , FILGID , UNOP   },
259         { "-L" , FILSYM , UNOP   },
260         { "-S" , FILSOCK, UNOP   },
261         { "="  , STREQ  , BINOP  },
262         { "==" , STREQ  , BINOP  },
263         { "!=" , STRNE  , BINOP  },
264         { "<"  , STRLT  , BINOP  },
265         { ">"  , STRGT  , BINOP  },
266         { "-eq", INTEQ  , BINOP  },
267         { "-ne", INTNE  , BINOP  },
268         { "-ge", INTGE  , BINOP  },
269         { "-gt", INTGT  , BINOP  },
270         { "-le", INTLE  , BINOP  },
271         { "-lt", INTLT  , BINOP  },
272         { "-nt", FILNT  , BINOP  },
273         { "-ot", FILOT  , BINOP  },
274         { "-ef", FILEQ  , BINOP  },
275         { "!"  , UNOT   , BUNOP  },
276         { "-a" , BAND   , BBINOP },
277         { "-o" , BOR    , BBINOP },
278         { "("  , LPAREN , PAREN  },
279         { ")"  , RPAREN , PAREN  },
280 };
281
282
283 #if ENABLE_FEATURE_TEST_64
284 typedef int64_t number_t;
285 #else
286 typedef int number_t;
287 #endif
288
289
290 /* We try to minimize both static and stack usage. */
291 struct test_statics {
292         char **args;
293         /* set only by check_operator(), either to bogus struct
294          * or points to matching operator_t struct. Never NULL. */
295         const struct operator_t *last_operator;
296         gid_t *group_array;
297         int ngroups;
298         jmp_buf leaving;
299 };
300
301 /* See test_ptr_hack.c */
302 extern struct test_statics *const test_ptr_to_statics;
303
304 #define S (*test_ptr_to_statics)
305 #define args            (S.args         )
306 #define last_operator   (S.last_operator)
307 #define group_array     (S.group_array  )
308 #define ngroups         (S.ngroups      )
309 #define leaving         (S.leaving      )
310
311 #define INIT_S() do { \
312         (*(struct test_statics**)&test_ptr_to_statics) = xzalloc(sizeof(S)); \
313         barrier(); \
314 } while (0)
315 #define DEINIT_S() do { \
316         free(test_ptr_to_statics); \
317 } while (0)
318
319 static number_t primary(enum token n);
320
321 static void syntax(const char *op, const char *msg) NORETURN;
322 static void syntax(const char *op, const char *msg)
323 {
324         if (op && *op) {
325                 bb_error_msg("%s: %s", op, msg);
326         } else {
327                 bb_error_msg("%s: %s"+4, msg);
328         }
329         longjmp(leaving, 2);
330 }
331
332 /* atoi with error detection */
333 //XXX: FIXME: duplicate of existing libbb function?
334 static number_t getn(const char *s)
335 {
336         char *p;
337 #if ENABLE_FEATURE_TEST_64
338         long long r;
339 #else
340         long r;
341 #endif
342
343         errno = 0;
344 #if ENABLE_FEATURE_TEST_64
345         r = strtoll(s, &p, 10);
346 #else
347         r = strtol(s, &p, 10);
348 #endif
349
350         if (errno != 0)
351                 syntax(s, "out of range");
352
353         if (*(skip_whitespace(p)))
354                 syntax(s, "bad number");
355
356         return r;
357 }
358
359 /* UNUSED
360 static int newerf(const char *f1, const char *f2)
361 {
362         struct stat b1, b2;
363
364         return (stat(f1, &b1) == 0 &&
365                         stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime);
366 }
367
368 static int olderf(const char *f1, const char *f2)
369 {
370         struct stat b1, b2;
371
372         return (stat(f1, &b1) == 0 &&
373                         stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime);
374 }
375
376 static int equalf(const char *f1, const char *f2)
377 {
378         struct stat b1, b2;
379
380         return (stat(f1, &b1) == 0 &&
381                         stat(f2, &b2) == 0 &&
382                         b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
383 }
384 */
385
386
387 static enum token check_operator(char *s)
388 {
389         static const struct operator_t no_op = {
390                 .op_num = -1,
391                 .op_type = -1
392         };
393         const struct operator_t *op;
394
395         last_operator = &no_op;
396         if (s == NULL) {
397                 return EOI;
398         }
399
400         op = ops;
401         do {
402                 if (strcmp(s, op->op_text) == 0) {
403                         last_operator = op;
404                         return op->op_num;
405                 }
406                 op++;
407         } while (op < ops + ARRAY_SIZE(ops));
408
409         return OPERAND;
410 }
411
412
413 static int binop(void)
414 {
415         const char *opnd1, *opnd2;
416         const struct operator_t *op;
417         number_t val1, val2;
418
419         opnd1 = *args;
420         check_operator(*++args);
421         op = last_operator;
422
423         opnd2 = *++args;
424         if (opnd2 == NULL)
425                 syntax(op->op_text, "argument expected");
426
427         if (is_int_op(op->op_num)) {
428                 val1 = getn(opnd1);
429                 val2 = getn(opnd2);
430                 if (op->op_num == INTEQ)
431                         return val1 == val2;
432                 if (op->op_num == INTNE)
433                         return val1 != val2;
434                 if (op->op_num == INTGE)
435                         return val1 >= val2;
436                 if (op->op_num == INTGT)
437                         return val1 >  val2;
438                 if (op->op_num == INTLE)
439                         return val1 <= val2;
440                 /*if (op->op_num == INTLT)*/
441                 return val1 <  val2;
442         }
443         if (is_str_op(op->op_num)) {
444                 val1 = strcmp(opnd1, opnd2);
445                 if (op->op_num == STREQ)
446                         return val1 == 0;
447                 if (op->op_num == STRNE)
448                         return val1 != 0;
449                 if (op->op_num == STRLT)
450                         return val1 < 0;
451                 /*if (op->op_num == STRGT)*/
452                 return val1 > 0;
453         }
454         /* We are sure that these three are by now the only binops we didn't check
455          * yet, so we do not check if the class is correct:
456          */
457 /*      if (is_file_op(op->op_num)) */
458         {
459                 struct stat b1, b2;
460
461                 if (stat(opnd1, &b1) || stat(opnd2, &b2))
462                         return 0; /* false, since at least one stat failed */
463                 if (op->op_num == FILNT)
464                         return b1.st_mtime > b2.st_mtime;
465                 if (op->op_num == FILOT)
466                         return b1.st_mtime < b2.st_mtime;
467                 /*if (op->op_num == FILEQ)*/
468                 return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
469         }
470         /*return 1; - NOTREACHED */
471 }
472
473
474 static void initialize_group_array(void)
475 {
476         int n;
477
478         /* getgroups may be expensive, try to use it only once */
479         ngroups = 32;
480         do {
481                 /* FIXME: ash tries so hard to not die on OOM,
482                  * and we spoil it with just one xrealloc here */
483                 /* We realloc, because test_main can be entered repeatedly by shell.
484                  * Testcase (ash): 'while true; do test -x some_file; done'
485                  * and watch top. (some_file must have owner != you) */
486                 n = ngroups;
487                 group_array = xrealloc(group_array, n * sizeof(gid_t));
488                 ngroups = getgroups(n, group_array);
489         } while (ngroups > n);
490 }
491
492
493 /* Return non-zero if GID is one that we have in our groups list. */
494 //XXX: FIXME: duplicate of existing libbb function?
495 // see toplevel TODO file:
496 // possible code duplication ingroup() and is_a_group_member()
497 static int is_a_group_member(gid_t gid)
498 {
499         int i;
500
501         /* Short-circuit if possible, maybe saving a call to getgroups(). */
502         if (gid == getgid() || gid == getegid())
503                 return 1;
504
505         if (ngroups == 0)
506                 initialize_group_array();
507
508         /* Search through the list looking for GID. */
509         for (i = 0; i < ngroups; i++)
510                 if (gid == group_array[i])
511                         return 1;
512
513         return 0;
514 }
515
516
517 /* Do the same thing access(2) does, but use the effective uid and gid,
518    and don't make the mistake of telling root that any file is
519    executable. */
520 static int test_eaccess(char *path, int mode)
521 {
522         struct stat st;
523         unsigned int euid = geteuid();
524
525         if (stat(path, &st) < 0)
526                 return -1;
527
528         if (euid == 0) {
529                 /* Root can read or write any file. */
530                 if (mode != X_OK)
531                         return 0;
532
533                 /* Root can execute any file that has any one of the execute
534                    bits set. */
535                 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
536                         return 0;
537         }
538
539         if (st.st_uid == euid)  /* owner */
540                 mode <<= 6;
541         else if (is_a_group_member(st.st_gid))
542                 mode <<= 3;
543
544         if (st.st_mode & mode)
545                 return 0;
546
547         return -1;
548 }
549
550
551 static int filstat(char *nm, enum token mode)
552 {
553         struct stat s;
554         unsigned i = i; /* gcc 3.x thinks it can be used uninitialized */
555
556         if (mode == FILSYM) {
557 #ifdef S_IFLNK
558                 if (lstat(nm, &s) == 0) {
559                         i = S_IFLNK;
560                         goto filetype;
561                 }
562 #endif
563                 return 0;
564         }
565
566         if (stat(nm, &s) != 0)
567                 return 0;
568         if (mode == FILEXIST)
569                 return 1;
570         if (is_file_access(mode)) {
571                 if (mode == FILRD)
572                         i = R_OK;
573                 if (mode == FILWR)
574                         i = W_OK;
575                 if (mode == FILEX)
576                         i = X_OK;
577                 return test_eaccess(nm, i) == 0;
578         }
579         if (is_file_type(mode)) {
580                 if (mode == FILREG)
581                         i = S_IFREG;
582                 if (mode == FILDIR)
583                         i = S_IFDIR;
584                 if (mode == FILCDEV)
585                         i = S_IFCHR;
586                 if (mode == FILBDEV)
587                         i = S_IFBLK;
588                 if (mode == FILFIFO) {
589 #ifdef S_IFIFO
590                         i = S_IFIFO;
591 #else
592                         return 0;
593 #endif
594                 }
595                 if (mode == FILSOCK) {
596 #ifdef S_IFSOCK
597                         i = S_IFSOCK;
598 #else
599                         return 0;
600 #endif
601                 }
602  filetype:
603                 return ((s.st_mode & S_IFMT) == i);
604         }
605         if (is_file_bit(mode)) {
606                 if (mode == FILSUID)
607                         i = S_ISUID;
608                 if (mode == FILSGID)
609                         i = S_ISGID;
610                 if (mode == FILSTCK)
611                         i = S_ISVTX;
612                 return ((s.st_mode & i) != 0);
613         }
614         if (mode == FILGZ)
615                 return s.st_size > 0L;
616         if (mode == FILUID)
617                 return s.st_uid == geteuid();
618         if (mode == FILGID)
619                 return s.st_gid == getegid();
620         return 1; /* NOTREACHED */
621 }
622
623
624 static number_t nexpr(enum token n)
625 {
626         number_t res;
627
628         nest_msg(">nexpr(%s)\n", TOKSTR[n]);
629         if (n == UNOT) {
630                 n = check_operator(*++args);
631                 if (n == EOI) {
632                         /* special case: [ ! ], [ a -a ! ] are valid */
633                         /* IOW, "! ARG" may miss ARG */
634                         unnest_msg("<nexpr:1 (!EOI)\n");
635                         return 1;
636                 }
637                 res = !nexpr(n);
638                 unnest_msg("<nexpr:%lld\n", res);
639                 return res;
640         }
641         res = primary(n);
642         unnest_msg("<nexpr:%lld\n", res);
643         return res;
644 }
645
646
647 static number_t aexpr(enum token n)
648 {
649         number_t res;
650
651         nest_msg(">aexpr(%s)\n", TOKSTR[n]);
652         res = nexpr(n);
653         dbg_msg("aexpr: nexpr:%lld, next args:%s\n", res, args[1]);
654         if (check_operator(*++args) == BAND) {
655                 dbg_msg("aexpr: arg is AND, next args:%s\n", args[1]);
656                 res = aexpr(check_operator(*++args)) && res;
657                 unnest_msg("<aexpr:%lld\n", res);
658                 return res;
659         }
660         args--;
661         unnest_msg("<aexpr:%lld, args:%s\n", res, args[0]);
662         return res;
663 }
664
665
666 static number_t oexpr(enum token n)
667 {
668         number_t res;
669
670         nest_msg(">oexpr(%s)\n", TOKSTR[n]);
671         res = aexpr(n);
672         dbg_msg("oexpr: aexpr:%lld, next args:%s\n", res, args[1]);
673         if (check_operator(*++args) == BOR) {
674                 dbg_msg("oexpr: next arg is OR, next args:%s\n", args[1]);
675                 res = oexpr(check_operator(*++args)) || res;
676                 unnest_msg("<oexpr:%lld\n", res);
677                 return res;
678         }
679         args--;
680         unnest_msg("<oexpr:%lld, args:%s\n", res, args[0]);
681         return res;
682 }
683
684
685 static number_t primary(enum token n)
686 {
687 #if TEST_DEBUG
688         number_t res = res; /* for compiler */
689 #else
690         number_t res;
691 #endif
692         const struct operator_t *args0_op;
693
694         nest_msg(">primary(%s)\n", TOKSTR[n]);
695         if (n == EOI) {
696                 syntax(NULL, "argument expected");
697         }
698         if (n == LPAREN) {
699                 res = oexpr(check_operator(*++args));
700                 if (check_operator(*++args) != RPAREN)
701                         syntax(NULL, "closing paren expected");
702                 unnest_msg("<primary:%lld\n", res);
703                 return res;
704         }
705
706         /* coreutils 6.9 checks "is args[1] binop and args[2] exist?" first,
707          * do the same */
708         args0_op = last_operator;
709         /* last_operator = operator at args[1] */
710         if (check_operator(args[1]) != EOI) { /* if args[1] != NULL */
711                 if (args[2]) {
712                         // coreutils also does this:
713                         // if (args[3] && args[0]="-l" && args[2] is BINOP)
714                         //      return binop(1 /* prepended by -l */);
715                         if (last_operator->op_type == BINOP)
716                                 unnest_msg_and_return(binop(), "<primary: binop:%lld\n");
717                 }
718         }
719         /* check "is args[0] unop?" second */
720         if (args0_op->op_type == UNOP) {
721                 /* unary expression */
722                 if (args[1] == NULL)
723 //                      syntax(args0_op->op_text, "argument expected");
724                         goto check_emptiness;
725                 args++;
726                 if (n == STREZ)
727                         unnest_msg_and_return(args[0][0] == '\0', "<primary:%lld\n");
728                 if (n == STRNZ)
729                         unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
730                 if (n == FILTT)
731                         unnest_msg_and_return(isatty(getn(*args)), "<primary: isatty(%s)%lld\n", *args);
732                 unnest_msg_and_return(filstat(*args, n), "<primary: filstat(%s):%lld\n", *args);
733         }
734
735         /*check_operator(args[1]); - already done */
736         if (last_operator->op_type == BINOP) {
737                 /* args[2] is known to be NULL, isn't it bound to fail? */
738                 unnest_msg_and_return(binop(), "<primary:%lld\n");
739         }
740  check_emptiness:
741         unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
742 }
743
744
745 int test_main(int argc, char **argv)
746 {
747         int res;
748         const char *arg0;
749 //      bool negate = 0;
750
751         arg0 = bb_basename(argv[0]);
752         if (arg0[0] == '[') {
753                 --argc;
754                 if (!arg0[1]) { /* "[" ? */
755                         if (NOT_LONE_CHAR(argv[argc], ']')) {
756                                 bb_error_msg("missing ]");
757                                 return 2;
758                         }
759                 } else { /* assuming "[[" */
760                         if (strcmp(argv[argc], "]]") != 0) {
761                                 bb_error_msg("missing ]]");
762                                 return 2;
763                         }
764                 }
765                 argv[argc] = NULL;
766         }
767
768         /* We must do DEINIT_S() prior to returning */
769         INIT_S();
770
771         res = setjmp(leaving);
772         if (res)
773                 goto ret;
774
775         /* resetting ngroups is probably unnecessary.  it will
776          * force a new call to getgroups(), which prevents using
777          * group data fetched during a previous call.  but the
778          * only way the group data could be stale is if there's
779          * been an intervening call to setgroups(), and this
780          * isn't likely in the case of a shell.  paranoia
781          * prevails...
782          */
783         /*ngroups = 0; - done by INIT_S() */
784
785         //argc--;
786         argv++;
787
788         /* Implement special cases from POSIX.2, section 4.62.4 */
789         if (!argv[0]) { /* "test" */
790                 res = 1;
791                 goto ret;
792         }
793 #if 0
794 // Now it's fixed in the parser and should not be needed
795         if (LONE_CHAR(argv[0], '!') && argv[1]) {
796                 negate = 1;
797                 //argc--;
798                 argv++;
799         }
800         if (!argv[1]) { /* "test [!] arg" */
801                 res = (*argv[0] == '\0');
802                 goto ret;
803         }
804         if (argv[2] && !argv[3]) {
805                 check_operator(argv[1]);
806                 if (last_operator->op_type == BINOP) {
807                         /* "test [!] arg1 <binary_op> arg2" */
808                         args = argv;
809                         res = (binop() == 0);
810                         goto ret;
811                 }
812         }
813
814         /* Some complex expression. Undo '!' removal */
815         if (negate) {
816                 negate = 0;
817                 //argc++;
818                 argv--;
819         }
820 #endif
821         args = argv;
822         res = !oexpr(check_operator(*args));
823
824         if (*args != NULL && *++args != NULL) {
825                 /* TODO: example when this happens? */
826                 bb_error_msg("%s: unknown operand", *args);
827                 res = 2;
828         }
829  ret:
830         DEINIT_S();
831 //      return negate ? !res : res;
832         return res;
833 }