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