• 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  * Deviations from posix: -k, [[ < > =~ ]]
8 
9 USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK))
10 USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP))
11 USE_SH(OLDTOY([[, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP))
12 
13 config TEST
14   bool "test"
15   default y
16   help
17     usage: test [-bcdefghkLprSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y]
18 
19     Return true or false by performing tests. No arguments is false, one argument
20     is true if not empty string.
21 
22     --- Tests with a single argument (after the option):
23     PATH is/has:
24       -b  block device   -f  regular file   -p  fifo           -u  setuid bit
25       -c  char device    -g  setgid         -r  readable       -w  writable
26       -d  directory      -h  symlink        -S  socket         -x  executable
27       -e  exists         -L  symlink        -s  nonzero size   -k  sticky bit
28     STRING is:
29       -n  nonzero size   -z  zero size
30     FD (integer file descriptor) is:
31       -t  a TTY
32 
33     --- Tests with one argument on each side of an operator:
34     Two strings:
35       =  are identical   !=  differ         =~  string matches regex
36     Alphabetical sort:
37       <  first is lower  >   first higher
38     Two integers:
39       -eq  equal         -gt  first > second    -lt  first < second
40       -ne  not equal     -ge  first >= second   -le  first <= second
41     Two files:
42       -ot  Older mtime   -nt  Newer mtime       -ef  same dev/inode
43 
44     --- Modify or combine tests:
45       ! EXPR     not (swap true/false)   EXPR -a EXPR    and (are both true)
46       ( EXPR )   evaluate this first     EXPR -o EXPR    or (is either true)
47 
48 config TEST_GLUE
49   bool
50   default y
51   depends on TEST || SH
52 */
53 
54 #include "toys.h"
55 
56 // Consume 3, 2, or 1 argument test, returning result and *count used.
do_test(char ** args,int * count)57 static int do_test(char **args, int *count)
58 {
59   char c, *s;
60   int i;
61 
62   if (*count>=3) {
63     *count = 3;
64     char *s = args[1], *ss = "eqnegtgeltleefotnt";
65     // TODO shell integration case insensitivity
66     if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]);
67     if (!strcmp(s, "!=")) return strcmp(args[0], args[2]);
68     if (!strcmp(s, "=~")) {
69       regex_t reg;
70 
71       // TODO: regex needs integrated quoting support with the shell.
72       // Ala [[ abc =~ "1"* ]] matches but [[ abc =~ 1"*" ]] does not
73       xregcomp(&reg, args[2], REG_NOSUB); // REG_EXTENDED? REG_ICASE?
74       i = regexec(&reg, args[0], 0, 0, 0);
75       regfree(&reg);
76 
77       return !i;
78     }
79     if ((*s=='<' || *s=='>') && !s[1]) {
80       i = strcmp(args[0], args[2]);
81       return (*s=='<') ? i<0 : i>0;
82     }
83     if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) {
84       struct stat st1, st2;
85       long long a QUIET, b QUIET;
86       if (i <= 10) {
87         a = atolx(args[0]);
88         b = atolx(args[2]);
89       } else {
90         if ((i == 12 ? stat : lstat)(args[0], &st1)
91           || (i == 12 ? stat : lstat)(args[2], &st2)) return 0;
92       }
93 
94       if (!i) return a == b;
95       if (i==2) return a != b;
96       if (i==4) return a > b;
97       if (i==6) return a >= b;
98       if (i==8) return a < b;
99       if (i==10) return a<= b;
100       if (i==12) return (st1.st_dev==st2.st_dev) && (st1.st_ino==st2.st_ino);
101       if (i==14) return (st1.st_mtim.tv_sec < st2.st_mtim.tv_sec) ||
102         (st1.st_mtim.tv_sec == st2.st_mtim.tv_sec &&
103         st1.st_mtim.tv_nsec < st2.st_mtim.tv_nsec);
104       if (i==16) return (st1.st_mtim.tv_sec > st2.st_mtim.tv_sec) ||
105         (st1.st_mtim.tv_sec == st2.st_mtim.tv_sec &&
106         st1.st_mtim.tv_nsec > st2.st_mtim.tv_nsec);
107     }
108   }
109   s = *args;
110   if (*count>=2 && *s == '-' && s[1] && !s[2]) {
111     *count = 2;
112     c = s[1];
113     if (c=='a') c = 'e';
114     if (-1 != (i = stridx("hLbcdefgkpSusxwr", c))) {
115       struct stat st;
116 
117       if (i>=13) return !access(args[1], 1<<(i-13));
118       // stat or lstat, check s
119       if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0;
120       if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0
121 
122       // handle file type checking and SUID/SGID
123       if ((i = ((char []){80,80,48,16,32,0,64,2,1,8,96,4}[i])<<9)>=4096)
124         return (st.st_mode&S_IFMT) == i;
125       else return (st.st_mode & i) == i;
126     } else if (c == 'z') return !*args[1];
127     else if (c == 'n') return *args[1];
128     else if (c == 't') return isatty(atolx(args[1]));
129   }
130   return *count = 0;
131 }
132 
133 #define NOT 1  // Most recent test had an odd number of preceding !
134 #define AND 2  // test before -a failed since -o or ( so force false
135 #define OR  4  // test before -o succeeded since ( so force true
test_main(void)136 void test_main(void)
137 {
138   char *s = (void *)1;
139   int pos, paren, pstack, result = 0;
140 
141   toys.exitval = 2;
142   if (CFG_TOYBOX && *toys.which->name=='[') {
143     if (toys.optc) for (s = toys.optargs[--toys.optc]; *s==']'; s++);
144     if (*s) error_exit("Missing ']'");
145   }
146 
147   // loop through command line arguments
148   if (toys.optc) for (pos = paren = pstack = 0; ; pos++) {
149     int len = toys.optc-pos;
150 
151     if (!len) perror_exit("need arg @%d", pos);
152 
153     // Evaluate next test
154     result = do_test(toys.optargs+pos, &len);
155     pos += len;
156     // Single argument could be ! ( or nonempty
157     if (!len) {
158       if (toys.optargs[pos+1]) {
159         if (!strcmp("!", toys.optargs[pos])) {
160           pstack ^= NOT;
161           continue;
162         }
163         if (!strcmp("(", toys.optargs[pos])) {
164           if (++paren>9) perror_exit("bad (");
165           pstack <<= 3;
166           continue;
167         }
168       }
169       result = *toys.optargs[pos++];
170     }
171     s = toys.optargs[pos];
172     for (;;) {
173 
174       // Handle pending ! -a -o (the else means -o beats -a)
175       if (pstack&NOT) result = !result;
176       pstack &= ~NOT;
177       if (pstack&OR) result = 1;
178       else if (pstack&AND) result = 0;
179 
180       // Do it again for every )
181       if (!paren || pos==toys.optc || strcmp(")", s)) break;
182       paren--;
183       pstack >>= 3;
184       s = toys.optargs[++pos];
185     }
186 
187     // Out of arguments?
188     if (pos==toys.optc) {
189       if (paren) perror_exit("need )");
190       break;
191     }
192 
193     // are we followed by -a or -o?
194 
195     if (!strcmp("-a", s)) {
196       if (!result) pstack |= AND;
197     } else if (!strcmp("-o", s)) {
198       // -o flushes -a even if previous test was false
199       pstack &=~AND;
200       if (result) pstack |= OR;
201     } else error_exit("too many arguments");
202   }
203 
204   // Invert C logic to get shell logic
205   toys.exitval = !result;
206 }
207