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