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