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