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