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