test.c revision 1.19 1 1.19 mycroft /* $NetBSD: test.c,v 1.19 1998/07/28 11:41:59 mycroft 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.19 mycroft __RCSID("$NetBSD: test.c,v 1.19 1998/07/28 11:41:59 mycroft 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.19 mycroft abort();
338 1.17 christos /* NOTREACHED */
339 1.1 glass }
340 1.1 glass }
341 1.1 glass
342 1.1 glass static int
343 1.13 jtc filstat(nm, mode)
344 1.13 jtc char *nm;
345 1.13 jtc enum token mode;
346 1.1 glass {
347 1.13 jtc struct stat s;
348 1.13 jtc
349 1.18 mycroft if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
350 1.13 jtc return 0;
351 1.1 glass
352 1.13 jtc switch (mode) {
353 1.13 jtc case FILRD:
354 1.13 jtc return access(nm, R_OK) == 0;
355 1.13 jtc case FILWR:
356 1.13 jtc return access(nm, W_OK) == 0;
357 1.13 jtc case FILEX:
358 1.13 jtc return access(nm, X_OK) == 0;
359 1.13 jtc case FILEXIST:
360 1.13 jtc return access(nm, F_OK) == 0;
361 1.13 jtc case FILREG:
362 1.18 mycroft return S_ISREG(s.st_mode);
363 1.13 jtc case FILDIR:
364 1.18 mycroft return S_ISDIR(s.st_mode);
365 1.13 jtc case FILCDEV:
366 1.18 mycroft return S_ISCHR(s.st_mode);
367 1.13 jtc case FILBDEV:
368 1.18 mycroft return S_ISBLK(s.st_mode);
369 1.13 jtc case FILFIFO:
370 1.18 mycroft return S_ISFIFO(s.st_mode);
371 1.13 jtc case FILSOCK:
372 1.18 mycroft return S_ISSOCK(s.st_mode);
373 1.18 mycroft case FILSYM:
374 1.18 mycroft return S_ISLNK(s.st_mode);
375 1.13 jtc case FILSUID:
376 1.18 mycroft return (s.st_mode & S_ISUID) != 0;
377 1.13 jtc case FILSGID:
378 1.18 mycroft return (s.st_mode & S_ISGID) != 0;
379 1.13 jtc case FILSTCK:
380 1.18 mycroft return (s.st_mode & S_ISVTX) != 0;
381 1.13 jtc case FILGZ:
382 1.18 mycroft return s.st_size > (off_t)0;
383 1.13 jtc case FILUID:
384 1.13 jtc return s.st_uid == geteuid();
385 1.13 jtc case FILGID:
386 1.13 jtc return s.st_gid == getegid();
387 1.13 jtc default:
388 1.13 jtc return 1;
389 1.13 jtc }
390 1.1 glass }
391 1.1 glass
392 1.13 jtc static enum token
393 1.13 jtc t_lex(s)
394 1.16 tls char *s;
395 1.1 glass {
396 1.16 tls struct t_op const *op = ops;
397 1.1 glass
398 1.13 jtc if (s == 0) {
399 1.13 jtc t_wp_op = (struct t_op *)0;
400 1.13 jtc return EOI;
401 1.13 jtc }
402 1.13 jtc while (op->op_text) {
403 1.13 jtc if (strcmp(s, op->op_text) == 0) {
404 1.13 jtc t_wp_op = op;
405 1.13 jtc return op->op_num;
406 1.13 jtc }
407 1.13 jtc op++;
408 1.13 jtc }
409 1.13 jtc t_wp_op = (struct t_op *)0;
410 1.13 jtc return OPERAND;
411 1.1 glass }
412 1.1 glass
413 1.13 jtc /* atoi with error detection */
414 1.1 glass static int
415 1.13 jtc getn(s)
416 1.17 christos const char *s;
417 1.1 glass {
418 1.6 alm char *p;
419 1.6 alm long r;
420 1.1 glass
421 1.6 alm errno = 0;
422 1.13 jtc r = strtol(s, &p, 10);
423 1.13 jtc
424 1.6 alm if (errno != 0)
425 1.13 jtc errx(2, "%s: out of range", s);
426 1.13 jtc
427 1.5 jtc while (isspace(*p))
428 1.13 jtc p++;
429 1.13 jtc
430 1.13 jtc if (*p)
431 1.13 jtc errx(2, "%s: bad number", s);
432 1.13 jtc
433 1.13 jtc return (int) r;
434 1.1 glass }
435 1.1 glass
436 1.13 jtc static int
437 1.13 jtc newerf (f1, f2)
438 1.17 christos const char *f1, *f2;
439 1.1 glass {
440 1.13 jtc struct stat b1, b2;
441 1.13 jtc
442 1.13 jtc return (stat (f1, &b1) == 0 &&
443 1.13 jtc stat (f2, &b2) == 0 &&
444 1.13 jtc b1.st_mtime > b2.st_mtime);
445 1.1 glass }
446 1.1 glass
447 1.13 jtc static int
448 1.13 jtc olderf (f1, f2)
449 1.17 christos const char *f1, *f2;
450 1.1 glass {
451 1.13 jtc struct stat b1, b2;
452 1.13 jtc
453 1.13 jtc return (stat (f1, &b1) == 0 &&
454 1.13 jtc stat (f2, &b2) == 0 &&
455 1.13 jtc b1.st_mtime < b2.st_mtime);
456 1.1 glass }
457 1.1 glass
458 1.13 jtc static int
459 1.13 jtc equalf (f1, f2)
460 1.17 christos const char *f1, *f2;
461 1.13 jtc {
462 1.13 jtc struct stat b1, b2;
463 1.1 glass
464 1.13 jtc return (stat (f1, &b1) == 0 &&
465 1.13 jtc stat (f2, &b2) == 0 &&
466 1.13 jtc b1.st_dev == b2.st_dev &&
467 1.13 jtc b1.st_ino == b2.st_ino);
468 1.1 glass }
469