]>
Commit | Line | Data |
---|---|---|
1 | /* $NetBSD: test.c,v 1.19 1998/07/28 11:41:59 mycroft Exp $ */ | |
2 | ||
3 | /* | |
4 | * test(1); version 7-like -- author Erik Baalbergen | |
5 | * modified by Eric Gisin to be used as built-in. | |
6 | * modified by Arnold Robbins to add SVR3 compatibility | |
7 | * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). | |
8 | * modified by J.T. Conklin for NetBSD. | |
9 | * | |
10 | * This program is in the Public Domain. | |
11 | */ | |
12 | ||
13 | #include <sys/cdefs.h> | |
14 | #ifndef lint | |
15 | __RCSID("$NetBSD: test.c,v 1.19 1998/07/28 11:41:59 mycroft Exp $"); | |
16 | #endif | |
17 | ||
18 | #include <sys/types.h> | |
19 | #include <sys/stat.h> | |
20 | #include <unistd.h> | |
21 | #include <ctype.h> | |
22 | #include <errno.h> | |
23 | #include <stdio.h> | |
24 | #include <stdlib.h> | |
25 | #include <string.h> | |
26 | #include <err.h> | |
27 | ||
28 | /* test(1) accepts the following grammar: | |
29 | oexpr ::= aexpr | aexpr "-o" oexpr ; | |
30 | aexpr ::= nexpr | nexpr "-a" aexpr ; | |
31 | nexpr ::= primary | "!" primary | |
32 | primary ::= unary-operator operand | |
33 | | operand binary-operator operand | |
34 | | operand | |
35 | | "(" oexpr ")" | |
36 | ; | |
37 | unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| | |
38 | "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; | |
39 | ||
40 | binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| | |
41 | "-nt"|"-ot"|"-ef"; | |
42 | operand ::= <any legal UNIX file name> | |
43 | */ | |
44 | ||
45 | enum token { | |
46 | EOI, | |
47 | FILRD, | |
48 | FILWR, | |
49 | FILEX, | |
50 | FILEXIST, | |
51 | FILREG, | |
52 | FILDIR, | |
53 | FILCDEV, | |
54 | FILBDEV, | |
55 | FILFIFO, | |
56 | FILSOCK, | |
57 | FILSYM, | |
58 | FILGZ, | |
59 | FILTT, | |
60 | FILSUID, | |
61 | FILSGID, | |
62 | FILSTCK, | |
63 | FILNT, | |
64 | FILOT, | |
65 | FILEQ, | |
66 | FILUID, | |
67 | FILGID, | |
68 | STREZ, | |
69 | STRNZ, | |
70 | STREQ, | |
71 | STRNE, | |
72 | STRLT, | |
73 | STRGT, | |
74 | INTEQ, | |
75 | INTNE, | |
76 | INTGE, | |
77 | INTGT, | |
78 | INTLE, | |
79 | INTLT, | |
80 | UNOT, | |
81 | BAND, | |
82 | BOR, | |
83 | LPAREN, | |
84 | RPAREN, | |
85 | OPERAND | |
86 | }; | |
87 | ||
88 | enum token_types { | |
89 | UNOP, | |
90 | BINOP, | |
91 | BUNOP, | |
92 | BBINOP, | |
93 | PAREN | |
94 | }; | |
95 | ||
96 | struct t_op { | |
97 | const char *op_text; | |
98 | short op_num, op_type; | |
99 | } const ops [] = { | |
100 | {"-r", FILRD, UNOP}, | |
101 | {"-w", FILWR, UNOP}, | |
102 | {"-x", FILEX, UNOP}, | |
103 | {"-e", FILEXIST,UNOP}, | |
104 | {"-f", FILREG, UNOP}, | |
105 | {"-d", FILDIR, UNOP}, | |
106 | {"-c", FILCDEV,UNOP}, | |
107 | {"-b", FILBDEV,UNOP}, | |
108 | {"-p", FILFIFO,UNOP}, | |
109 | {"-u", FILSUID,UNOP}, | |
110 | {"-g", FILSGID,UNOP}, | |
111 | {"-k", FILSTCK,UNOP}, | |
112 | {"-s", FILGZ, UNOP}, | |
113 | {"-t", FILTT, UNOP}, | |
114 | {"-z", STREZ, UNOP}, | |
115 | {"-n", STRNZ, UNOP}, | |
116 | {"-h", FILSYM, UNOP}, /* for backwards compat */ | |
117 | {"-O", FILUID, UNOP}, | |
118 | {"-G", FILGID, UNOP}, | |
119 | {"-L", FILSYM, UNOP}, | |
120 | {"-S", FILSOCK,UNOP}, | |
121 | {"=", STREQ, BINOP}, | |
122 | {"!=", STRNE, BINOP}, | |
123 | {"<", STRLT, BINOP}, | |
124 | {">", STRGT, BINOP}, | |
125 | {"-eq", INTEQ, BINOP}, | |
126 | {"-ne", INTNE, BINOP}, | |
127 | {"-ge", INTGE, BINOP}, | |
128 | {"-gt", INTGT, BINOP}, | |
129 | {"-le", INTLE, BINOP}, | |
130 | {"-lt", INTLT, BINOP}, | |
131 | {"-nt", FILNT, BINOP}, | |
132 | {"-ot", FILOT, BINOP}, | |
133 | {"-ef", FILEQ, BINOP}, | |
134 | {"!", UNOT, BUNOP}, | |
135 | {"-a", BAND, BBINOP}, | |
136 | {"-o", BOR, BBINOP}, | |
137 | {"(", LPAREN, PAREN}, | |
138 | {")", RPAREN, PAREN}, | |
139 | {0, 0, 0} | |
140 | }; | |
141 | ||
142 | char **t_wp; | |
143 | struct t_op const *t_wp_op; | |
144 | ||
145 | static void syntax __P((const char *, const char *)); | |
146 | static int oexpr __P((enum token)); | |
147 | static int aexpr __P((enum token)); | |
148 | static int nexpr __P((enum token)); | |
149 | static int primary __P((enum token)); | |
150 | static int binop __P((void)); | |
151 | static int filstat __P((char *, enum token)); | |
152 | static enum token t_lex __P((char *)); | |
153 | static int getn __P((const char *)); | |
154 | static int newerf __P((const char *, const char *)); | |
155 | static int olderf __P((const char *, const char *)); | |
156 | static int equalf __P((const char *, const char *)); | |
157 | ||
158 | int main __P((int, char **)); | |
159 | ||
160 | int | |
161 | main(argc, argv) | |
162 | int argc; | |
163 | char **argv; | |
164 | { | |
165 | int res; | |
166 | ||
167 | if (argv[0] && strcmp(argv[0], "[") == 0) { | |
168 | if (strcmp(argv[--argc], "]")) | |
169 | errx(2, "missing ]"); | |
170 | argv[argc] = NULL; | |
171 | } | |
172 | ||
173 | /* Implement special cases from POSIX.2, section 4.62.4 */ | |
174 | switch (argc) { | |
175 | case 1: | |
176 | return 1; | |
177 | case 2: | |
178 | return (*argv[1] == '\0'); | |
179 | case 3: | |
180 | if (argv[1][0] == '!' && argv[1][1] == '\0') { | |
181 | return !(*argv[2] == '\0'); | |
182 | } | |
183 | break; | |
184 | case 4: | |
185 | if (argv[1][0] != '!' || argv[1][1] != '\0') { | |
186 | if (t_lex(argv[2]), | |
187 | t_wp_op && t_wp_op->op_type == BINOP) { | |
188 | t_wp = &argv[1]; | |
189 | return (binop() == 0); | |
190 | } | |
191 | } | |
192 | break; | |
193 | case 5: | |
194 | if (argv[1][0] == '!' && argv[1][1] == '\0') { | |
195 | if (t_lex(argv[3]), | |
196 | t_wp_op && t_wp_op->op_type == BINOP) { | |
197 | t_wp = &argv[2]; | |
198 | return !(binop() == 0); | |
199 | } | |
200 | } | |
201 | break; | |
202 | } | |
203 | ||
204 | t_wp = &argv[1]; | |
205 | res = !oexpr(t_lex(*t_wp)); | |
206 | ||
207 | if (*t_wp != NULL && *++t_wp != NULL) | |
208 | syntax(*t_wp, "unknown operand"); | |
209 | ||
210 | return res; | |
211 | } | |
212 | ||
213 | static void | |
214 | syntax(op, msg) | |
215 | const char *op; | |
216 | const char *msg; | |
217 | { | |
218 | if (op && *op) | |
219 | errx(2, "%s: %s", op, msg); | |
220 | else | |
221 | errx(2, "%s", msg); | |
222 | } | |
223 | ||
224 | static int | |
225 | oexpr(n) | |
226 | enum token n; | |
227 | { | |
228 | int res; | |
229 | ||
230 | res = aexpr(n); | |
231 | if (t_lex(*++t_wp) == BOR) | |
232 | return oexpr(t_lex(*++t_wp)) || res; | |
233 | t_wp--; | |
234 | return res; | |
235 | } | |
236 | ||
237 | static int | |
238 | aexpr(n) | |
239 | enum token n; | |
240 | { | |
241 | int res; | |
242 | ||
243 | res = nexpr(n); | |
244 | if (t_lex(*++t_wp) == BAND) | |
245 | return aexpr(t_lex(*++t_wp)) && res; | |
246 | t_wp--; | |
247 | return res; | |
248 | } | |
249 | ||
250 | static int | |
251 | nexpr(n) | |
252 | enum token n; /* token */ | |
253 | { | |
254 | if (n == UNOT) | |
255 | return !nexpr(t_lex(*++t_wp)); | |
256 | return primary(n); | |
257 | } | |
258 | ||
259 | static int | |
260 | primary(n) | |
261 | enum token n; | |
262 | { | |
263 | int res; | |
264 | ||
265 | if (n == EOI) | |
266 | syntax(NULL, "argument expected"); | |
267 | if (n == LPAREN) { | |
268 | res = oexpr(t_lex(*++t_wp)); | |
269 | if (t_lex(*++t_wp) != RPAREN) | |
270 | syntax(NULL, "closing paren expected"); | |
271 | return res; | |
272 | } | |
273 | if (t_wp_op && t_wp_op->op_type == UNOP) { | |
274 | /* unary expression */ | |
275 | if (*++t_wp == NULL) | |
276 | syntax(t_wp_op->op_text, "argument expected"); | |
277 | switch (n) { | |
278 | case STREZ: | |
279 | return strlen(*t_wp) == 0; | |
280 | case STRNZ: | |
281 | return strlen(*t_wp) != 0; | |
282 | case FILTT: | |
283 | return isatty(getn(*t_wp)); | |
284 | default: | |
285 | return filstat(*t_wp, n); | |
286 | } | |
287 | } | |
288 | ||
289 | if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { | |
290 | return binop(); | |
291 | } | |
292 | ||
293 | return strlen(*t_wp) > 0; | |
294 | } | |
295 | ||
296 | static int | |
297 | binop() | |
298 | { | |
299 | const char *opnd1, *opnd2; | |
300 | struct t_op const *op; | |
301 | ||
302 | opnd1 = *t_wp; | |
303 | (void) t_lex(*++t_wp); | |
304 | op = t_wp_op; | |
305 | ||
306 | if ((opnd2 = *++t_wp) == (char *)0) | |
307 | syntax(op->op_text, "argument expected"); | |
308 | ||
309 | switch (op->op_num) { | |
310 | case STREQ: | |
311 | return strcmp(opnd1, opnd2) == 0; | |
312 | case STRNE: | |
313 | return strcmp(opnd1, opnd2) != 0; | |
314 | case STRLT: | |
315 | return strcmp(opnd1, opnd2) < 0; | |
316 | case STRGT: | |
317 | return strcmp(opnd1, opnd2) > 0; | |
318 | case INTEQ: | |
319 | return getn(opnd1) == getn(opnd2); | |
320 | case INTNE: | |
321 | return getn(opnd1) != getn(opnd2); | |
322 | case INTGE: | |
323 | return getn(opnd1) >= getn(opnd2); | |
324 | case INTGT: | |
325 | return getn(opnd1) > getn(opnd2); | |
326 | case INTLE: | |
327 | return getn(opnd1) <= getn(opnd2); | |
328 | case INTLT: | |
329 | return getn(opnd1) < getn(opnd2); | |
330 | case FILNT: | |
331 | return newerf (opnd1, opnd2); | |
332 | case FILOT: | |
333 | return olderf (opnd1, opnd2); | |
334 | case FILEQ: | |
335 | return equalf (opnd1, opnd2); | |
336 | default: | |
337 | abort(); | |
338 | /* NOTREACHED */ | |
339 | } | |
340 | } | |
341 | ||
342 | static int | |
343 | filstat(nm, mode) | |
344 | char *nm; | |
345 | enum token mode; | |
346 | { | |
347 | struct stat s; | |
348 | ||
349 | if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) | |
350 | return 0; | |
351 | ||
352 | switch (mode) { | |
353 | case FILRD: | |
354 | return access(nm, R_OK) == 0; | |
355 | case FILWR: | |
356 | return access(nm, W_OK) == 0; | |
357 | case FILEX: | |
358 | return access(nm, X_OK) == 0; | |
359 | case FILEXIST: | |
360 | return access(nm, F_OK) == 0; | |
361 | case FILREG: | |
362 | return S_ISREG(s.st_mode); | |
363 | case FILDIR: | |
364 | return S_ISDIR(s.st_mode); | |
365 | case FILCDEV: | |
366 | return S_ISCHR(s.st_mode); | |
367 | case FILBDEV: | |
368 | return S_ISBLK(s.st_mode); | |
369 | case FILFIFO: | |
370 | return S_ISFIFO(s.st_mode); | |
371 | case FILSOCK: | |
372 | return S_ISSOCK(s.st_mode); | |
373 | case FILSYM: | |
374 | return S_ISLNK(s.st_mode); | |
375 | case FILSUID: | |
376 | return (s.st_mode & S_ISUID) != 0; | |
377 | case FILSGID: | |
378 | return (s.st_mode & S_ISGID) != 0; | |
379 | case FILSTCK: | |
380 | return (s.st_mode & S_ISVTX) != 0; | |
381 | case FILGZ: | |
382 | return s.st_size > (off_t)0; | |
383 | case FILUID: | |
384 | return s.st_uid == geteuid(); | |
385 | case FILGID: | |
386 | return s.st_gid == getegid(); | |
387 | default: | |
388 | return 1; | |
389 | } | |
390 | } | |
391 | ||
392 | static enum token | |
393 | t_lex(s) | |
394 | char *s; | |
395 | { | |
396 | struct t_op const *op = ops; | |
397 | ||
398 | if (s == 0) { | |
399 | t_wp_op = (struct t_op *)0; | |
400 | return EOI; | |
401 | } | |
402 | while (op->op_text) { | |
403 | if (strcmp(s, op->op_text) == 0) { | |
404 | t_wp_op = op; | |
405 | return op->op_num; | |
406 | } | |
407 | op++; | |
408 | } | |
409 | t_wp_op = (struct t_op *)0; | |
410 | return OPERAND; | |
411 | } | |
412 | ||
413 | /* atoi with error detection */ | |
414 | static int | |
415 | getn(s) | |
416 | const char *s; | |
417 | { | |
418 | char *p; | |
419 | long r; | |
420 | ||
421 | errno = 0; | |
422 | r = strtol(s, &p, 10); | |
423 | ||
424 | if (errno != 0) | |
425 | errx(2, "%s: out of range", s); | |
426 | ||
427 | while (isspace(*p)) | |
428 | p++; | |
429 | ||
430 | if (*p) | |
431 | errx(2, "%s: bad number", s); | |
432 | ||
433 | return (int) r; | |
434 | } | |
435 | ||
436 | static int | |
437 | newerf (f1, f2) | |
438 | const char *f1, *f2; | |
439 | { | |
440 | struct stat b1, b2; | |
441 | ||
442 | return (stat (f1, &b1) == 0 && | |
443 | stat (f2, &b2) == 0 && | |
444 | b1.st_mtime > b2.st_mtime); | |
445 | } | |
446 | ||
447 | static int | |
448 | olderf (f1, f2) | |
449 | const char *f1, *f2; | |
450 | { | |
451 | struct stat b1, b2; | |
452 | ||
453 | return (stat (f1, &b1) == 0 && | |
454 | stat (f2, &b2) == 0 && | |
455 | b1.st_mtime < b2.st_mtime); | |
456 | } | |
457 | ||
458 | static int | |
459 | equalf (f1, f2) | |
460 | const char *f1, *f2; | |
461 | { | |
462 | struct stat b1, b2; | |
463 | ||
464 | return (stat (f1, &b1) == 0 && | |
465 | stat (f2, &b2) == 0 && | |
466 | b1.st_dev == b2.st_dev && | |
467 | b1.st_ino == b2.st_ino); | |
468 | } |