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