1/* See LICENSE file for copyright and license details. */
2
3
4#include <sys/stat.h>
5
6#include <ctype.h>
7#include <fcntl.h>
8#include <string.h>
9#include <unistd.h>
10
11#include "util.h"
12
13static int
14intcmp(char *a, char *b)
15{
16 char *s;
17 int asign = *a == '-' ? -1 : 1;
18 int bsign = *b == '-' ? -1 : 1;
19
20 if (*a == '-' || *a == '+') a += 1;
21 if (*b == '-' || *b == '+') b += 1;
22
23 if (!*a || !*b)
24 goto noint;
25 for (s = a; *s; s++)
26 if (!isdigit(*s))
27 goto noint;
28 for (s = b; *s; s++)
29 if (!isdigit(*s))
30 goto noint;
31
32 while (*a == '0') a++;
33 while (*b == '0') b++;
34 asign *= !!*a;
35 bsign *= !!*b;
36
37 if (asign != bsign)
38 return asign < bsign ? -1 : 1;
39 else if (strlen(a) != strlen(b))
40 return asign * (strlen(a) < strlen(b) ? -1 : 1);
41 else
42 return asign * strcmp(a, b);
43
44noint:
45 enprintf(2, "expected integer operands\n");
46
47 return 0; /* not reached */
48}
49
50static int
51mtimecmp(struct stat *buf1, struct stat *buf2)
52{
53 if (buf1->st_mtime < buf2->st_mtime) return -1;
54 if (buf1->st_mtime > buf2->st_mtime) return +1;
55#ifdef st_mtime
56 if (buf1->st_mtim.tv_nsec < buf2->st_mtim.tv_nsec) return -1;
57 if (buf1->st_mtim.tv_nsec > buf2->st_mtim.tv_nsec) return +1;
58#endif
59 return 0;
60}
61
62static int unary_b(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISBLK (buf.st_mode); }
63static int unary_c(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISCHR (buf.st_mode); }
64static int unary_d(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISDIR (buf.st_mode); }
65static int unary_f(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISREG (buf.st_mode); }
66static int unary_g(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISGID & buf.st_mode ; }
67static int unary_h(char *s) { struct stat buf; if (lstat(s, &buf)) return 0; return S_ISLNK (buf.st_mode); }
68static int unary_k(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISVTX & buf.st_mode ; }
69static int unary_p(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISFIFO (buf.st_mode); }
70static int unary_S(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISSOCK (buf.st_mode); }
71static int unary_s(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return buf.st_size ; }
72static int unary_u(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISUID & buf.st_mode ; }
73
74static int unary_n(char *s) { return *s; }
75static int unary_z(char *s) { return !*s; }
76
77static int unary_e(char *s) { return !faccessat(AT_FDCWD, s, F_OK, AT_EACCESS); }
78static int unary_r(char *s) { return !faccessat(AT_FDCWD, s, R_OK, AT_EACCESS); }
79static int unary_w(char *s) { return !faccessat(AT_FDCWD, s, W_OK, AT_EACCESS); }
80static int unary_x(char *s) { return !faccessat(AT_FDCWD, s, X_OK, AT_EACCESS); }
81
82static int unary_t(char *s) { int fd = enstrtonum(2, s, 0, INT_MAX); return isatty(fd); }
83
84static int binary_se(char *s1, char *s2) { return !strcmp(s1, s2); }
85static int binary_sn(char *s1, char *s2) { return strcmp(s1, s2); }
86
87static int binary_eq(char *s1, char *s2) { return intcmp(s1, s2) == 0; }
88static int binary_ne(char *s1, char *s2) { return intcmp(s1, s2) != 0; }
89static int binary_gt(char *s1, char *s2) { return intcmp(s1, s2) > 0; }
90static int binary_ge(char *s1, char *s2) { return intcmp(s1, s2) >= 0; }
91static int binary_lt(char *s1, char *s2) { return intcmp(s1, s2) < 0; }
92static int binary_le(char *s1, char *s2) { return intcmp(s1, s2) <= 0; }
93
94static int
95binary_ef(char *s1, char *s2)
96{
97 struct stat buf1, buf2;
98 if (stat(s1, &buf1) || stat(s2, &buf2)) return 0;
99 return buf1.st_dev == buf2.st_dev && buf1.st_ino == buf2.st_ino;
100}
101
102static int
103binary_ot(char *s1, char *s2)
104{
105 struct stat buf1, buf2;
106 if (stat(s1, &buf1) || stat(s2, &buf2)) return 0;
107 return mtimecmp(&buf1, &buf2) < 0;
108}
109
110static int
111binary_nt(char *s1, char *s2)
112{
113 struct stat buf1, buf2;
114 if (stat(s1, &buf1) || stat(s2, &buf2)) return 0;
115 return mtimecmp(&buf1, &buf2) > 0;
116}
117
118struct test {
119 char *name;
120 union {
121 int (*u)(char *);
122 int (*b)(char *, char *);
123 } func;
124};
125
126static struct test unary[] = {
127 { "-b", { .u = unary_b } },
128 { "-c", { .u = unary_c } },
129 { "-d", { .u = unary_d } },
130 { "-e", { .u = unary_e } },
131 { "-f", { .u = unary_f } },
132 { "-g", { .u = unary_g } },
133 { "-h", { .u = unary_h } },
134 { "-k", { .u = unary_k } },
135 { "-L", { .u = unary_h } },
136 { "-n", { .u = unary_n } },
137 { "-p", { .u = unary_p } },
138 { "-r", { .u = unary_r } },
139 { "-S", { .u = unary_S } },
140 { "-s", { .u = unary_s } },
141 { "-t", { .u = unary_t } },
142 { "-u", { .u = unary_u } },
143 { "-w", { .u = unary_w } },
144 { "-x", { .u = unary_x } },
145 { "-z", { .u = unary_z } },
146
147 { NULL },
148};
149
150static struct test binary[] = {
151 { "=" , { .b = binary_se } },
152 { "!=" , { .b = binary_sn } },
153 { "-eq", { .b = binary_eq } },
154 { "-ne", { .b = binary_ne } },
155 { "-gt", { .b = binary_gt } },
156 { "-ge", { .b = binary_ge } },
157 { "-lt", { .b = binary_lt } },
158 { "-le", { .b = binary_le } },
159 { "-ef", { .b = binary_ef } },
160 { "-ot", { .b = binary_ot } },
161 { "-nt", { .b = binary_nt } },
162
163 { NULL },
164};
165
166static struct test *
167find_test(struct test *tests, char *name)
168{
169 struct test *t;
170
171 for (t = tests; t->name; t++)
172 if (!strcmp(t->name, name))
173 return t;
174
175 return NULL;
176}
177
178static int
179noarg(char *argv[])
180{
181 (void)argv;
182 return 0;
183}
184
185static int
186onearg(char *argv[])
187{
188 return unary_n(argv[0]);
189}
190
191static int
192twoarg(char *argv[])
193{
194 struct test *t;
195
196 if (!strcmp(argv[0], "!"))
197 return !onearg(argv + 1);
198
199 if ((t = find_test(unary, *argv)))
200 return t->func.u(argv[1]);
201
202 enprintf(2, "bad unary test %s\n", argv[0]);
203
204 return 0; /* not reached */
205}
206
207static int
208threearg(char *argv[])
209{
210 struct test *t = find_test(binary, argv[1]);
211
212 if (t)
213 return t->func.b(argv[0], argv[2]);
214
215 if (!strcmp(argv[0], "!"))
216 return !twoarg(argv + 1);
217
218 enprintf(2, "bad binary test %s\n", argv[1]);
219
220 return 0; /* not reached */
221}
222
223static int
224fourarg(char *argv[])
225{
226 if (!strcmp(argv[0], "!"))
227 return !threearg(argv + 1);
228
229 enprintf(2, "too many arguments\n");
230
231 return 0; /* not reached */
232}
233
234// ?man test: evaluate condition
235// ?man check file types and compare values, returning 0 or 1
236int
237main(int argc, char *argv[])
238{
239 int (*narg[])(char *[]) = { noarg, onearg, twoarg, threearg, fourarg };
240 size_t len;
241
242 argv0 = *argv, argv0 ? (argc--, argv++) : (void *)0;
243
244 len = argv0 ? strlen(argv0) : 0;
245 if (len && argv0[--len] == '[' && (!len || argv0[--len] == '/') && strcmp(argv[--argc], "]"))
246 enprintf(2, "no matching ]\n");
247
248 if (argc > 4)
249 enprintf(2, "too many arguments\n");
250
251 return !narg[argc](argv);
252}