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