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