test.c revision 86622
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 86622 2001-11-19 20:39:24Z knu $";
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("%s: out of range", s);
474
475	while (isspace((unsigned char)*p))
476		p++;
477
478	if (*p)
479		error("%s: bad number", s);
480
481	return (int) r;
482}
483
484/* atoi with error detection and 64 bit range */
485static quad_t
486getq(s)
487	const char *s;
488{
489	char *p;
490	quad_t r;
491
492	errno = 0;
493	r = strtoq(s, &p, 10);
494
495	if (errno != 0)
496		error("%s: out of range", s);
497
498	while (isspace((unsigned char)*p))
499		p++;
500
501	if (*p)
502		error("%s: bad number", s);
503
504	return r;
505}
506
507static int
508intcmp (s1, s2)
509	const char *s1, *s2;
510{
511	quad_t q1, q2;
512
513
514	q1 = getq(s1);
515	q2 = getq(s2);
516
517	if (q1 > q2)
518		return 1;
519
520	if (q1 < q2)
521		return -1;
522
523	return 0;
524}
525
526static int
527newerf (f1, f2)
528	const char *f1, *f2;
529{
530	struct stat b1, b2;
531
532	return (stat (f1, &b1) == 0 &&
533		stat (f2, &b2) == 0 &&
534		b1.st_mtime > b2.st_mtime);
535}
536
537static int
538olderf (f1, f2)
539	const char *f1, *f2;
540{
541	struct stat b1, b2;
542
543	return (stat (f1, &b1) == 0 &&
544		stat (f2, &b2) == 0 &&
545		b1.st_mtime < b2.st_mtime);
546}
547
548static int
549equalf (f1, f2)
550	const char *f1, *f2;
551{
552	struct stat b1, b2;
553
554	return (stat (f1, &b1) == 0 &&
555		stat (f2, &b2) == 0 &&
556		b1.st_dev == b2.st_dev &&
557		b1.st_ino == b2.st_ino);
558}
559