test.c revision 1.28 1 1.28 christos /* $NetBSD: test.c,v 1.28 2006/08/04 19:07:55 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.28 christos __RCSID("$NetBSD: test.c,v 1.28 2006/08/04 19:07:55 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.13 jtc binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
43 1.13 jtc "-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.22 christos static struct t_op {
99 1.13 jtc const char *op_text;
100 1.13 jtc short op_num, op_type;
101 1.13 jtc } const ops [] = {
102 1.13 jtc {"-r", FILRD, UNOP},
103 1.13 jtc {"-w", FILWR, UNOP},
104 1.13 jtc {"-x", FILEX, UNOP},
105 1.13 jtc {"-e", FILEXIST,UNOP},
106 1.13 jtc {"-f", FILREG, UNOP},
107 1.13 jtc {"-d", FILDIR, UNOP},
108 1.13 jtc {"-c", FILCDEV,UNOP},
109 1.13 jtc {"-b", FILBDEV,UNOP},
110 1.13 jtc {"-p", FILFIFO,UNOP},
111 1.13 jtc {"-u", FILSUID,UNOP},
112 1.13 jtc {"-g", FILSGID,UNOP},
113 1.13 jtc {"-k", FILSTCK,UNOP},
114 1.13 jtc {"-s", FILGZ, UNOP},
115 1.13 jtc {"-t", FILTT, UNOP},
116 1.13 jtc {"-z", STREZ, UNOP},
117 1.13 jtc {"-n", STRNZ, UNOP},
118 1.13 jtc {"-h", FILSYM, UNOP}, /* for backwards compat */
119 1.13 jtc {"-O", FILUID, UNOP},
120 1.13 jtc {"-G", FILGID, UNOP},
121 1.13 jtc {"-L", FILSYM, UNOP},
122 1.13 jtc {"-S", FILSOCK,UNOP},
123 1.13 jtc {"=", STREQ, BINOP},
124 1.13 jtc {"!=", STRNE, BINOP},
125 1.13 jtc {"<", STRLT, BINOP},
126 1.13 jtc {">", STRGT, BINOP},
127 1.13 jtc {"-eq", INTEQ, BINOP},
128 1.13 jtc {"-ne", INTNE, BINOP},
129 1.13 jtc {"-ge", INTGE, BINOP},
130 1.13 jtc {"-gt", INTGT, BINOP},
131 1.13 jtc {"-le", INTLE, BINOP},
132 1.13 jtc {"-lt", INTLT, BINOP},
133 1.13 jtc {"-nt", FILNT, BINOP},
134 1.13 jtc {"-ot", FILOT, BINOP},
135 1.13 jtc {"-ef", FILEQ, BINOP},
136 1.13 jtc {"!", UNOT, BUNOP},
137 1.13 jtc {"-a", BAND, BBINOP},
138 1.13 jtc {"-o", BOR, BBINOP},
139 1.13 jtc {"(", LPAREN, PAREN},
140 1.13 jtc {")", RPAREN, PAREN},
141 1.13 jtc {0, 0, 0}
142 1.1 glass };
143 1.1 glass
144 1.22 christos static char **t_wp;
145 1.22 christos static struct t_op const *t_wp_op;
146 1.1 glass
147 1.23 wiz static void syntax(const char *, const char *);
148 1.23 wiz static int oexpr(enum token);
149 1.23 wiz static int aexpr(enum token);
150 1.23 wiz static int nexpr(enum token);
151 1.23 wiz static int primary(enum token);
152 1.23 wiz static int binop(void);
153 1.23 wiz static int filstat(char *, enum token);
154 1.23 wiz static enum token t_lex(char *);
155 1.23 wiz static int isoperand(void);
156 1.23 wiz static int getn(const char *);
157 1.23 wiz static int newerf(const char *, const char *);
158 1.23 wiz static int olderf(const char *, const char *);
159 1.23 wiz static int equalf(const char *, const char *);
160 1.17 christos
161 1.22 christos #if defined(SHELL)
162 1.23 wiz extern void error(const char *, ...) __attribute__((__noreturn__));
163 1.22 christos #else
164 1.23 wiz static void error(const char *, ...) __attribute__((__noreturn__));
165 1.22 christos
166 1.22 christos static void
167 1.22 christos error(const char *msg, ...)
168 1.22 christos {
169 1.22 christos va_list ap;
170 1.22 christos
171 1.22 christos va_start(ap, msg);
172 1.22 christos verrx(2, msg, ap);
173 1.22 christos /*NOTREACHED*/
174 1.22 christos va_end(ap);
175 1.22 christos }
176 1.22 christos #endif
177 1.22 christos
178 1.22 christos #ifdef SHELL
179 1.23 wiz int testcmd(int, char **);
180 1.22 christos
181 1.22 christos int
182 1.23 wiz testcmd(int argc, char **argv)
183 1.22 christos #else
184 1.24 wiz int main(int, char *[]);
185 1.1 glass
186 1.1 glass int
187 1.24 wiz main(int argc, char *argv[])
188 1.22 christos #endif
189 1.1 glass {
190 1.24 wiz int res;
191 1.28 christos const char *argv0;
192 1.22 christos
193 1.28 christos #ifdef SHELL
194 1.28 christos argv0 = argv[0];
195 1.28 christos #else
196 1.24 wiz setprogname(argv[0]);
197 1.28 christos argv0 = getprogname();
198 1.28 christos #endif
199 1.28 christos if (strcmp(argv0, "[") == 0) {
200 1.1 glass if (strcmp(argv[--argc], "]"))
201 1.22 christos error("missing ]");
202 1.1 glass argv[argc] = NULL;
203 1.1 glass }
204 1.1 glass
205 1.22 christos if (argc < 2)
206 1.22 christos return 1;
207 1.22 christos
208 1.14 cgd t_wp = &argv[1];
209 1.13 jtc res = !oexpr(t_lex(*t_wp));
210 1.13 jtc
211 1.13 jtc if (*t_wp != NULL && *++t_wp != NULL)
212 1.21 kleink syntax(*t_wp, "unexpected operator");
213 1.13 jtc
214 1.13 jtc return res;
215 1.13 jtc }
216 1.13 jtc
217 1.13 jtc static void
218 1.23 wiz syntax(const char *op, const char *msg)
219 1.13 jtc {
220 1.26 simonb
221 1.13 jtc if (op && *op)
222 1.22 christos error("%s: %s", op, msg);
223 1.13 jtc else
224 1.22 christos error("%s", msg);
225 1.13 jtc }
226 1.13 jtc
227 1.13 jtc static int
228 1.23 wiz oexpr(enum token n)
229 1.13 jtc {
230 1.13 jtc int res;
231 1.13 jtc
232 1.13 jtc res = aexpr(n);
233 1.13 jtc if (t_lex(*++t_wp) == BOR)
234 1.13 jtc return oexpr(t_lex(*++t_wp)) || res;
235 1.13 jtc t_wp--;
236 1.13 jtc return res;
237 1.13 jtc }
238 1.13 jtc
239 1.13 jtc static int
240 1.23 wiz aexpr(enum token n)
241 1.13 jtc {
242 1.13 jtc int res;
243 1.13 jtc
244 1.13 jtc res = nexpr(n);
245 1.13 jtc if (t_lex(*++t_wp) == BAND)
246 1.13 jtc return aexpr(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 nexpr(enum token n)
253 1.13 jtc {
254 1.26 simonb
255 1.13 jtc if (n == UNOT)
256 1.13 jtc return !nexpr(t_lex(*++t_wp));
257 1.13 jtc return primary(n);
258 1.13 jtc }
259 1.13 jtc
260 1.13 jtc static int
261 1.23 wiz primary(enum token n)
262 1.13 jtc {
263 1.21 kleink enum token nn;
264 1.13 jtc int res;
265 1.13 jtc
266 1.13 jtc if (n == EOI)
267 1.21 kleink return 0; /* missing expression */
268 1.13 jtc if (n == LPAREN) {
269 1.21 kleink if ((nn = t_lex(*++t_wp)) == RPAREN)
270 1.21 kleink return 0; /* missing expression */
271 1.21 kleink res = oexpr(nn);
272 1.13 jtc if (t_lex(*++t_wp) != RPAREN)
273 1.13 jtc syntax(NULL, "closing paren expected");
274 1.13 jtc return res;
275 1.1 glass }
276 1.13 jtc if (t_wp_op && t_wp_op->op_type == UNOP) {
277 1.13 jtc /* unary expression */
278 1.13 jtc if (*++t_wp == NULL)
279 1.13 jtc syntax(t_wp_op->op_text, "argument expected");
280 1.13 jtc switch (n) {
281 1.13 jtc case STREZ:
282 1.13 jtc return strlen(*t_wp) == 0;
283 1.13 jtc case STRNZ:
284 1.13 jtc return strlen(*t_wp) != 0;
285 1.13 jtc case FILTT:
286 1.13 jtc return isatty(getn(*t_wp));
287 1.13 jtc default:
288 1.13 jtc return filstat(*t_wp, n);
289 1.1 glass }
290 1.13 jtc }
291 1.14 cgd
292 1.14 cgd if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
293 1.14 cgd return binop();
294 1.14 cgd }
295 1.14 cgd
296 1.14 cgd return strlen(*t_wp) > 0;
297 1.14 cgd }
298 1.14 cgd
299 1.14 cgd static int
300 1.23 wiz binop(void)
301 1.14 cgd {
302 1.16 tls const char *opnd1, *opnd2;
303 1.14 cgd struct t_op const *op;
304 1.14 cgd
305 1.13 jtc opnd1 = *t_wp;
306 1.13 jtc (void) t_lex(*++t_wp);
307 1.14 cgd op = t_wp_op;
308 1.13 jtc
309 1.26 simonb if ((opnd2 = *++t_wp) == NULL)
310 1.14 cgd syntax(op->op_text, "argument expected");
311 1.13 jtc
312 1.14 cgd switch (op->op_num) {
313 1.14 cgd case STREQ:
314 1.14 cgd return strcmp(opnd1, opnd2) == 0;
315 1.14 cgd case STRNE:
316 1.14 cgd return strcmp(opnd1, opnd2) != 0;
317 1.14 cgd case STRLT:
318 1.14 cgd return strcmp(opnd1, opnd2) < 0;
319 1.14 cgd case STRGT:
320 1.14 cgd return strcmp(opnd1, opnd2) > 0;
321 1.14 cgd case INTEQ:
322 1.14 cgd return getn(opnd1) == getn(opnd2);
323 1.14 cgd case INTNE:
324 1.14 cgd return getn(opnd1) != getn(opnd2);
325 1.14 cgd case INTGE:
326 1.14 cgd return getn(opnd1) >= getn(opnd2);
327 1.14 cgd case INTGT:
328 1.14 cgd return getn(opnd1) > getn(opnd2);
329 1.14 cgd case INTLE:
330 1.14 cgd return getn(opnd1) <= getn(opnd2);
331 1.14 cgd case INTLT:
332 1.14 cgd return getn(opnd1) < getn(opnd2);
333 1.14 cgd case FILNT:
334 1.26 simonb return newerf(opnd1, opnd2);
335 1.14 cgd case FILOT:
336 1.26 simonb return olderf(opnd1, opnd2);
337 1.14 cgd case FILEQ:
338 1.26 simonb return equalf(opnd1, opnd2);
339 1.17 christos default:
340 1.19 mycroft abort();
341 1.17 christos /* NOTREACHED */
342 1.1 glass }
343 1.1 glass }
344 1.1 glass
345 1.1 glass static int
346 1.23 wiz filstat(char *nm, enum token mode)
347 1.1 glass {
348 1.13 jtc struct stat s;
349 1.13 jtc
350 1.18 mycroft if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
351 1.13 jtc return 0;
352 1.1 glass
353 1.13 jtc switch (mode) {
354 1.13 jtc case FILRD:
355 1.13 jtc return access(nm, R_OK) == 0;
356 1.13 jtc case FILWR:
357 1.13 jtc return access(nm, W_OK) == 0;
358 1.13 jtc case FILEX:
359 1.13 jtc return access(nm, X_OK) == 0;
360 1.13 jtc case FILEXIST:
361 1.13 jtc return access(nm, F_OK) == 0;
362 1.13 jtc case FILREG:
363 1.18 mycroft return S_ISREG(s.st_mode);
364 1.13 jtc case FILDIR:
365 1.18 mycroft return S_ISDIR(s.st_mode);
366 1.13 jtc case FILCDEV:
367 1.18 mycroft return S_ISCHR(s.st_mode);
368 1.13 jtc case FILBDEV:
369 1.18 mycroft return S_ISBLK(s.st_mode);
370 1.13 jtc case FILFIFO:
371 1.18 mycroft return S_ISFIFO(s.st_mode);
372 1.13 jtc case FILSOCK:
373 1.18 mycroft return S_ISSOCK(s.st_mode);
374 1.18 mycroft case FILSYM:
375 1.18 mycroft return S_ISLNK(s.st_mode);
376 1.13 jtc case FILSUID:
377 1.18 mycroft return (s.st_mode & S_ISUID) != 0;
378 1.13 jtc case FILSGID:
379 1.18 mycroft return (s.st_mode & S_ISGID) != 0;
380 1.13 jtc case FILSTCK:
381 1.18 mycroft return (s.st_mode & S_ISVTX) != 0;
382 1.13 jtc case FILGZ:
383 1.18 mycroft return s.st_size > (off_t)0;
384 1.13 jtc case FILUID:
385 1.13 jtc return s.st_uid == geteuid();
386 1.13 jtc case FILGID:
387 1.13 jtc return s.st_gid == getegid();
388 1.13 jtc default:
389 1.13 jtc return 1;
390 1.13 jtc }
391 1.1 glass }
392 1.1 glass
393 1.13 jtc static enum token
394 1.23 wiz t_lex(char *s)
395 1.1 glass {
396 1.24 wiz struct t_op const *op;
397 1.24 wiz
398 1.24 wiz op = ops;
399 1.1 glass
400 1.13 jtc if (s == 0) {
401 1.26 simonb t_wp_op = NULL;
402 1.13 jtc return EOI;
403 1.13 jtc }
404 1.13 jtc while (op->op_text) {
405 1.13 jtc if (strcmp(s, op->op_text) == 0) {
406 1.21 kleink if ((op->op_type == UNOP && isoperand()) ||
407 1.21 kleink (op->op_num == LPAREN && *(t_wp+1) == 0))
408 1.21 kleink break;
409 1.13 jtc t_wp_op = op;
410 1.13 jtc return op->op_num;
411 1.13 jtc }
412 1.13 jtc op++;
413 1.13 jtc }
414 1.26 simonb t_wp_op = NULL;
415 1.13 jtc return OPERAND;
416 1.21 kleink }
417 1.21 kleink
418 1.21 kleink static int
419 1.23 wiz isoperand(void)
420 1.21 kleink {
421 1.24 wiz struct t_op const *op;
422 1.24 wiz char *s, *t;
423 1.21 kleink
424 1.24 wiz op = ops;
425 1.21 kleink if ((s = *(t_wp+1)) == 0)
426 1.21 kleink return 1;
427 1.21 kleink if ((t = *(t_wp+2)) == 0)
428 1.21 kleink return 0;
429 1.21 kleink while (op->op_text) {
430 1.21 kleink if (strcmp(s, op->op_text) == 0)
431 1.21 kleink return op->op_type == BINOP &&
432 1.21 kleink (t[0] != ')' || t[1] != '\0');
433 1.21 kleink op++;
434 1.21 kleink }
435 1.21 kleink return 0;
436 1.1 glass }
437 1.1 glass
438 1.13 jtc /* atoi with error detection */
439 1.1 glass static int
440 1.23 wiz getn(const char *s)
441 1.1 glass {
442 1.6 alm char *p;
443 1.6 alm long r;
444 1.1 glass
445 1.6 alm errno = 0;
446 1.13 jtc r = strtol(s, &p, 10);
447 1.13 jtc
448 1.6 alm if (errno != 0)
449 1.22 christos error("%s: out of range", s);
450 1.13 jtc
451 1.20 christos while (isspace((unsigned char)*p))
452 1.22 christos p++;
453 1.13 jtc
454 1.13 jtc if (*p)
455 1.22 christos error("%s: bad number", s);
456 1.13 jtc
457 1.13 jtc return (int) r;
458 1.1 glass }
459 1.1 glass
460 1.13 jtc static int
461 1.26 simonb newerf(const char *f1, const char *f2)
462 1.1 glass {
463 1.13 jtc struct stat b1, b2;
464 1.13 jtc
465 1.26 simonb return (stat(f1, &b1) == 0 &&
466 1.26 simonb stat(f2, &b2) == 0 &&
467 1.13 jtc b1.st_mtime > b2.st_mtime);
468 1.1 glass }
469 1.1 glass
470 1.13 jtc static int
471 1.26 simonb olderf(const char *f1, const char *f2)
472 1.1 glass {
473 1.13 jtc struct stat b1, b2;
474 1.13 jtc
475 1.26 simonb return (stat(f1, &b1) == 0 &&
476 1.26 simonb stat(f2, &b2) == 0 &&
477 1.13 jtc b1.st_mtime < b2.st_mtime);
478 1.1 glass }
479 1.1 glass
480 1.13 jtc static int
481 1.26 simonb equalf(const char *f1, const char *f2)
482 1.13 jtc {
483 1.13 jtc struct stat b1, b2;
484 1.1 glass
485 1.26 simonb return (stat(f1, &b1) == 0 &&
486 1.26 simonb stat(f2, &b2) == 0 &&
487 1.13 jtc b1.st_dev == b2.st_dev &&
488 1.13 jtc b1.st_ino == b2.st_ino);
489 1.1 glass }
490