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