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