xref: /netbsd-src/bin/test/test.c (revision 6ea46cb5e46c49111a6ecf3bcbe3c7e2730fe9f6)
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