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