toybox/toys/posix/test.c
<<
>>
Prefs
   1/* test.c - evaluate expression
   2 *
   3 * Copyright 2018 Rob Landley <rob@landley.net>
   4 *
   5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
   6
   7USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
   8USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
   9
  10config TEST
  11  bool "test"
  12  default y
  13  help
  14    usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
  15
  16    Return true or false by performing tests. (With no arguments return false.)
  17
  18    --- Tests with a single argument (after the option):
  19    PATH is/has:
  20      -b  block device   -f  regular file   -p  fifo           -u  setuid bit
  21      -c  char device    -g  setgid         -r  read bit       -w  write bit
  22      -d  directory      -h  symlink        -S  socket         -x  execute bit
  23      -e  exists         -L  symlink        -s  nonzero size
  24    STRING is:
  25      -n  nonzero size   -z  zero size      (STRING by itself implies -n)
  26    FD (integer file descriptor) is:
  27      -t  a TTY
  28
  29    --- Tests with one argument on each side of an operator:
  30    Two strings:
  31      =  are identical   !=  differ
  32    Two integers:
  33      -eq  equal         -gt  first > second    -lt  first < second
  34      -ne  not equal     -ge  first >= second   -le  first <= second
  35
  36    --- Modify or combine tests:
  37      ! EXPR     not (swap true/false)   EXPR -a EXPR    and (are both true)
  38      ( EXPR )   evaluate this first     EXPR -o EXPR    or (is either true)
  39*/
  40
  41#include "toys.h"
  42
  43// Consume 3, 2, or 1 argument test, returning result and *count used.
  44int do_test(char **args, int *count)
  45{
  46  char c, *s;
  47  int i;
  48
  49  if (*count>=3) {
  50    *count = 3;
  51    char *s = args[1], *ss = "eqnegtgeltle";
  52    if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
  53    if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
  54    if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
  55      long long a = atolx(args[0]), b = atolx(args[2]);
  56
  57      if (!i) return a == b;
  58      if (i==2) return a != b;
  59      if (i==4) return a > b;
  60      if (i==6) return a >= b;
  61      if (i==8) return a < b;
  62      if (i==10) return a<= b;
  63    }
  64  }
  65  s = *args;
  66  if (*count>=2 && *s == '-' && s[1] && !s[2]) {
  67    *count = 2;
  68    c = s[1];
  69    if (-1 != (i = stridx("hLbcdefgpSusxwr", c))) {
  70      struct stat st;
  71
  72      // stat or lstat, then handle rwx and s
  73      if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
  74      if (i>=12) return !!(st.st_mode&(0x111<<(i-12)));
  75      if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
  76
  77      // handle file type checking and SUID/SGID
  78      if ((i = (unsigned short []){80,80,48,16,32,0,64,2,8,96,4}[i]<<9)>=4096)
  79        return (st.st_mode&S_IFMT) == i;
  80      else return (st.st_mode & i) == i;
  81    } else if (c == 'z') return !*args[1];
  82    else if (c == 'n') return *args[1];
  83    else if (c == 't') return isatty(atolx(args[1]));
  84  }
  85  return *count = 0;
  86}
  87
  88#define NOT 1  // Most recent test had an odd number of preceding !
  89#define AND 2  // test before -a failed since -o or ( so force false
  90#define OR  4  // test before -o succeeded since ( so force true
  91void test_main(void)
  92{
  93  char *s;
  94  int pos, paren, pstack, result = 0;
  95
  96  toys.exitval = 2;
  97  if (!strcmp("[", toys.which->name))
  98    if (!toys.optc || strcmp("]", toys.optargs[--toys.optc]))
  99      error_exit("Missing ']'");
 100
 101  // loop through command line arguments
 102  if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
 103    int len = toys.optc-pos;
 104
 105    if (!toys.optargs[pos]) perror_exit("need arg @%d", pos);
 106
 107    // Evaluate next test
 108    result = do_test(toys.optargs+pos, &len);
 109    pos += len;
 110    // Single argument could be ! ( or nonempty
 111    if (!len) {
 112      if (toys.optargs[pos+1]) {
 113        if (!strcmp("!", toys.optargs[pos])) {
 114          pstack ^= NOT;
 115          continue;
 116        }
 117        if (!strcmp("(", toys.optargs[pos])) {
 118          if (++paren>9) perror_exit("bad (");
 119          pstack <<= 3;
 120          continue;
 121        }
 122      }
 123      result = *toys.optargs[pos++];
 124    }
 125    s = toys.optargs[pos];
 126    for (;;) {
 127
 128      // Handle pending ! -a -o (the else means -o beats -a)
 129      if (pstack&NOT) result = !result;
 130      pstack &= ~NOT;
 131      if (pstack&OR) result = 1;
 132      else if (pstack&AND) result = 0;
 133
 134      // Do it again for every )
 135      if (!paren || !s || strcmp(")", s)) break;
 136      paren--;
 137      pstack >>= 3;
 138      s = toys.optargs[++pos];
 139    }
 140
 141    // Out of arguments?
 142    if (!s) {
 143      if (paren) perror_exit("need )");
 144      break;
 145    }
 146
 147    // are we followed by -a or -o?
 148
 149    if (!strcmp("-a", s)) {
 150      if (!result) pstack |= AND;
 151    } else if (!strcmp("-o", s)) {
 152      // -o flushes -a even if previous test was false
 153      pstack &=~AND;
 154      if (result) pstack |= OR;
 155    } else error_exit("too many arguments");
 156  }
 157
 158  // Invert C logic to get shell logic
 159  toys.exitval = !result;
 160}
 161