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