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