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