test.c revision 96375
1169691Skan/*	$NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $	*/
2169691Skan
3169691Skan/*
4169691Skan * test(1); version 7-like  --  author Erik Baalbergen
5169691Skan * modified by Eric Gisin to be used as built-in.
6169691Skan * modified by Arnold Robbins to add SVR3 compatibility
7169691Skan * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
8169691Skan * modified by J.T. Conklin for NetBSD.
9169691Skan *
10169691Skan * This program is in the Public Domain.
11169691Skan */
12169691Skan
13169691Skan#ifndef lint
14169691Skanstatic const char rcsid[] =
15169691Skan  "$FreeBSD: head/bin/test/test.c 96375 2002-05-11 01:24:39Z alfred $";
16169691Skan#endif /* not lint */
17169691Skan
18169691Skan#include <sys/types.h>
19169691Skan#include <sys/stat.h>
20169691Skan
21169691Skan#include <ctype.h>
22169691Skan#include <err.h>
23169691Skan#include <errno.h>
24169691Skan#include <inttypes.h>
25169691Skan#include <limits.h>
26169691Skan#include <stdarg.h>
27169691Skan#include <stdio.h>
28169691Skan#include <stdlib.h>
29169691Skan#include <string.h>
30169691Skan#include <unistd.h>
31169691Skan
32169691Skan#ifdef SHELL
33169691Skan#define main testcmd
34169691Skan#include "bltin/bltin.h"
35169691Skan#else
36169691Skan#include <locale.h>
37169691Skan
38169691Skanstatic void error(const char *, ...) __attribute__((__noreturn__))
39169691Skan	__printf0like(1, 2);
40169691Skan
41169691Skanstatic void
42169691Skanerror(const char *msg, ...)
43169691Skan{
44169691Skan	va_list ap;
45169691Skan	va_start(ap, msg);
46169691Skan	verrx(2, msg, ap);
47169691Skan	/*NOTREACHED*/
48169691Skan	va_end(ap);
49169691Skan}
50169691Skan#endif
51169691Skan
52169691Skan/* test(1) accepts the following grammar:
53169691Skan	oexpr	::= aexpr | aexpr "-o" oexpr ;
54169691Skan	aexpr	::= nexpr | nexpr "-a" aexpr ;
55169691Skan	nexpr	::= primary | "!" primary
56169691Skan	primary	::= unary-operator operand
57169691Skan		| operand binary-operator operand
58169691Skan		| operand
59169691Skan		| "(" oexpr ")"
60169691Skan		;
61169691Skan	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
62169691Skan		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
63169691Skan
64169691Skan	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
65169691Skan			"-nt"|"-ot"|"-ef";
66169691Skan	operand ::= <any legal UNIX file name>
67169691Skan*/
68169691Skan
69169691Skanenum token {
70169691Skan	EOI,
71169691Skan	FILRD,
72169691Skan	FILWR,
73169691Skan	FILEX,
74169691Skan	FILEXIST,
75169691Skan	FILREG,
76169691Skan	FILDIR,
77169691Skan	FILCDEV,
78169691Skan	FILBDEV,
79169691Skan	FILFIFO,
80169691Skan	FILSOCK,
81169691Skan	FILSYM,
82169691Skan	FILGZ,
83169691Skan	FILTT,
84169691Skan	FILSUID,
85169691Skan	FILSGID,
86169691Skan	FILSTCK,
87169691Skan	FILNT,
88169691Skan	FILOT,
89169691Skan	FILEQ,
90169691Skan	FILUID,
91169691Skan	FILGID,
92169691Skan	STREZ,
93169691Skan	STRNZ,
94169691Skan	STREQ,
95169691Skan	STRNE,
96169691Skan	STRLT,
97169691Skan	STRGT,
98169691Skan	INTEQ,
99169691Skan	INTNE,
100169691Skan	INTGE,
101169691Skan	INTGT,
102169691Skan	INTLE,
103169691Skan	INTLT,
104169691Skan	UNOT,
105169691Skan	BAND,
106169691Skan	BOR,
107169691Skan	LPAREN,
108169691Skan	RPAREN,
109169691Skan	OPERAND
110169691Skan};
111169691Skan
112169691Skanenum token_types {
113169691Skan	UNOP,
114169691Skan	BINOP,
115169691Skan	BUNOP,
116169691Skan	BBINOP,
117169691Skan	PAREN
118169691Skan};
119169691Skan
120169691Skanstruct t_op {
121169691Skan	const char *op_text;
122169691Skan	short op_num, op_type;
123169691Skan} const ops [] = {
124169691Skan	{"-r",	FILRD,	UNOP},
125169691Skan	{"-w",	FILWR,	UNOP},
126169691Skan	{"-x",	FILEX,	UNOP},
127169691Skan	{"-e",	FILEXIST,UNOP},
128169691Skan	{"-f",	FILREG,	UNOP},
129169691Skan	{"-d",	FILDIR,	UNOP},
130169691Skan	{"-c",	FILCDEV,UNOP},
131169691Skan	{"-b",	FILBDEV,UNOP},
132169691Skan	{"-p",	FILFIFO,UNOP},
133169691Skan	{"-u",	FILSUID,UNOP},
134169691Skan	{"-g",	FILSGID,UNOP},
135169691Skan	{"-k",	FILSTCK,UNOP},
136169691Skan	{"-s",	FILGZ,	UNOP},
137169691Skan	{"-t",	FILTT,	UNOP},
138169691Skan	{"-z",	STREZ,	UNOP},
139169691Skan	{"-n",	STRNZ,	UNOP},
140169691Skan	{"-h",	FILSYM,	UNOP},		/* for backwards compat */
141169691Skan	{"-O",	FILUID,	UNOP},
142169691Skan	{"-G",	FILGID,	UNOP},
143169691Skan	{"-L",	FILSYM,	UNOP},
144169691Skan	{"-S",	FILSOCK,UNOP},
145169691Skan	{"=",	STREQ,	BINOP},
146169691Skan	{"!=",	STRNE,	BINOP},
147169691Skan	{"<",	STRLT,	BINOP},
148169691Skan	{">",	STRGT,	BINOP},
149169691Skan	{"-eq",	INTEQ,	BINOP},
150169691Skan	{"-ne",	INTNE,	BINOP},
151169691Skan	{"-ge",	INTGE,	BINOP},
152169691Skan	{"-gt",	INTGT,	BINOP},
153169691Skan	{"-le",	INTLE,	BINOP},
154169691Skan	{"-lt",	INTLT,	BINOP},
155169691Skan	{"-nt",	FILNT,	BINOP},
156169691Skan	{"-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