test.c revision 1.31 1 1.31 christos /* $NetBSD: test.c,v 1.31 2007/03/28 01:47:25 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.31 christos __RCSID("$NetBSD: test.c,v 1.31 2007/03/28 01:47:25 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.13 jtc if (t_lex(*++t_wp) == BOR)
244 1.13 jtc return oexpr(t_lex(*++t_wp)) || res;
245 1.13 jtc t_wp--;
246 1.13 jtc return res;
247 1.13 jtc }
248 1.13 jtc
249 1.13 jtc static int
250 1.23 wiz aexpr(enum token n)
251 1.13 jtc {
252 1.13 jtc int res;
253 1.13 jtc
254 1.13 jtc res = nexpr(n);
255 1.13 jtc if (t_lex(*++t_wp) == BAND)
256 1.13 jtc return aexpr(t_lex(*++t_wp)) && res;
257 1.13 jtc t_wp--;
258 1.13 jtc return res;
259 1.13 jtc }
260 1.13 jtc
261 1.13 jtc static int
262 1.23 wiz nexpr(enum token n)
263 1.13 jtc {
264 1.26 simonb
265 1.13 jtc if (n == UNOT)
266 1.13 jtc return !nexpr(t_lex(*++t_wp));
267 1.13 jtc return primary(n);
268 1.13 jtc }
269 1.13 jtc
270 1.13 jtc static int
271 1.23 wiz primary(enum token n)
272 1.13 jtc {
273 1.21 kleink enum token nn;
274 1.13 jtc int res;
275 1.13 jtc
276 1.13 jtc if (n == EOI)
277 1.21 kleink return 0; /* missing expression */
278 1.13 jtc if (n == LPAREN) {
279 1.21 kleink if ((nn = t_lex(*++t_wp)) == RPAREN)
280 1.21 kleink return 0; /* missing expression */
281 1.21 kleink res = oexpr(nn);
282 1.13 jtc if (t_lex(*++t_wp) != RPAREN)
283 1.13 jtc syntax(NULL, "closing paren expected");
284 1.13 jtc return res;
285 1.1 glass }
286 1.13 jtc if (t_wp_op && t_wp_op->op_type == UNOP) {
287 1.13 jtc /* unary expression */
288 1.13 jtc if (*++t_wp == NULL)
289 1.13 jtc syntax(t_wp_op->op_text, "argument expected");
290 1.13 jtc switch (n) {
291 1.13 jtc case STREZ:
292 1.13 jtc return strlen(*t_wp) == 0;
293 1.13 jtc case STRNZ:
294 1.13 jtc return strlen(*t_wp) != 0;
295 1.13 jtc case FILTT:
296 1.13 jtc return isatty(getn(*t_wp));
297 1.13 jtc default:
298 1.13 jtc return filstat(*t_wp, n);
299 1.1 glass }
300 1.13 jtc }
301 1.14 cgd
302 1.14 cgd if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
303 1.14 cgd return binop();
304 1.14 cgd }
305 1.14 cgd
306 1.14 cgd return strlen(*t_wp) > 0;
307 1.14 cgd }
308 1.14 cgd
309 1.14 cgd static int
310 1.23 wiz binop(void)
311 1.14 cgd {
312 1.16 tls const char *opnd1, *opnd2;
313 1.14 cgd struct t_op const *op;
314 1.14 cgd
315 1.13 jtc opnd1 = *t_wp;
316 1.13 jtc (void) t_lex(*++t_wp);
317 1.14 cgd op = t_wp_op;
318 1.13 jtc
319 1.26 simonb if ((opnd2 = *++t_wp) == NULL)
320 1.14 cgd syntax(op->op_text, "argument expected");
321 1.13 jtc
322 1.14 cgd switch (op->op_num) {
323 1.14 cgd case STREQ:
324 1.14 cgd return strcmp(opnd1, opnd2) == 0;
325 1.14 cgd case STRNE:
326 1.14 cgd return strcmp(opnd1, opnd2) != 0;
327 1.14 cgd case STRLT:
328 1.14 cgd return strcmp(opnd1, opnd2) < 0;
329 1.14 cgd case STRGT:
330 1.14 cgd return strcmp(opnd1, opnd2) > 0;
331 1.14 cgd case INTEQ:
332 1.14 cgd return getn(opnd1) == getn(opnd2);
333 1.14 cgd case INTNE:
334 1.14 cgd return getn(opnd1) != getn(opnd2);
335 1.14 cgd case INTGE:
336 1.14 cgd return getn(opnd1) >= getn(opnd2);
337 1.14 cgd case INTGT:
338 1.14 cgd return getn(opnd1) > getn(opnd2);
339 1.14 cgd case INTLE:
340 1.14 cgd return getn(opnd1) <= getn(opnd2);
341 1.14 cgd case INTLT:
342 1.14 cgd return getn(opnd1) < getn(opnd2);
343 1.14 cgd case FILNT:
344 1.26 simonb return newerf(opnd1, opnd2);
345 1.14 cgd case FILOT:
346 1.26 simonb return olderf(opnd1, opnd2);
347 1.14 cgd case FILEQ:
348 1.26 simonb return equalf(opnd1, opnd2);
349 1.17 christos default:
350 1.19 mycroft abort();
351 1.17 christos /* NOTREACHED */
352 1.1 glass }
353 1.1 glass }
354 1.1 glass
355 1.1 glass static int
356 1.23 wiz filstat(char *nm, enum token mode)
357 1.1 glass {
358 1.13 jtc struct stat s;
359 1.13 jtc
360 1.18 mycroft if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
361 1.13 jtc return 0;
362 1.1 glass
363 1.13 jtc switch (mode) {
364 1.13 jtc case FILRD:
365 1.13 jtc return access(nm, R_OK) == 0;
366 1.13 jtc case FILWR:
367 1.13 jtc return access(nm, W_OK) == 0;
368 1.13 jtc case FILEX:
369 1.13 jtc return access(nm, X_OK) == 0;
370 1.13 jtc case FILEXIST:
371 1.13 jtc return access(nm, F_OK) == 0;
372 1.13 jtc case FILREG:
373 1.18 mycroft return S_ISREG(s.st_mode);
374 1.13 jtc case FILDIR:
375 1.18 mycroft return S_ISDIR(s.st_mode);
376 1.13 jtc case FILCDEV:
377 1.18 mycroft return S_ISCHR(s.st_mode);
378 1.13 jtc case FILBDEV:
379 1.18 mycroft return S_ISBLK(s.st_mode);
380 1.13 jtc case FILFIFO:
381 1.18 mycroft return S_ISFIFO(s.st_mode);
382 1.13 jtc case FILSOCK:
383 1.18 mycroft return S_ISSOCK(s.st_mode);
384 1.18 mycroft case FILSYM:
385 1.18 mycroft return S_ISLNK(s.st_mode);
386 1.13 jtc case FILSUID:
387 1.18 mycroft return (s.st_mode & S_ISUID) != 0;
388 1.13 jtc case FILSGID:
389 1.18 mycroft return (s.st_mode & S_ISGID) != 0;
390 1.13 jtc case FILSTCK:
391 1.18 mycroft return (s.st_mode & S_ISVTX) != 0;
392 1.13 jtc case FILGZ:
393 1.18 mycroft return s.st_size > (off_t)0;
394 1.13 jtc case FILUID:
395 1.13 jtc return s.st_uid == geteuid();
396 1.13 jtc case FILGID:
397 1.13 jtc return s.st_gid == getegid();
398 1.13 jtc default:
399 1.13 jtc return 1;
400 1.13 jtc }
401 1.1 glass }
402 1.1 glass
403 1.31 christos #define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text
404 1.31 christos
405 1.31 christos static int
406 1.31 christos compare1(const void *va, const void *vb)
407 1.31 christos {
408 1.31 christos const unsigned char *a = va;
409 1.31 christos const unsigned char *b = VTOC(vb);
410 1.31 christos
411 1.31 christos return a[0] - b[0];
412 1.31 christos }
413 1.31 christos
414 1.31 christos static int
415 1.31 christos compare2(const void *va, const void *vb)
416 1.31 christos {
417 1.31 christos const unsigned char *a = va;
418 1.31 christos const unsigned char *b = VTOC(vb);
419 1.31 christos int z = a[0] - b[0];
420 1.31 christos
421 1.31 christos return z ? z : (a[1] - b[1]);
422 1.31 christos }
423 1.31 christos
424 1.31 christos static struct t_op const *
425 1.31 christos findop(const char *s)
426 1.31 christos {
427 1.31 christos if (s[0] == '-') {
428 1.31 christos if (s[1] == '\0')
429 1.31 christos return NULL;
430 1.31 christos if (s[2] == '\0')
431 1.31 christos return bsearch(s + 1, mop2, __arraycount(mop2),
432 1.31 christos sizeof(*mop2), compare1);
433 1.31 christos else if (s[3] != '\0')
434 1.31 christos return NULL;
435 1.31 christos else
436 1.31 christos return bsearch(s + 1, mop3, __arraycount(mop3),
437 1.31 christos sizeof(*mop3), compare2);
438 1.31 christos } else {
439 1.31 christos if (s[1] == '\0')
440 1.31 christos return bsearch(s, cop, __arraycount(cop), sizeof(*cop),
441 1.31 christos compare1);
442 1.31 christos else if (strcmp(s, cop2[0].op_text) == 0)
443 1.31 christos return cop2;
444 1.31 christos else
445 1.31 christos return NULL;
446 1.31 christos }
447 1.31 christos }
448 1.31 christos
449 1.13 jtc static enum token
450 1.23 wiz t_lex(char *s)
451 1.1 glass {
452 1.24 wiz struct t_op const *op;
453 1.24 wiz
454 1.31 christos if (s == NULL) {
455 1.26 simonb t_wp_op = NULL;
456 1.13 jtc return EOI;
457 1.13 jtc }
458 1.31 christos
459 1.31 christos if ((op = findop(s)) != NULL) {
460 1.31 christos if (!((op->op_type == UNOP && isoperand()) ||
461 1.31 christos (op->op_num == LPAREN && *(t_wp+1) == 0))) {
462 1.13 jtc t_wp_op = op;
463 1.13 jtc return op->op_num;
464 1.13 jtc }
465 1.13 jtc }
466 1.26 simonb t_wp_op = NULL;
467 1.13 jtc return OPERAND;
468 1.21 kleink }
469 1.21 kleink
470 1.21 kleink static int
471 1.23 wiz isoperand(void)
472 1.21 kleink {
473 1.24 wiz struct t_op const *op;
474 1.24 wiz char *s, *t;
475 1.21 kleink
476 1.21 kleink if ((s = *(t_wp+1)) == 0)
477 1.21 kleink return 1;
478 1.21 kleink if ((t = *(t_wp+2)) == 0)
479 1.21 kleink return 0;
480 1.31 christos if ((op = findop(s)) != NULL)
481 1.31 christos return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
482 1.21 kleink return 0;
483 1.1 glass }
484 1.1 glass
485 1.13 jtc /* atoi with error detection */
486 1.1 glass static int
487 1.23 wiz getn(const char *s)
488 1.1 glass {
489 1.6 alm char *p;
490 1.6 alm long r;
491 1.1 glass
492 1.6 alm errno = 0;
493 1.13 jtc r = strtol(s, &p, 10);
494 1.13 jtc
495 1.6 alm if (errno != 0)
496 1.22 christos error("%s: out of range", s);
497 1.13 jtc
498 1.20 christos while (isspace((unsigned char)*p))
499 1.22 christos p++;
500 1.13 jtc
501 1.13 jtc if (*p)
502 1.22 christos error("%s: bad number", s);
503 1.13 jtc
504 1.13 jtc return (int) r;
505 1.1 glass }
506 1.1 glass
507 1.13 jtc static int
508 1.26 simonb newerf(const char *f1, const char *f2)
509 1.1 glass {
510 1.13 jtc struct stat b1, b2;
511 1.13 jtc
512 1.26 simonb return (stat(f1, &b1) == 0 &&
513 1.26 simonb stat(f2, &b2) == 0 &&
514 1.13 jtc b1.st_mtime > b2.st_mtime);
515 1.1 glass }
516 1.1 glass
517 1.13 jtc static int
518 1.26 simonb olderf(const char *f1, const char *f2)
519 1.1 glass {
520 1.13 jtc struct stat b1, b2;
521 1.13 jtc
522 1.26 simonb return (stat(f1, &b1) == 0 &&
523 1.26 simonb stat(f2, &b2) == 0 &&
524 1.13 jtc b1.st_mtime < b2.st_mtime);
525 1.1 glass }
526 1.1 glass
527 1.13 jtc static int
528 1.26 simonb equalf(const char *f1, const char *f2)
529 1.13 jtc {
530 1.13 jtc struct stat b1, b2;
531 1.1 glass
532 1.26 simonb return (stat(f1, &b1) == 0 &&
533 1.26 simonb stat(f2, &b2) == 0 &&
534 1.13 jtc b1.st_dev == b2.st_dev &&
535 1.13 jtc b1.st_ino == b2.st_ino);
536 1.1 glass }
537