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