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