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