test.c revision 1.17 1 1.17 christos /* $NetBSD: test.c,v 1.17 1997/07/20 21:36:26 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.17 christos __RCSID("$NetBSD: test.c,v 1.17 1997/07/20 21:36:26 christos 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.17 christos static int getn __P((const char *));
154 1.17 christos static int newerf __P((const char *, const char *));
155 1.17 christos static int olderf __P((const char *, const char *));
156 1.17 christos static int equalf __P((const char *, const char *));
157 1.17 christos
158 1.17 christos int main __P((int, char **));
159 1.1 glass
160 1.1 glass int
161 1.1 glass main(argc, argv)
162 1.1 glass int argc;
163 1.13 jtc char **argv;
164 1.1 glass {
165 1.13 jtc int res;
166 1.1 glass
167 1.13 jtc if (strcmp(argv[0], "[") == 0) {
168 1.1 glass if (strcmp(argv[--argc], "]"))
169 1.13 jtc errx(2, "missing ]");
170 1.1 glass argv[argc] = NULL;
171 1.1 glass }
172 1.1 glass
173 1.13 jtc /* Implement special cases from POSIX.2, section 4.62.4 */
174 1.13 jtc switch (argc) {
175 1.13 jtc case 1:
176 1.13 jtc return 1;
177 1.13 jtc case 2:
178 1.13 jtc return (*argv[1] == '\0');
179 1.13 jtc case 3:
180 1.13 jtc if (argv[1][0] == '!' && argv[1][1] == '\0') {
181 1.13 jtc return !(*argv[2] == '\0');
182 1.1 glass }
183 1.14 cgd break;
184 1.14 cgd case 4:
185 1.14 cgd if (argv[1][0] != '!' || argv[1][1] != '\0') {
186 1.14 cgd if (t_lex(argv[2]),
187 1.14 cgd t_wp_op && t_wp_op->op_type == BINOP) {
188 1.14 cgd t_wp = &argv[1];
189 1.14 cgd return (binop() == 0);
190 1.14 cgd }
191 1.14 cgd }
192 1.14 cgd break;
193 1.14 cgd case 5:
194 1.14 cgd if (argv[1][0] == '!' && argv[1][1] == '\0') {
195 1.14 cgd if (t_lex(argv[3]),
196 1.14 cgd t_wp_op && t_wp_op->op_type == BINOP) {
197 1.14 cgd t_wp = &argv[2];
198 1.14 cgd return !(binop() == 0);
199 1.14 cgd }
200 1.14 cgd }
201 1.14 cgd break;
202 1.1 glass }
203 1.1 glass
204 1.14 cgd t_wp = &argv[1];
205 1.13 jtc res = !oexpr(t_lex(*t_wp));
206 1.13 jtc
207 1.13 jtc if (*t_wp != NULL && *++t_wp != NULL)
208 1.13 jtc syntax(*t_wp, "unknown operand");
209 1.13 jtc
210 1.13 jtc return res;
211 1.13 jtc }
212 1.13 jtc
213 1.13 jtc static void
214 1.13 jtc syntax(op, msg)
215 1.17 christos const char *op;
216 1.17 christos const char *msg;
217 1.13 jtc {
218 1.13 jtc if (op && *op)
219 1.13 jtc errx(2, "%s: %s", op, msg);
220 1.13 jtc else
221 1.13 jtc errx(2, "%s", msg);
222 1.13 jtc }
223 1.13 jtc
224 1.13 jtc static int
225 1.13 jtc oexpr(n)
226 1.13 jtc enum token n;
227 1.13 jtc {
228 1.13 jtc int res;
229 1.13 jtc
230 1.13 jtc res = aexpr(n);
231 1.13 jtc if (t_lex(*++t_wp) == BOR)
232 1.13 jtc return oexpr(t_lex(*++t_wp)) || res;
233 1.13 jtc t_wp--;
234 1.13 jtc return res;
235 1.13 jtc }
236 1.13 jtc
237 1.13 jtc static int
238 1.13 jtc aexpr(n)
239 1.13 jtc enum token n;
240 1.13 jtc {
241 1.13 jtc int res;
242 1.13 jtc
243 1.13 jtc res = nexpr(n);
244 1.13 jtc if (t_lex(*++t_wp) == BAND)
245 1.13 jtc return aexpr(t_lex(*++t_wp)) && res;
246 1.13 jtc t_wp--;
247 1.13 jtc return res;
248 1.13 jtc }
249 1.13 jtc
250 1.13 jtc static int
251 1.13 jtc nexpr(n)
252 1.13 jtc enum token n; /* token */
253 1.13 jtc {
254 1.13 jtc if (n == UNOT)
255 1.13 jtc return !nexpr(t_lex(*++t_wp));
256 1.13 jtc return primary(n);
257 1.13 jtc }
258 1.13 jtc
259 1.13 jtc static int
260 1.13 jtc primary(n)
261 1.13 jtc enum token n;
262 1.13 jtc {
263 1.13 jtc int res;
264 1.13 jtc
265 1.13 jtc if (n == EOI)
266 1.13 jtc syntax(NULL, "argument expected");
267 1.13 jtc if (n == LPAREN) {
268 1.13 jtc res = oexpr(t_lex(*++t_wp));
269 1.13 jtc if (t_lex(*++t_wp) != RPAREN)
270 1.13 jtc syntax(NULL, "closing paren expected");
271 1.13 jtc return res;
272 1.1 glass }
273 1.13 jtc if (t_wp_op && t_wp_op->op_type == UNOP) {
274 1.13 jtc /* unary expression */
275 1.13 jtc if (*++t_wp == NULL)
276 1.13 jtc syntax(t_wp_op->op_text, "argument expected");
277 1.13 jtc switch (n) {
278 1.13 jtc case STREZ:
279 1.13 jtc return strlen(*t_wp) == 0;
280 1.13 jtc case STRNZ:
281 1.13 jtc return strlen(*t_wp) != 0;
282 1.13 jtc case FILTT:
283 1.13 jtc return isatty(getn(*t_wp));
284 1.13 jtc default:
285 1.13 jtc return filstat(*t_wp, n);
286 1.1 glass }
287 1.13 jtc }
288 1.14 cgd
289 1.14 cgd if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
290 1.14 cgd return binop();
291 1.14 cgd }
292 1.14 cgd
293 1.14 cgd return strlen(*t_wp) > 0;
294 1.14 cgd }
295 1.14 cgd
296 1.14 cgd static int
297 1.14 cgd binop()
298 1.14 cgd {
299 1.16 tls const char *opnd1, *opnd2;
300 1.14 cgd struct t_op const *op;
301 1.14 cgd
302 1.13 jtc opnd1 = *t_wp;
303 1.13 jtc (void) t_lex(*++t_wp);
304 1.14 cgd op = t_wp_op;
305 1.13 jtc
306 1.14 cgd if ((opnd2 = *++t_wp) == (char *)0)
307 1.14 cgd syntax(op->op_text, "argument expected");
308 1.13 jtc
309 1.14 cgd switch (op->op_num) {
310 1.14 cgd case STREQ:
311 1.14 cgd return strcmp(opnd1, opnd2) == 0;
312 1.14 cgd case STRNE:
313 1.14 cgd return strcmp(opnd1, opnd2) != 0;
314 1.14 cgd case STRLT:
315 1.14 cgd return strcmp(opnd1, opnd2) < 0;
316 1.14 cgd case STRGT:
317 1.14 cgd return strcmp(opnd1, opnd2) > 0;
318 1.14 cgd case INTEQ:
319 1.14 cgd return getn(opnd1) == getn(opnd2);
320 1.14 cgd case INTNE:
321 1.14 cgd return getn(opnd1) != getn(opnd2);
322 1.14 cgd case INTGE:
323 1.14 cgd return getn(opnd1) >= getn(opnd2);
324 1.14 cgd case INTGT:
325 1.14 cgd return getn(opnd1) > getn(opnd2);
326 1.14 cgd case INTLE:
327 1.14 cgd return getn(opnd1) <= getn(opnd2);
328 1.14 cgd case INTLT:
329 1.14 cgd return getn(opnd1) < getn(opnd2);
330 1.14 cgd case FILNT:
331 1.14 cgd return newerf (opnd1, opnd2);
332 1.14 cgd case FILOT:
333 1.14 cgd return olderf (opnd1, opnd2);
334 1.14 cgd case FILEQ:
335 1.14 cgd return equalf (opnd1, opnd2);
336 1.17 christos default:
337 1.17 christos /* NOTREACHED */
338 1.17 christos abort();
339 1.17 christos return 0;
340 1.1 glass }
341 1.1 glass }
342 1.1 glass
343 1.1 glass static int
344 1.13 jtc filstat(nm, mode)
345 1.13 jtc char *nm;
346 1.13 jtc enum token mode;
347 1.1 glass {
348 1.13 jtc struct stat s;
349 1.13 jtc int i;
350 1.13 jtc
351 1.13 jtc if (mode == FILSYM) {
352 1.13 jtc #ifdef S_IFLNK
353 1.13 jtc if (lstat(nm, &s) == 0) {
354 1.13 jtc i = S_IFLNK;
355 1.13 jtc goto filetype;
356 1.13 jtc }
357 1.13 jtc #endif
358 1.13 jtc return 0;
359 1.1 glass }
360 1.1 glass
361 1.13 jtc if (stat(nm, &s) != 0)
362 1.13 jtc return 0;
363 1.1 glass
364 1.13 jtc switch (mode) {
365 1.13 jtc case FILRD:
366 1.13 jtc return access(nm, R_OK) == 0;
367 1.13 jtc case FILWR:
368 1.13 jtc return access(nm, W_OK) == 0;
369 1.13 jtc case FILEX:
370 1.13 jtc return access(nm, X_OK) == 0;
371 1.13 jtc case FILEXIST:
372 1.13 jtc return access(nm, F_OK) == 0;
373 1.13 jtc case FILREG:
374 1.1 glass i = S_IFREG;
375 1.1 glass goto filetype;
376 1.13 jtc case FILDIR:
377 1.1 glass i = S_IFDIR;
378 1.1 glass goto filetype;
379 1.13 jtc case FILCDEV:
380 1.1 glass i = S_IFCHR;
381 1.1 glass goto filetype;
382 1.13 jtc case FILBDEV:
383 1.1 glass i = S_IFBLK;
384 1.1 glass goto filetype;
385 1.13 jtc case FILFIFO:
386 1.13 jtc #ifdef S_IFIFO
387 1.1 glass i = S_IFIFO;
388 1.1 glass goto filetype;
389 1.13 jtc #else
390 1.13 jtc return 0;
391 1.13 jtc #endif
392 1.13 jtc case FILSOCK:
393 1.13 jtc #ifdef S_IFSOCK
394 1.13 jtc i = S_IFSOCK;
395 1.13 jtc goto filetype;
396 1.13 jtc #else
397 1.13 jtc return 0;
398 1.13 jtc #endif
399 1.13 jtc case FILSUID:
400 1.1 glass i = S_ISUID;
401 1.1 glass goto filebit;
402 1.13 jtc case FILSGID:
403 1.1 glass i = S_ISGID;
404 1.1 glass goto filebit;
405 1.13 jtc case FILSTCK:
406 1.1 glass i = S_ISVTX;
407 1.13 jtc goto filebit;
408 1.13 jtc case FILGZ:
409 1.13 jtc return s.st_size > 0L;
410 1.13 jtc case FILUID:
411 1.13 jtc return s.st_uid == geteuid();
412 1.13 jtc case FILGID:
413 1.13 jtc return s.st_gid == getegid();
414 1.13 jtc default:
415 1.13 jtc return 1;
416 1.13 jtc }
417 1.1 glass
418 1.13 jtc filetype:
419 1.13 jtc return ((s.st_mode & S_IFMT) == i);
420 1.1 glass
421 1.13 jtc filebit:
422 1.13 jtc return ((s.st_mode & i) != 0);
423 1.1 glass }
424 1.1 glass
425 1.13 jtc static enum token
426 1.13 jtc t_lex(s)
427 1.16 tls char *s;
428 1.1 glass {
429 1.16 tls struct t_op const *op = ops;
430 1.1 glass
431 1.13 jtc if (s == 0) {
432 1.13 jtc t_wp_op = (struct t_op *)0;
433 1.13 jtc return EOI;
434 1.13 jtc }
435 1.13 jtc while (op->op_text) {
436 1.13 jtc if (strcmp(s, op->op_text) == 0) {
437 1.13 jtc t_wp_op = op;
438 1.13 jtc return op->op_num;
439 1.13 jtc }
440 1.13 jtc op++;
441 1.13 jtc }
442 1.13 jtc t_wp_op = (struct t_op *)0;
443 1.13 jtc return OPERAND;
444 1.1 glass }
445 1.1 glass
446 1.13 jtc /* atoi with error detection */
447 1.1 glass static int
448 1.13 jtc getn(s)
449 1.17 christos const char *s;
450 1.1 glass {
451 1.6 alm char *p;
452 1.6 alm long r;
453 1.1 glass
454 1.6 alm errno = 0;
455 1.13 jtc r = strtol(s, &p, 10);
456 1.13 jtc
457 1.6 alm if (errno != 0)
458 1.13 jtc errx(2, "%s: out of range", s);
459 1.13 jtc
460 1.5 jtc while (isspace(*p))
461 1.13 jtc p++;
462 1.13 jtc
463 1.13 jtc if (*p)
464 1.13 jtc errx(2, "%s: bad number", s);
465 1.13 jtc
466 1.13 jtc return (int) r;
467 1.1 glass }
468 1.1 glass
469 1.13 jtc static int
470 1.13 jtc newerf (f1, f2)
471 1.17 christos const char *f1, *f2;
472 1.1 glass {
473 1.13 jtc struct stat b1, b2;
474 1.13 jtc
475 1.13 jtc return (stat (f1, &b1) == 0 &&
476 1.13 jtc 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.13 jtc olderf (f1, f2)
482 1.17 christos const char *f1, *f2;
483 1.1 glass {
484 1.13 jtc struct stat b1, b2;
485 1.13 jtc
486 1.13 jtc return (stat (f1, &b1) == 0 &&
487 1.13 jtc stat (f2, &b2) == 0 &&
488 1.13 jtc b1.st_mtime < b2.st_mtime);
489 1.1 glass }
490 1.1 glass
491 1.13 jtc static int
492 1.13 jtc equalf (f1, f2)
493 1.17 christos const char *f1, *f2;
494 1.13 jtc {
495 1.13 jtc struct stat b1, b2;
496 1.1 glass
497 1.13 jtc return (stat (f1, &b1) == 0 &&
498 1.13 jtc stat (f2, &b2) == 0 &&
499 1.13 jtc b1.st_dev == b2.st_dev &&
500 1.13 jtc b1.st_ino == b2.st_ino);
501 1.1 glass }
502