xref: /dragonfly/bin/test/test.c (revision af79c6e5)
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.4 2003/09/28 14:39:15 hmp 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 (enum token);
177 static int	binop (void);
178 static int	equalf (const char *, const char *);
179 static int	filstat (char *, enum token);
180 static int	getn (const char *);
181 static quad_t	getq (const char *);
182 static int	intcmp (const char *, const char *);
183 static int	isoperand (void);
184 static int	newerf (const char *, const char *);
185 static int	nexpr (enum token);
186 static int	oexpr (enum token);
187 static int	olderf (const char *, const char *);
188 static int	primary (enum token);
189 static void	syntax (const char *, const char *);
190 static enum	token t_lex (char *);
191 
192 int
193 main(int argc, char **argv)
194 {
195 	gid_t	egid, gid;
196 	uid_t	euid, uid;
197 	int	res;
198 	char	*p;
199 
200 	if ((p = rindex(argv[0], '/')) == NULL)
201 		p = argv[0];
202 	else
203 		p++;
204 	if (strcmp(p, "[") == 0) {
205 		if (strcmp(argv[--argc], "]") != 0)
206 			error("missing ]");
207 		argv[argc] = NULL;
208 	}
209 
210 	/* no expression => false */
211 	if (--argc <= 0)
212 		return 1;
213 
214 	/* XXX work around the absence of an eaccess(2) syscall */
215 	egid = getegid();
216 	euid = geteuid();
217 	gid = getgid();
218 	uid = getuid();
219 	(void)setregid(egid, gid);
220 	(void)setreuid(euid, uid);
221 
222 	nargc = argc;
223 	t_wp = &argv[1];
224 	res = !oexpr(t_lex(*t_wp));
225 
226 	if (--nargc > 0)
227 		syntax(*t_wp, "unexpected operator");
228 	(void)setregid(gid, egid);
229 	(void)setreuid(uid, euid);
230 
231 	return res;
232 }
233 
234 static void
235 syntax(const char *op, const char *msg)
236 {
237 
238 	if (op && *op)
239 		error("%s: %s", op, msg);
240 	else
241 		error("%s", msg);
242 }
243 
244 static int
245 oexpr(enum token n)
246 {
247 	int res;
248 
249 	res = aexpr(n);
250 	if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR)
251 		return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ||
252 		    res;
253 	t_wp--;
254 	nargc++;
255 	return res;
256 }
257 
258 static int
259 aexpr(enum token n)
260 {
261 	int res;
262 
263 	res = nexpr(n);
264 	if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND)
265 		return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) &&
266 		    res;
267 	t_wp--;
268 	nargc++;
269 	return res;
270 }
271 
272 static int
273 nexpr(enum token n)
274 {
275 	if (n == UNOT)
276 		return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL));
277 	return primary(n);
278 }
279 
280 static int
281 primary(enum token n)
282 {
283 	enum token nn;
284 	int res;
285 
286 	if (n == EOI)
287 		return 0;		/* missing expression */
288 	if (n == LPAREN) {
289 		if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ==
290 		    RPAREN)
291 			return 0;	/* missing expression */
292 		res = oexpr(nn);
293 		if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
294 			syntax(NULL, "closing paren expected");
295 		return res;
296 	}
297 	if (t_wp_op && t_wp_op->op_type == UNOP) {
298 		/* unary expression */
299 		if (--nargc == 0)
300 			syntax(t_wp_op->op_text, "argument expected");
301 		switch (n) {
302 		case STREZ:
303 			return strlen(*++t_wp) == 0;
304 		case STRNZ:
305 			return strlen(*++t_wp) != 0;
306 		case FILTT:
307 			return isatty(getn(*++t_wp));
308 		default:
309 			return filstat(*++t_wp, n);
310 		}
311 	}
312 
313 	if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type ==
314 	    BINOP) {
315 		return binop();
316 	}
317 
318 	return strlen(*t_wp) > 0;
319 }
320 
321 static int
322 binop(void)
323 {
324 	const char *opnd1, *opnd2;
325 	struct t_op const *op;
326 
327 	opnd1 = *t_wp;
328 	(void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL);
329 	op = t_wp_op;
330 
331 	if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
332 		syntax(op->op_text, "argument expected");
333 
334 	switch (op->op_num) {
335 	case STREQ:
336 		return strcmp(opnd1, opnd2) == 0;
337 	case STRNE:
338 		return strcmp(opnd1, opnd2) != 0;
339 	case STRLT:
340 		return strcmp(opnd1, opnd2) < 0;
341 	case STRGT:
342 		return strcmp(opnd1, opnd2) > 0;
343 	case INTEQ:
344 		return intcmp(opnd1, opnd2) == 0;
345 	case INTNE:
346 		return intcmp(opnd1, opnd2) != 0;
347 	case INTGE:
348 		return intcmp(opnd1, opnd2) >= 0;
349 	case INTGT:
350 		return intcmp(opnd1, opnd2) > 0;
351 	case INTLE:
352 		return intcmp(opnd1, opnd2) <= 0;
353 	case INTLT:
354 		return intcmp(opnd1, opnd2) < 0;
355 	case FILNT:
356 		return newerf (opnd1, opnd2);
357 	case FILOT:
358 		return olderf (opnd1, opnd2);
359 	case FILEQ:
360 		return equalf (opnd1, opnd2);
361 	default:
362 		abort();
363 		/* NOTREACHED */
364 	}
365 }
366 
367 static int
368 filstat(char *nm, enum token mode)
369 {
370 	struct stat s;
371 
372 	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
373 		return 0;
374 
375 	switch (mode) {
376 	case FILRD:
377 		return access(nm, R_OK) == 0;
378 	case FILWR:
379 		return access(nm, W_OK) == 0;
380 	case FILEX:
381 		/* XXX work around access(2) false positives for superuser */
382 		if (access(nm, X_OK) != 0)
383 			return 0;
384 		if (S_ISDIR(s.st_mode) || getuid() != 0)
385 			return 1;
386 		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
387 	case FILEXIST:
388 		return access(nm, F_OK) == 0;
389 	case FILREG:
390 		return S_ISREG(s.st_mode);
391 	case FILDIR:
392 		return S_ISDIR(s.st_mode);
393 	case FILCDEV:
394 		return S_ISCHR(s.st_mode);
395 	case FILBDEV:
396 		return S_ISBLK(s.st_mode);
397 	case FILFIFO:
398 		return S_ISFIFO(s.st_mode);
399 	case FILSOCK:
400 		return S_ISSOCK(s.st_mode);
401 	case FILSYM:
402 		return S_ISLNK(s.st_mode);
403 	case FILSUID:
404 		return (s.st_mode & S_ISUID) != 0;
405 	case FILSGID:
406 		return (s.st_mode & S_ISGID) != 0;
407 	case FILSTCK:
408 		return (s.st_mode & S_ISVTX) != 0;
409 	case FILGZ:
410 		return s.st_size > (off_t)0;
411 	case FILUID:
412 		return s.st_uid == geteuid();
413 	case FILGID:
414 		return s.st_gid == getegid();
415 	default:
416 		return 1;
417 	}
418 }
419 
420 static enum token
421 t_lex(char *s)
422 {
423 	struct t_op const *op = ops;
424 
425 	if (s == 0) {
426 		t_wp_op = NULL;
427 		return EOI;
428 	}
429 	while (op->op_text) {
430 		if (strcmp(s, op->op_text) == 0) {
431 			if ((op->op_type == UNOP && isoperand()) ||
432 			    (op->op_num == LPAREN && nargc == 1))
433 				break;
434 			t_wp_op = op;
435 			return op->op_num;
436 		}
437 		op++;
438 	}
439 	t_wp_op = NULL;
440 	return OPERAND;
441 }
442 
443 static int
444 isoperand(void)
445 {
446 	struct t_op const *op = ops;
447 	char *s;
448 	char *t;
449 
450 	if (nargc == 1)
451 		return 1;
452 	if (nargc == 2)
453 		return 0;
454 	s = *(t_wp + 1);
455 	t = *(t_wp + 2);
456 	while (op->op_text) {
457 		if (strcmp(s, op->op_text) == 0)
458 			return op->op_type == BINOP &&
459 			    (t[0] != ')' || t[1] != '\0');
460 		op++;
461 	}
462 	return 0;
463 }
464 
465 /* atoi with error detection */
466 static int
467 getn(const char *s)
468 {
469 	char *p;
470 	long r;
471 
472 	errno = 0;
473 	r = strtol(s, &p, 10);
474 
475 	if (errno != 0)
476 		error((errno == EINVAL) ? "%s: bad number" :
477 					  "%s: out of range", s);
478 
479 	while (isspace((unsigned char)*p))
480 		p++;
481 
482 	if (*p)
483 		error("%s: bad number", s);
484 
485 	return (int) r;
486 }
487 
488 /* atoi with error detection and 64 bit range */
489 static quad_t
490 getq(const char *s)
491 {
492 	char *p;
493 	quad_t r;
494 
495 	errno = 0;
496 	r = strtoq(s, &p, 10);
497 
498 	if (errno != 0)
499 		error((errno == EINVAL) ? "%s: bad number" :
500 					  "%s: out of range", s);
501 
502 	while (isspace((unsigned char)*p))
503 		p++;
504 
505 	if (*p)
506 		error("%s: bad number", s);
507 
508 	return r;
509 }
510 
511 static int
512 intcmp (const char *s1, const char *s2)
513 {
514 	quad_t q1, q2;
515 
516 
517 	q1 = getq(s1);
518 	q2 = getq(s2);
519 
520 	if (q1 > q2)
521 		return 1;
522 
523 	if (q1 < q2)
524 		return -1;
525 
526 	return 0;
527 }
528 
529 static int
530 newerf (const char *f1, const char *f2)
531 {
532 	struct stat b1, b2;
533 
534 	if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
535 		return 0;
536 
537 	if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec)
538 		return 1;
539 	if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec)
540 		return 0;
541 
542 	return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec);
543 }
544 
545 static int
546 olderf (const char *f1, const char *f2)
547 {
548 	return (newerf(f2, f1));
549 }
550 
551 static int
552 equalf (const char *f1, const char *f2)
553 {
554 	struct stat b1, b2;
555 
556 	return (stat (f1, &b1) == 0 &&
557 		stat (f2, &b2) == 0 &&
558 		b1.st_dev == b2.st_dev &&
559 		b1.st_ino == b2.st_ino);
560 }
561