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