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