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