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