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