test.c revision 1.14 1 1.13 jtc /*
2 1.13 jtc * test(1); version 7-like -- author Erik Baalbergen
3 1.13 jtc * modified by Eric Gisin to be used as built-in.
4 1.13 jtc * modified by Arnold Robbins to add SVR3 compatibility
5 1.13 jtc * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
6 1.13 jtc * modified by J.T. Conklin for NetBSD.
7 1.1 glass *
8 1.13 jtc * This program is in the Public Domain.
9 1.1 glass */
10 1.1 glass
11 1.1 glass #ifndef lint
12 1.14 cgd static char *rcsid = "$Id: test.c,v 1.14 1994/07/07 19:08:11 cgd Exp $";
13 1.13 jtc #endif
14 1.1 glass
15 1.1 glass #include <sys/types.h>
16 1.1 glass #include <sys/stat.h>
17 1.13 jtc #include <unistd.h>
18 1.13 jtc #include <ctype.h>
19 1.1 glass #include <errno.h>
20 1.13 jtc #include <stdio.h>
21 1.1 glass #include <stdlib.h>
22 1.1 glass #include <string.h>
23 1.13 jtc #include <err.h>
24 1.1 glass
25 1.13 jtc /* test(1) accepts the following grammar:
26 1.13 jtc oexpr ::= aexpr | aexpr "-o" oexpr ;
27 1.13 jtc aexpr ::= nexpr | nexpr "-a" aexpr ;
28 1.14 cgd nexpr ::= primary | "!" primary
29 1.13 jtc primary ::= unary-operator operand
30 1.13 jtc | operand binary-operator operand
31 1.13 jtc | operand
32 1.13 jtc | "(" oexpr ")"
33 1.13 jtc ;
34 1.13 jtc unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
35 1.13 jtc "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
36 1.13 jtc
37 1.13 jtc binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
38 1.13 jtc "-nt"|"-ot"|"-ef";
39 1.13 jtc operand ::= <any legal UNIX file name>
40 1.13 jtc */
41 1.13 jtc
42 1.13 jtc enum token {
43 1.13 jtc EOI,
44 1.13 jtc FILRD,
45 1.13 jtc FILWR,
46 1.13 jtc FILEX,
47 1.13 jtc FILEXIST,
48 1.13 jtc FILREG,
49 1.13 jtc FILDIR,
50 1.13 jtc FILCDEV,
51 1.13 jtc FILBDEV,
52 1.13 jtc FILFIFO,
53 1.13 jtc FILSOCK,
54 1.13 jtc FILSYM,
55 1.13 jtc FILGZ,
56 1.13 jtc FILTT,
57 1.13 jtc FILSUID,
58 1.13 jtc FILSGID,
59 1.13 jtc FILSTCK,
60 1.13 jtc FILNT,
61 1.13 jtc FILOT,
62 1.13 jtc FILEQ,
63 1.13 jtc FILUID,
64 1.13 jtc FILGID,
65 1.13 jtc STREZ,
66 1.13 jtc STRNZ,
67 1.13 jtc STREQ,
68 1.13 jtc STRNE,
69 1.13 jtc STRLT,
70 1.13 jtc STRGT,
71 1.13 jtc INTEQ,
72 1.13 jtc INTNE,
73 1.13 jtc INTGE,
74 1.13 jtc INTGT,
75 1.13 jtc INTLE,
76 1.13 jtc INTLT,
77 1.13 jtc UNOT,
78 1.13 jtc BAND,
79 1.13 jtc BOR,
80 1.13 jtc LPAREN,
81 1.13 jtc RPAREN,
82 1.13 jtc OPERAND
83 1.13 jtc };
84 1.1 glass
85 1.13 jtc enum token_types {
86 1.13 jtc UNOP,
87 1.13 jtc BINOP,
88 1.13 jtc BUNOP,
89 1.13 jtc BBINOP,
90 1.13 jtc PAREN
91 1.1 glass };
92 1.1 glass
93 1.13 jtc struct t_op {
94 1.13 jtc const char *op_text;
95 1.13 jtc short op_num, op_type;
96 1.13 jtc } const ops [] = {
97 1.13 jtc {"-r", FILRD, UNOP},
98 1.13 jtc {"-w", FILWR, UNOP},
99 1.13 jtc {"-x", FILEX, UNOP},
100 1.13 jtc {"-e", FILEXIST,UNOP},
101 1.13 jtc {"-f", FILREG, UNOP},
102 1.13 jtc {"-d", FILDIR, UNOP},
103 1.13 jtc {"-c", FILCDEV,UNOP},
104 1.13 jtc {"-b", FILBDEV,UNOP},
105 1.13 jtc {"-p", FILFIFO,UNOP},
106 1.13 jtc {"-u", FILSUID,UNOP},
107 1.13 jtc {"-g", FILSGID,UNOP},
108 1.13 jtc {"-k", FILSTCK,UNOP},
109 1.13 jtc {"-s", FILGZ, UNOP},
110 1.13 jtc {"-t", FILTT, UNOP},
111 1.13 jtc {"-z", STREZ, UNOP},
112 1.13 jtc {"-n", STRNZ, UNOP},
113 1.13 jtc {"-h", FILSYM, UNOP}, /* for backwards compat */
114 1.13 jtc {"-O", FILUID, UNOP},
115 1.13 jtc {"-G", FILGID, UNOP},
116 1.13 jtc {"-L", FILSYM, UNOP},
117 1.13 jtc {"-S", FILSOCK,UNOP},
118 1.13 jtc {"=", STREQ, BINOP},
119 1.13 jtc {"!=", STRNE, BINOP},
120 1.13 jtc {"<", STRLT, BINOP},
121 1.13 jtc {">", STRGT, BINOP},
122 1.13 jtc {"-eq", INTEQ, BINOP},
123 1.13 jtc {"-ne", INTNE, BINOP},
124 1.13 jtc {"-ge", INTGE, BINOP},
125 1.13 jtc {"-gt", INTGT, BINOP},
126 1.13 jtc {"-le", INTLE, BINOP},
127 1.13 jtc {"-lt", INTLT, BINOP},
128 1.13 jtc {"-nt", FILNT, BINOP},
129 1.13 jtc {"-ot", FILOT, BINOP},
130 1.13 jtc {"-ef", FILEQ, BINOP},
131 1.13 jtc {"!", UNOT, BUNOP},
132 1.13 jtc {"-a", BAND, BBINOP},
133 1.13 jtc {"-o", BOR, BBINOP},
134 1.13 jtc {"(", LPAREN, PAREN},
135 1.13 jtc {")", RPAREN, PAREN},
136 1.13 jtc {0, 0, 0}
137 1.1 glass };
138 1.1 glass
139 1.13 jtc char **t_wp;
140 1.13 jtc struct t_op const *t_wp_op;
141 1.1 glass
142 1.13 jtc static enum token t_lex();
143 1.13 jtc static int oexpr();
144 1.13 jtc static int aexpr();
145 1.13 jtc static int nexpr();
146 1.14 cgd static int binop();
147 1.13 jtc static int primary();
148 1.13 jtc static int filstat();
149 1.13 jtc static int getn();
150 1.13 jtc static int newerf();
151 1.13 jtc static int olderf();
152 1.13 jtc static int equalf();
153 1.13 jtc static void syntax();
154 1.1 glass
155 1.1 glass int
156 1.1 glass main(argc, argv)
157 1.1 glass int argc;
158 1.13 jtc char **argv;
159 1.1 glass {
160 1.13 jtc int res;
161 1.1 glass
162 1.13 jtc if (strcmp(argv[0], "[") == 0) {
163 1.1 glass if (strcmp(argv[--argc], "]"))
164 1.13 jtc errx(2, "missing ]");
165 1.1 glass argv[argc] = NULL;
166 1.1 glass }
167 1.1 glass
168 1.13 jtc /* Implement special cases from POSIX.2, section 4.62.4 */
169 1.13 jtc switch (argc) {
170 1.13 jtc case 1:
171 1.13 jtc return 1;
172 1.13 jtc case 2:
173 1.13 jtc return (*argv[1] == '\0');
174 1.13 jtc case 3:
175 1.13 jtc if (argv[1][0] == '!' && argv[1][1] == '\0') {
176 1.13 jtc return !(*argv[2] == '\0');
177 1.1 glass }
178 1.14 cgd break;
179 1.14 cgd case 4:
180 1.14 cgd if (argv[1][0] != '!' || argv[1][1] != '\0') {
181 1.14 cgd if (t_lex(argv[2]),
182 1.14 cgd t_wp_op && t_wp_op->op_type == BINOP) {
183 1.14 cgd t_wp = &argv[1];
184 1.14 cgd return (binop() == 0);
185 1.14 cgd }
186 1.14 cgd }
187 1.14 cgd break;
188 1.14 cgd case 5:
189 1.14 cgd if (argv[1][0] == '!' && argv[1][1] == '\0') {
190 1.14 cgd if (t_lex(argv[3]),
191 1.14 cgd t_wp_op && t_wp_op->op_type == BINOP) {
192 1.14 cgd t_wp = &argv[2];
193 1.14 cgd return !(binop() == 0);
194 1.14 cgd }
195 1.14 cgd }
196 1.14 cgd break;
197 1.1 glass }
198 1.1 glass
199 1.14 cgd t_wp = &argv[1];
200 1.13 jtc res = !oexpr(t_lex(*t_wp));
201 1.13 jtc
202 1.13 jtc if (*t_wp != NULL && *++t_wp != NULL)
203 1.13 jtc syntax(*t_wp, "unknown operand");
204 1.13 jtc
205 1.13 jtc return res;
206 1.13 jtc }
207 1.13 jtc
208 1.13 jtc static void
209 1.13 jtc syntax(op, msg)
210 1.13 jtc char *op;
211 1.13 jtc char *msg;
212 1.13 jtc {
213 1.13 jtc if (op && *op)
214 1.13 jtc errx(2, "%s: %s", op, msg);
215 1.13 jtc else
216 1.13 jtc errx(2, "%s", msg);
217 1.13 jtc }
218 1.13 jtc
219 1.13 jtc static int
220 1.13 jtc oexpr(n)
221 1.13 jtc enum token n;
222 1.13 jtc {
223 1.13 jtc int res;
224 1.13 jtc
225 1.13 jtc res = aexpr(n);
226 1.13 jtc if (t_lex(*++t_wp) == BOR)
227 1.13 jtc return oexpr(t_lex(*++t_wp)) || res;
228 1.13 jtc t_wp--;
229 1.13 jtc return res;
230 1.13 jtc }
231 1.13 jtc
232 1.13 jtc static int
233 1.13 jtc aexpr(n)
234 1.13 jtc 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.13 jtc nexpr(n)
247 1.13 jtc enum token n; /* token */
248 1.13 jtc {
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.13 jtc primary(n)
256 1.13 jtc enum token n;
257 1.13 jtc {
258 1.13 jtc int res;
259 1.13 jtc
260 1.13 jtc if (n == EOI)
261 1.13 jtc syntax(NULL, "argument expected");
262 1.13 jtc if (n == LPAREN) {
263 1.13 jtc res = oexpr(t_lex(*++t_wp));
264 1.13 jtc if (t_lex(*++t_wp) != RPAREN)
265 1.13 jtc syntax(NULL, "closing paren expected");
266 1.13 jtc return res;
267 1.1 glass }
268 1.13 jtc if (t_wp_op && t_wp_op->op_type == UNOP) {
269 1.13 jtc /* unary expression */
270 1.13 jtc if (*++t_wp == NULL)
271 1.13 jtc syntax(t_wp_op->op_text, "argument expected");
272 1.13 jtc switch (n) {
273 1.13 jtc case STREZ:
274 1.13 jtc return strlen(*t_wp) == 0;
275 1.13 jtc case STRNZ:
276 1.13 jtc return strlen(*t_wp) != 0;
277 1.13 jtc case FILTT:
278 1.13 jtc return isatty(getn(*t_wp));
279 1.13 jtc default:
280 1.13 jtc return filstat(*t_wp, n);
281 1.1 glass }
282 1.13 jtc }
283 1.14 cgd
284 1.14 cgd if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
285 1.14 cgd return binop();
286 1.14 cgd }
287 1.14 cgd
288 1.14 cgd return strlen(*t_wp) > 0;
289 1.14 cgd }
290 1.14 cgd
291 1.14 cgd static int
292 1.14 cgd binop()
293 1.14 cgd {
294 1.14 cgd register const char *opnd1, *opnd2;
295 1.14 cgd struct t_op const *op;
296 1.14 cgd
297 1.13 jtc opnd1 = *t_wp;
298 1.13 jtc (void) t_lex(*++t_wp);
299 1.14 cgd op = t_wp_op;
300 1.13 jtc
301 1.14 cgd if ((opnd2 = *++t_wp) == (char *)0)
302 1.14 cgd syntax(op->op_text, "argument expected");
303 1.13 jtc
304 1.14 cgd switch (op->op_num) {
305 1.14 cgd case STREQ:
306 1.14 cgd return strcmp(opnd1, opnd2) == 0;
307 1.14 cgd case STRNE:
308 1.14 cgd return strcmp(opnd1, opnd2) != 0;
309 1.14 cgd case STRLT:
310 1.14 cgd return strcmp(opnd1, opnd2) < 0;
311 1.14 cgd case STRGT:
312 1.14 cgd return strcmp(opnd1, opnd2) > 0;
313 1.14 cgd case INTEQ:
314 1.14 cgd return getn(opnd1) == getn(opnd2);
315 1.14 cgd case INTNE:
316 1.14 cgd return getn(opnd1) != getn(opnd2);
317 1.14 cgd case INTGE:
318 1.14 cgd return getn(opnd1) >= getn(opnd2);
319 1.14 cgd case INTGT:
320 1.14 cgd return getn(opnd1) > getn(opnd2);
321 1.14 cgd case INTLE:
322 1.14 cgd return getn(opnd1) <= getn(opnd2);
323 1.14 cgd case INTLT:
324 1.14 cgd return getn(opnd1) < getn(opnd2);
325 1.14 cgd case FILNT:
326 1.14 cgd return newerf (opnd1, opnd2);
327 1.14 cgd case FILOT:
328 1.14 cgd return olderf (opnd1, opnd2);
329 1.14 cgd case FILEQ:
330 1.14 cgd return equalf (opnd1, opnd2);
331 1.1 glass }
332 1.14 cgd /* NOTREACHED */
333 1.1 glass }
334 1.1 glass
335 1.1 glass static int
336 1.13 jtc filstat(nm, mode)
337 1.13 jtc char *nm;
338 1.13 jtc enum token mode;
339 1.1 glass {
340 1.13 jtc struct stat s;
341 1.13 jtc int i;
342 1.13 jtc
343 1.13 jtc if (mode == FILSYM) {
344 1.13 jtc #ifdef S_IFLNK
345 1.13 jtc if (lstat(nm, &s) == 0) {
346 1.13 jtc i = S_IFLNK;
347 1.13 jtc goto filetype;
348 1.13 jtc }
349 1.13 jtc #endif
350 1.13 jtc return 0;
351 1.1 glass }
352 1.1 glass
353 1.13 jtc if (stat(nm, &s) != 0)
354 1.13 jtc return 0;
355 1.1 glass
356 1.13 jtc switch (mode) {
357 1.13 jtc case FILRD:
358 1.13 jtc return access(nm, R_OK) == 0;
359 1.13 jtc case FILWR:
360 1.13 jtc return access(nm, W_OK) == 0;
361 1.13 jtc case FILEX:
362 1.13 jtc return access(nm, X_OK) == 0;
363 1.13 jtc case FILEXIST:
364 1.13 jtc return access(nm, F_OK) == 0;
365 1.13 jtc case FILREG:
366 1.1 glass i = S_IFREG;
367 1.1 glass goto filetype;
368 1.13 jtc case FILDIR:
369 1.1 glass i = S_IFDIR;
370 1.1 glass goto filetype;
371 1.13 jtc case FILCDEV:
372 1.1 glass i = S_IFCHR;
373 1.1 glass goto filetype;
374 1.13 jtc case FILBDEV:
375 1.1 glass i = S_IFBLK;
376 1.1 glass goto filetype;
377 1.13 jtc case FILFIFO:
378 1.13 jtc #ifdef S_IFIFO
379 1.1 glass i = S_IFIFO;
380 1.1 glass goto filetype;
381 1.13 jtc #else
382 1.13 jtc return 0;
383 1.13 jtc #endif
384 1.13 jtc case FILSOCK:
385 1.13 jtc #ifdef S_IFSOCK
386 1.13 jtc i = S_IFSOCK;
387 1.13 jtc goto filetype;
388 1.13 jtc #else
389 1.13 jtc return 0;
390 1.13 jtc #endif
391 1.13 jtc case FILSUID:
392 1.1 glass i = S_ISUID;
393 1.1 glass goto filebit;
394 1.13 jtc case FILSGID:
395 1.1 glass i = S_ISGID;
396 1.1 glass goto filebit;
397 1.13 jtc case FILSTCK:
398 1.1 glass i = S_ISVTX;
399 1.13 jtc goto filebit;
400 1.13 jtc case FILGZ:
401 1.13 jtc return s.st_size > 0L;
402 1.13 jtc case FILUID:
403 1.13 jtc return s.st_uid == geteuid();
404 1.13 jtc case FILGID:
405 1.13 jtc return s.st_gid == getegid();
406 1.13 jtc default:
407 1.13 jtc return 1;
408 1.13 jtc }
409 1.1 glass
410 1.13 jtc filetype:
411 1.13 jtc return ((s.st_mode & S_IFMT) == i);
412 1.1 glass
413 1.13 jtc filebit:
414 1.13 jtc return ((s.st_mode & i) != 0);
415 1.1 glass }
416 1.1 glass
417 1.13 jtc static enum token
418 1.13 jtc t_lex(s)
419 1.13 jtc register char *s;
420 1.1 glass {
421 1.13 jtc register struct t_op const *op = ops;
422 1.1 glass
423 1.13 jtc if (s == 0) {
424 1.13 jtc t_wp_op = (struct t_op *)0;
425 1.13 jtc return EOI;
426 1.13 jtc }
427 1.13 jtc while (op->op_text) {
428 1.13 jtc if (strcmp(s, op->op_text) == 0) {
429 1.13 jtc t_wp_op = op;
430 1.13 jtc return op->op_num;
431 1.13 jtc }
432 1.13 jtc op++;
433 1.13 jtc }
434 1.13 jtc t_wp_op = (struct t_op *)0;
435 1.13 jtc return OPERAND;
436 1.1 glass }
437 1.1 glass
438 1.13 jtc /* atoi with error detection */
439 1.1 glass static int
440 1.13 jtc getn(s)
441 1.13 jtc 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.13 jtc errx(2, "%s: out of range", s);
451 1.13 jtc
452 1.5 jtc while (isspace(*p))
453 1.13 jtc p++;
454 1.13 jtc
455 1.13 jtc if (*p)
456 1.13 jtc errx(2, "%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.13 jtc newerf (f1, f2)
463 1.13 jtc char *f1, *f2;
464 1.1 glass {
465 1.13 jtc struct stat b1, b2;
466 1.13 jtc
467 1.13 jtc return (stat (f1, &b1) == 0 &&
468 1.13 jtc stat (f2, &b2) == 0 &&
469 1.13 jtc b1.st_mtime > b2.st_mtime);
470 1.1 glass }
471 1.1 glass
472 1.13 jtc static int
473 1.13 jtc olderf (f1, f2)
474 1.13 jtc char *f1, *f2;
475 1.1 glass {
476 1.13 jtc struct stat b1, b2;
477 1.13 jtc
478 1.13 jtc return (stat (f1, &b1) == 0 &&
479 1.13 jtc stat (f2, &b2) == 0 &&
480 1.13 jtc b1.st_mtime < b2.st_mtime);
481 1.1 glass }
482 1.1 glass
483 1.13 jtc static int
484 1.13 jtc equalf (f1, f2)
485 1.13 jtc char *f1, *f2;
486 1.13 jtc {
487 1.13 jtc struct stat b1, b2;
488 1.1 glass
489 1.13 jtc return (stat (f1, &b1) == 0 &&
490 1.13 jtc stat (f2, &b2) == 0 &&
491 1.13 jtc b1.st_dev == b2.st_dev &&
492 1.13 jtc b1.st_ino == b2.st_ino);
493 1.1 glass }
494