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