• Home
  • 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|TOYFLAG_MAYFORK))
8 USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
9 
10 config 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.
do_test(char ** args,int * count)44 int 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
test_main(void)91 void 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