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