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