test.c revision 1.32 1 /* $NetBSD: test.c,v 1.32 2007/05/24 18:47:08 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.32 2007/05/24 18:47:08 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_wp == NULL)
244 return res;
245 if (t_lex(*++t_wp) == BOR)
246 return oexpr(t_lex(*++t_wp)) || res;
247 t_wp--;
248 return res;
249 }
250
251 static int
252 aexpr(enum token n)
253 {
254 int res;
255
256 res = nexpr(n);
257 if (*t_wp == NULL)
258 return res;
259 if (t_lex(*++t_wp) == BAND)
260 return aexpr(t_lex(*++t_wp)) && res;
261 t_wp--;
262 return res;
263 }
264
265 static int
266 nexpr(enum token n)
267 {
268
269 if (n == UNOT)
270 return !nexpr(t_lex(*++t_wp));
271 return primary(n);
272 }
273
274 static int
275 primary(enum token n)
276 {
277 enum token nn;
278 int res;
279
280 if (n == EOI)
281 return 0; /* missing expression */
282 if (n == LPAREN) {
283 if ((nn = t_lex(*++t_wp)) == RPAREN)
284 return 0; /* missing expression */
285 res = oexpr(nn);
286 if (t_lex(*++t_wp) != RPAREN)
287 syntax(NULL, "closing paren expected");
288 return res;
289 }
290 if (t_wp_op && t_wp_op->op_type == UNOP) {
291 /* unary expression */
292 if (*++t_wp == NULL)
293 syntax(t_wp_op->op_text, "argument expected");
294 switch (n) {
295 case STREZ:
296 return strlen(*t_wp) == 0;
297 case STRNZ:
298 return strlen(*t_wp) != 0;
299 case FILTT:
300 return isatty(getn(*t_wp));
301 default:
302 return filstat(*t_wp, n);
303 }
304 }
305
306 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
307 return binop();
308 }
309
310 return strlen(*t_wp) > 0;
311 }
312
313 static int
314 binop(void)
315 {
316 const char *opnd1, *opnd2;
317 struct t_op const *op;
318
319 opnd1 = *t_wp;
320 (void) t_lex(*++t_wp);
321 op = t_wp_op;
322
323 if ((opnd2 = *++t_wp) == NULL)
324 syntax(op->op_text, "argument expected");
325
326 switch (op->op_num) {
327 case STREQ:
328 return strcmp(opnd1, opnd2) == 0;
329 case STRNE:
330 return strcmp(opnd1, opnd2) != 0;
331 case STRLT:
332 return strcmp(opnd1, opnd2) < 0;
333 case STRGT:
334 return strcmp(opnd1, opnd2) > 0;
335 case INTEQ:
336 return getn(opnd1) == getn(opnd2);
337 case INTNE:
338 return getn(opnd1) != getn(opnd2);
339 case INTGE:
340 return getn(opnd1) >= getn(opnd2);
341 case INTGT:
342 return getn(opnd1) > getn(opnd2);
343 case INTLE:
344 return getn(opnd1) <= getn(opnd2);
345 case INTLT:
346 return getn(opnd1) < getn(opnd2);
347 case FILNT:
348 return newerf(opnd1, opnd2);
349 case FILOT:
350 return olderf(opnd1, opnd2);
351 case FILEQ:
352 return equalf(opnd1, opnd2);
353 default:
354 abort();
355 /* NOTREACHED */
356 }
357 }
358
359 static int
360 filstat(char *nm, enum token mode)
361 {
362 struct stat s;
363
364 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
365 return 0;
366
367 switch (mode) {
368 case FILRD:
369 return access(nm, R_OK) == 0;
370 case FILWR:
371 return access(nm, W_OK) == 0;
372 case FILEX:
373 return access(nm, X_OK) == 0;
374 case FILEXIST:
375 return access(nm, F_OK) == 0;
376 case FILREG:
377 return S_ISREG(s.st_mode);
378 case FILDIR:
379 return S_ISDIR(s.st_mode);
380 case FILCDEV:
381 return S_ISCHR(s.st_mode);
382 case FILBDEV:
383 return S_ISBLK(s.st_mode);
384 case FILFIFO:
385 return S_ISFIFO(s.st_mode);
386 case FILSOCK:
387 return S_ISSOCK(s.st_mode);
388 case FILSYM:
389 return S_ISLNK(s.st_mode);
390 case FILSUID:
391 return (s.st_mode & S_ISUID) != 0;
392 case FILSGID:
393 return (s.st_mode & S_ISGID) != 0;
394 case FILSTCK:
395 return (s.st_mode & S_ISVTX) != 0;
396 case FILGZ:
397 return s.st_size > (off_t)0;
398 case FILUID:
399 return s.st_uid == geteuid();
400 case FILGID:
401 return s.st_gid == getegid();
402 default:
403 return 1;
404 }
405 }
406
407 #define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text
408
409 static int
410 compare1(const void *va, const void *vb)
411 {
412 const unsigned char *a = va;
413 const unsigned char *b = VTOC(vb);
414
415 return a[0] - b[0];
416 }
417
418 static int
419 compare2(const void *va, const void *vb)
420 {
421 const unsigned char *a = va;
422 const unsigned char *b = VTOC(vb);
423 int z = a[0] - b[0];
424
425 return z ? z : (a[1] - b[1]);
426 }
427
428 static struct t_op const *
429 findop(const char *s)
430 {
431 if (s[0] == '-') {
432 if (s[1] == '\0')
433 return NULL;
434 if (s[2] == '\0')
435 return bsearch(s + 1, mop2, __arraycount(mop2),
436 sizeof(*mop2), compare1);
437 else if (s[3] != '\0')
438 return NULL;
439 else
440 return bsearch(s + 1, mop3, __arraycount(mop3),
441 sizeof(*mop3), compare2);
442 } else {
443 if (s[1] == '\0')
444 return bsearch(s, cop, __arraycount(cop), sizeof(*cop),
445 compare1);
446 else if (strcmp(s, cop2[0].op_text) == 0)
447 return cop2;
448 else
449 return NULL;
450 }
451 }
452
453 static enum token
454 t_lex(char *s)
455 {
456 struct t_op const *op;
457
458 if (s == NULL) {
459 t_wp_op = NULL;
460 return EOI;
461 }
462
463 if ((op = findop(s)) != NULL) {
464 if (!((op->op_type == UNOP && isoperand()) ||
465 (op->op_num == LPAREN && *(t_wp+1) == 0))) {
466 t_wp_op = op;
467 return op->op_num;
468 }
469 }
470 t_wp_op = NULL;
471 return OPERAND;
472 }
473
474 static int
475 isoperand(void)
476 {
477 struct t_op const *op;
478 char *s, *t;
479
480 if ((s = *(t_wp+1)) == 0)
481 return 1;
482 if ((t = *(t_wp+2)) == 0)
483 return 0;
484 if ((op = findop(s)) != NULL)
485 return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
486 return 0;
487 }
488
489 /* atoi with error detection */
490 static int
491 getn(const char *s)
492 {
493 char *p;
494 long r;
495
496 errno = 0;
497 r = strtol(s, &p, 10);
498
499 if (errno != 0)
500 error("%s: out of range", s);
501
502 while (isspace((unsigned char)*p))
503 p++;
504
505 if (*p)
506 error("%s: bad number", s);
507
508 return (int) r;
509 }
510
511 static int
512 newerf(const char *f1, const char *f2)
513 {
514 struct stat b1, b2;
515
516 return (stat(f1, &b1) == 0 &&
517 stat(f2, &b2) == 0 &&
518 b1.st_mtime > b2.st_mtime);
519 }
520
521 static int
522 olderf(const char *f1, const char *f2)
523 {
524 struct stat b1, b2;
525
526 return (stat(f1, &b1) == 0 &&
527 stat(f2, &b2) == 0 &&
528 b1.st_mtime < b2.st_mtime);
529 }
530
531 static int
532 equalf(const char *f1, const char *f2)
533 {
534 struct stat b1, b2;
535
536 return (stat(f1, &b1) == 0 &&
537 stat(f2, &b2) == 0 &&
538 b1.st_dev == b2.st_dev &&
539 b1.st_ino == b2.st_ino);
540 }
541