xref: /freebsd/bin/test/test.c (revision 3a31b7eb32ad60e1e05b2b2e184ff47e4afbb874)
1 /*	$NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink 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 #ifndef lint
14 static const char rcsid[] =
15   "$FreeBSD$";
16 #endif /* not lint */
17 
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 
21 #include <ctype.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #ifdef SHELL
32 #define main testcmd
33 #include "bltin/bltin.h"
34 #else
35 static void error(const char *, ...) __attribute__((__noreturn__));
36 
37 static void
38 #ifdef __STDC__
39 error(const char *msg, ...)
40 #else
41 error(va_alist)
42 	va_dcl
43 #endif
44 {
45 	va_list ap;
46 #ifndef __STDC__
47 	const char *msg;
48 
49 	va_start(ap);
50 	msg = va_arg(ap, const char *);
51 #else
52 	va_start(ap, msg);
53 #endif
54 	verrx(2, msg, ap);
55 	/*NOTREACHED*/
56 	va_end(ap);
57 }
58 #endif
59 
60 /* test(1) accepts the following grammar:
61 	oexpr	::= aexpr | aexpr "-o" oexpr ;
62 	aexpr	::= nexpr | nexpr "-a" aexpr ;
63 	nexpr	::= primary | "!" primary
64 	primary	::= unary-operator operand
65 		| operand binary-operator operand
66 		| operand
67 		| "(" oexpr ")"
68 		;
69 	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
70 		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
71 
72 	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
73 			"-nt"|"-ot"|"-ef";
74 	operand ::= <any legal UNIX file name>
75 */
76 
77 enum token {
78 	EOI,
79 	FILRD,
80 	FILWR,
81 	FILEX,
82 	FILEXIST,
83 	FILREG,
84 	FILDIR,
85 	FILCDEV,
86 	FILBDEV,
87 	FILFIFO,
88 	FILSOCK,
89 	FILSYM,
90 	FILGZ,
91 	FILTT,
92 	FILSUID,
93 	FILSGID,
94 	FILSTCK,
95 	FILNT,
96 	FILOT,
97 	FILEQ,
98 	FILUID,
99 	FILGID,
100 	STREZ,
101 	STRNZ,
102 	STREQ,
103 	STRNE,
104 	STRLT,
105 	STRGT,
106 	INTEQ,
107 	INTNE,
108 	INTGE,
109 	INTGT,
110 	INTLE,
111 	INTLT,
112 	UNOT,
113 	BAND,
114 	BOR,
115 	LPAREN,
116 	RPAREN,
117 	OPERAND
118 };
119 
120 enum token_types {
121 	UNOP,
122 	BINOP,
123 	BUNOP,
124 	BBINOP,
125 	PAREN
126 };
127 
128 struct t_op {
129 	const char *op_text;
130 	short op_num, op_type;
131 } const ops [] = {
132 	{"-r",	FILRD,	UNOP},
133 	{"-w",	FILWR,	UNOP},
134 	{"-x",	FILEX,	UNOP},
135 	{"-e",	FILEXIST,UNOP},
136 	{"-f",	FILREG,	UNOP},
137 	{"-d",	FILDIR,	UNOP},
138 	{"-c",	FILCDEV,UNOP},
139 	{"-b",	FILBDEV,UNOP},
140 	{"-p",	FILFIFO,UNOP},
141 	{"-u",	FILSUID,UNOP},
142 	{"-g",	FILSGID,UNOP},
143 	{"-k",	FILSTCK,UNOP},
144 	{"-s",	FILGZ,	UNOP},
145 	{"-t",	FILTT,	UNOP},
146 	{"-z",	STREZ,	UNOP},
147 	{"-n",	STRNZ,	UNOP},
148 	{"-h",	FILSYM,	UNOP},		/* for backwards compat */
149 	{"-O",	FILUID,	UNOP},
150 	{"-G",	FILGID,	UNOP},
151 	{"-L",	FILSYM,	UNOP},
152 	{"-S",	FILSOCK,UNOP},
153 	{"=",	STREQ,	BINOP},
154 	{"!=",	STRNE,	BINOP},
155 	{"<",	STRLT,	BINOP},
156 	{">",	STRGT,	BINOP},
157 	{"-eq",	INTEQ,	BINOP},
158 	{"-ne",	INTNE,	BINOP},
159 	{"-ge",	INTGE,	BINOP},
160 	{"-gt",	INTGT,	BINOP},
161 	{"-le",	INTLE,	BINOP},
162 	{"-lt",	INTLT,	BINOP},
163 	{"-nt",	FILNT,	BINOP},
164 	{"-ot",	FILOT,	BINOP},
165 	{"-ef",	FILEQ,	BINOP},
166 	{"!",	UNOT,	BUNOP},
167 	{"-a",	BAND,	BBINOP},
168 	{"-o",	BOR,	BBINOP},
169 	{"(",	LPAREN,	PAREN},
170 	{")",	RPAREN,	PAREN},
171 	{0,	0,	0}
172 };
173 
174 struct t_op const *t_wp_op;
175 char **t_wp;
176 
177 static int	aexpr __P((enum token));
178 static int	binop __P((void));
179 static int	equalf __P((const char *, const char *));
180 static int	filstat __P((char *, enum token));
181 static int	getn __P((const char *));
182 static quad_t	getq __P((const char *));
183 static int	intcmp __P((const char *, const char *));
184 static int	isoperand __P((void));
185 int		main __P((int, char **));
186 static int	newerf __P((const char *, const char *));
187 static int	nexpr __P((enum token));
188 static int	oexpr __P((enum token));
189 static int	olderf __P((const char *, const char *));
190 static int	primary __P((enum token));
191 static void	syntax __P((const char *, const char *));
192 static enum	token t_lex __P((char *));
193 
194 int
195 main(argc, argv)
196 	int argc;
197 	char **argv;
198 {
199 	int	res;
200 	char	*p;
201 
202 	if ((p = rindex(argv[0], '/')) == NULL)
203 		p = argv[0];
204 	else
205 		p++;
206 	if (strcmp(p, "[") == 0) {
207 		if (strcmp(argv[--argc], "]") != 0)
208 			error("missing ]");
209 		argv[argc] = NULL;
210 	}
211 
212 	/* no expression => false */
213 	if (--argc <= 0)
214 		return 1;
215 
216 	/* XXX work around the absence of an eaccess(2) syscall */
217 	(void)setgid(getegid());
218 	(void)setuid(geteuid());
219 
220 	t_wp = &argv[1];
221 	res = !oexpr(t_lex(*t_wp));
222 
223 	if (*t_wp != NULL && *++t_wp != NULL)
224 		syntax(*t_wp, "unexpected operator");
225 
226 	return res;
227 }
228 
229 static void
230 syntax(op, msg)
231 	const char	*op;
232 	const char	*msg;
233 {
234 
235 	if (op && *op)
236 		error("%s: %s", op, msg);
237 	else
238 		error("%s", msg);
239 }
240 
241 static int
242 oexpr(n)
243 	enum token n;
244 {
245 	int res;
246 
247 	res = aexpr(n);
248 	if (t_lex(*++t_wp) == BOR)
249 		return oexpr(t_lex(*++t_wp)) || res;
250 	t_wp--;
251 	return res;
252 }
253 
254 static int
255 aexpr(n)
256 	enum token n;
257 {
258 	int res;
259 
260 	res = nexpr(n);
261 	if (t_lex(*++t_wp) == BAND)
262 		return aexpr(t_lex(*++t_wp)) && res;
263 	t_wp--;
264 	return res;
265 }
266 
267 static int
268 nexpr(n)
269 	enum token n;			/* token */
270 {
271 	if (n == UNOT)
272 		return !nexpr(t_lex(*++t_wp));
273 	return primary(n);
274 }
275 
276 static int
277 primary(n)
278 	enum token n;
279 {
280 	enum token nn;
281 	int res;
282 
283 	if (n == EOI)
284 		return 0;		/* missing expression */
285 	if (n == LPAREN) {
286 		if ((nn = t_lex(*++t_wp)) == RPAREN)
287 			return 0;	/* missing expression */
288 		res = oexpr(nn);
289 		if (t_lex(*++t_wp) != RPAREN)
290 			syntax(NULL, "closing paren expected");
291 		return res;
292 	}
293 	if (t_wp_op && t_wp_op->op_type == UNOP) {
294 		/* unary expression */
295 		if (*++t_wp == NULL)
296 			syntax(t_wp_op->op_text, "argument expected");
297 		switch (n) {
298 		case STREZ:
299 			return strlen(*t_wp) == 0;
300 		case STRNZ:
301 			return strlen(*t_wp) != 0;
302 		case FILTT:
303 			return isatty(getn(*t_wp));
304 		default:
305 			return filstat(*t_wp, n);
306 		}
307 	}
308 
309 	if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
310 		return binop();
311 	}
312 
313 	return strlen(*t_wp) > 0;
314 }
315 
316 static int
317 binop()
318 {
319 	const char *opnd1, *opnd2;
320 	struct t_op const *op;
321 
322 	opnd1 = *t_wp;
323 	(void) t_lex(*++t_wp);
324 	op = t_wp_op;
325 
326 	if ((opnd2 = *++t_wp) == NULL)
327 		syntax(op->op_text, "argument expected");
328 
329 	switch (op->op_num) {
330 	case STREQ:
331 		return strcmp(opnd1, opnd2) == 0;
332 	case STRNE:
333 		return strcmp(opnd1, opnd2) != 0;
334 	case STRLT:
335 		return strcmp(opnd1, opnd2) < 0;
336 	case STRGT:
337 		return strcmp(opnd1, opnd2) > 0;
338 	case INTEQ:
339 		return intcmp(opnd1, opnd2) == 0;
340 	case INTNE:
341 		return intcmp(opnd1, opnd2) != 0;
342 	case INTGE:
343 		return intcmp(opnd1, opnd2) >= 0;
344 	case INTGT:
345 		return intcmp(opnd1, opnd2) > 0;
346 	case INTLE:
347 		return intcmp(opnd1, opnd2) <= 0;
348 	case INTLT:
349 		return intcmp(opnd1, opnd2) < 0;
350 	case FILNT:
351 		return newerf (opnd1, opnd2);
352 	case FILOT:
353 		return olderf (opnd1, opnd2);
354 	case FILEQ:
355 		return equalf (opnd1, opnd2);
356 	default:
357 		abort();
358 		/* NOTREACHED */
359 	}
360 }
361 
362 static int
363 filstat(nm, mode)
364 	char *nm;
365 	enum token mode;
366 {
367 	struct stat s;
368 
369 	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
370 		return 0;
371 
372 	switch (mode) {
373 	case FILRD:
374 		return access(nm, R_OK) == 0;
375 	case FILWR:
376 		return access(nm, W_OK) == 0;
377 	case FILEX:
378 		/* XXX work around access(2) false positives for superuser */
379 		if (access(nm, X_OK) != 0)
380 			return 0;
381 		if (S_ISDIR(s.st_mode) || getuid() != 0)
382 			return 1;
383 		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
384 	case FILEXIST:
385 		return access(nm, F_OK) == 0;
386 	case FILREG:
387 		return S_ISREG(s.st_mode);
388 	case FILDIR:
389 		return S_ISDIR(s.st_mode);
390 	case FILCDEV:
391 		return S_ISCHR(s.st_mode);
392 	case FILBDEV:
393 		return S_ISBLK(s.st_mode);
394 	case FILFIFO:
395 		return S_ISFIFO(s.st_mode);
396 	case FILSOCK:
397 		return S_ISSOCK(s.st_mode);
398 	case FILSYM:
399 		return S_ISLNK(s.st_mode);
400 	case FILSUID:
401 		return (s.st_mode & S_ISUID) != 0;
402 	case FILSGID:
403 		return (s.st_mode & S_ISGID) != 0;
404 	case FILSTCK:
405 		return (s.st_mode & S_ISVTX) != 0;
406 	case FILGZ:
407 		return s.st_size > (off_t)0;
408 	case FILUID:
409 		return s.st_uid == geteuid();
410 	case FILGID:
411 		return s.st_gid == getegid();
412 	default:
413 		return 1;
414 	}
415 }
416 
417 static enum token
418 t_lex(s)
419 	char *s;
420 {
421 	struct t_op const *op = ops;
422 
423 	if (s == 0) {
424 		t_wp_op = NULL;
425 		return EOI;
426 	}
427 	while (op->op_text) {
428 		if (strcmp(s, op->op_text) == 0) {
429 			if ((op->op_type == UNOP && isoperand()) ||
430 			    (op->op_num == LPAREN && *(t_wp+1) == 0))
431 				break;
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 static int
442 isoperand()
443 {
444 	struct t_op const *op = ops;
445 	char *s;
446 	char *t;
447 
448 	if ((s  = *(t_wp+1)) == 0)
449 		return 1;
450 	if ((t = *(t_wp+2)) == 0)
451 		return 0;
452 	while (op->op_text) {
453 		if (strcmp(s, op->op_text) == 0)
454 			return op->op_type == BINOP &&
455 			    (t[0] != ')' || t[1] != '\0');
456 		op++;
457 	}
458 	return 0;
459 }
460 
461 /* atoi with error detection */
462 static int
463 getn(s)
464 	const char *s;
465 {
466 	char *p;
467 	long r;
468 
469 	errno = 0;
470 	r = strtol(s, &p, 10);
471 
472 	if (errno != 0)
473 		error("%s: out of range", s);
474 
475 	while (isspace((unsigned char)*p))
476 		p++;
477 
478 	if (*p)
479 		error("%s: bad number", s);
480 
481 	return (int) r;
482 }
483 
484 /* atoi with error detection and 64 bit range */
485 static quad_t
486 getq(s)
487 	const char *s;
488 {
489 	char *p;
490 	quad_t r;
491 
492 	errno = 0;
493 	r = strtoq(s, &p, 10);
494 
495 	if (errno != 0)
496 		error("%s: out of range", s);
497 
498 	while (isspace((unsigned char)*p))
499 		p++;
500 
501 	if (*p)
502 		error("%s: bad number", s);
503 
504 	return r;
505 }
506 
507 static int
508 intcmp (s1, s2)
509 	const char *s1, *s2;
510 {
511 	quad_t q1, q2;
512 
513 
514 	q1 = getq(s1);
515 	q2 = getq(s2);
516 
517 	if (q1 > q2)
518 		return 1;
519 
520 	if (q1 < q2)
521 		return -1;
522 
523 	return 0;
524 }
525 
526 static int
527 newerf (f1, f2)
528 	const char *f1, *f2;
529 {
530 	struct stat b1, b2;
531 
532 	return (stat (f1, &b1) == 0 &&
533 		stat (f2, &b2) == 0 &&
534 		b1.st_mtime > b2.st_mtime);
535 }
536 
537 static int
538 olderf (f1, f2)
539 	const char *f1, *f2;
540 {
541 	struct stat b1, b2;
542 
543 	return (stat (f1, &b1) == 0 &&
544 		stat (f2, &b2) == 0 &&
545 		b1.st_mtime < b2.st_mtime);
546 }
547 
548 static int
549 equalf (f1, f2)
550 	const char *f1, *f2;
551 {
552 	struct stat b1, b2;
553 
554 	return (stat (f1, &b1) == 0 &&
555 		stat (f2, &b2) == 0 &&
556 		b1.st_dev == b2.st_dev &&
557 		b1.st_ino == b2.st_ino);
558 }
559