test.c revision 50087
149884Ssheldonh/*	$NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $	*/
249884Ssheldonh
349884Ssheldonh/*
449884Ssheldonh * test(1); version 7-like  --  author Erik Baalbergen
549884Ssheldonh * modified by Eric Gisin to be used as built-in.
649884Ssheldonh * modified by Arnold Robbins to add SVR3 compatibility
749884Ssheldonh * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
849884Ssheldonh * modified by J.T. Conklin for NetBSD.
91556Srgrimes *
1049884Ssheldonh * This program is in the Public Domain.
111556Srgrimes */
121556Srgrimes
131556Srgrimes#ifndef lint
1436152Scharnierstatic const char rcsid[] =
1550087Sgreen	"$Id: test.c,v 1.24 1999/08/18 00:18:52 green Exp $";
161556Srgrimes#endif /* not lint */
171556Srgrimes
1849884Ssheldonh#include <sys/types.h>
191556Srgrimes#include <sys/stat.h>
201556Srgrimes
211556Srgrimes#include <ctype.h>
221556Srgrimes#include <err.h>
231556Srgrimes#include <errno.h>
241556Srgrimes#include <stdio.h>
251556Srgrimes#include <stdlib.h>
261556Srgrimes#include <string.h>
271556Srgrimes#include <unistd.h>
281556Srgrimes
2949884Ssheldonh/* test(1) accepts the following grammar:
3049884Ssheldonh	oexpr	::= aexpr | aexpr "-o" oexpr ;
3149884Ssheldonh	aexpr	::= nexpr | nexpr "-a" aexpr ;
3249884Ssheldonh	nexpr	::= primary | "!" primary
3349884Ssheldonh	primary	::= unary-operator operand
3449884Ssheldonh		| operand binary-operator operand
3549884Ssheldonh		| operand
3649884Ssheldonh		| "(" oexpr ")"
3749884Ssheldonh		;
3849884Ssheldonh	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
3949884Ssheldonh		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
401556Srgrimes
4149884Ssheldonh	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
4249884Ssheldonh			"-nt"|"-ot"|"-ef";
4349884Ssheldonh	operand ::= <any legal UNIX file name>
4449884Ssheldonh*/
451556Srgrimes
4649884Ssheldonhenum token {
4749884Ssheldonh	EOI,
4849884Ssheldonh	FILRD,
4949884Ssheldonh	FILWR,
5049884Ssheldonh	FILEX,
5149884Ssheldonh	FILEXIST,
5249884Ssheldonh	FILREG,
5349884Ssheldonh	FILDIR,
5449884Ssheldonh	FILCDEV,
5549884Ssheldonh	FILBDEV,
5649884Ssheldonh	FILFIFO,
5749884Ssheldonh	FILSOCK,
5849884Ssheldonh	FILSYM,
5949884Ssheldonh	FILGZ,
6049884Ssheldonh	FILTT,
6149884Ssheldonh	FILSUID,
6249884Ssheldonh	FILSGID,
6349884Ssheldonh	FILSTCK,
6449884Ssheldonh	FILNT,
6549884Ssheldonh	FILOT,
6649884Ssheldonh	FILEQ,
6749884Ssheldonh	FILUID,
6849884Ssheldonh	FILGID,
6949884Ssheldonh	STREZ,
7049884Ssheldonh	STRNZ,
7149884Ssheldonh	STREQ,
7249884Ssheldonh	STRNE,
7349884Ssheldonh	STRLT,
7449884Ssheldonh	STRGT,
7549884Ssheldonh	INTEQ,
7649884Ssheldonh	INTNE,
7749884Ssheldonh	INTGE,
7849884Ssheldonh	INTGT,
7949884Ssheldonh	INTLE,
8049884Ssheldonh	INTLT,
8149884Ssheldonh	UNOT,
8249884Ssheldonh	BAND,
8349884Ssheldonh	BOR,
8449884Ssheldonh	LPAREN,
8549884Ssheldonh	RPAREN,
8649884Ssheldonh	OPERAND
871556Srgrimes};
881556Srgrimes
8949884Ssheldonhenum token_types {
9049884Ssheldonh	UNOP,
9149884Ssheldonh	BINOP,
9249884Ssheldonh	BUNOP,
9349884Ssheldonh	BBINOP,
9449884Ssheldonh	PAREN
951556Srgrimes};
961556Srgrimes
9749884Ssheldonhstruct t_op {
9849884Ssheldonh	const char *op_text;
9949884Ssheldonh	short op_num, op_type;
10049884Ssheldonh} const ops [] = {
10149884Ssheldonh	{"-r",	FILRD,	UNOP},
10249884Ssheldonh	{"-w",	FILWR,	UNOP},
10349884Ssheldonh	{"-x",	FILEX,	UNOP},
10449884Ssheldonh	{"-e",	FILEXIST,UNOP},
10549884Ssheldonh	{"-f",	FILREG,	UNOP},
10649884Ssheldonh	{"-d",	FILDIR,	UNOP},
10749884Ssheldonh	{"-c",	FILCDEV,UNOP},
10849884Ssheldonh	{"-b",	FILBDEV,UNOP},
10949884Ssheldonh	{"-p",	FILFIFO,UNOP},
11049884Ssheldonh	{"-u",	FILSUID,UNOP},
11149884Ssheldonh	{"-g",	FILSGID,UNOP},
11249884Ssheldonh	{"-k",	FILSTCK,UNOP},
11349884Ssheldonh	{"-s",	FILGZ,	UNOP},
11449884Ssheldonh	{"-t",	FILTT,	UNOP},
11549884Ssheldonh	{"-z",	STREZ,	UNOP},
11649884Ssheldonh	{"-n",	STRNZ,	UNOP},
11749884Ssheldonh	{"-h",	FILSYM,	UNOP},		/* for backwards compat */
11849884Ssheldonh	{"-O",	FILUID,	UNOP},
11949884Ssheldonh	{"-G",	FILGID,	UNOP},
12049884Ssheldonh	{"-L",	FILSYM,	UNOP},
12149884Ssheldonh	{"-S",	FILSOCK,UNOP},
12249884Ssheldonh	{"=",	STREQ,	BINOP},
12349884Ssheldonh	{"!=",	STRNE,	BINOP},
12449884Ssheldonh	{"<",	STRLT,	BINOP},
12549884Ssheldonh	{">",	STRGT,	BINOP},
12649884Ssheldonh	{"-eq",	INTEQ,	BINOP},
12749884Ssheldonh	{"-ne",	INTNE,	BINOP},
12849884Ssheldonh	{"-ge",	INTGE,	BINOP},
12949884Ssheldonh	{"-gt",	INTGT,	BINOP},
13049884Ssheldonh	{"-le",	INTLE,	BINOP},
13149884Ssheldonh	{"-lt",	INTLT,	BINOP},
13249884Ssheldonh	{"-nt",	FILNT,	BINOP},
13349884Ssheldonh	{"-ot",	FILOT,	BINOP},
13449884Ssheldonh	{"-ef",	FILEQ,	BINOP},
13549884Ssheldonh	{"!",	UNOT,	BUNOP},
13649884Ssheldonh	{"-a",	BAND,	BBINOP},
13749884Ssheldonh	{"-o",	BOR,	BBINOP},
13849884Ssheldonh	{"(",	LPAREN,	PAREN},
13949884Ssheldonh	{")",	RPAREN,	PAREN},
14049884Ssheldonh	{0,	0,	0}
1411556Srgrimes};
1421556Srgrimes
14349884Ssheldonhstruct t_op const *t_wp_op;
14449884Ssheldonhchar **t_wp;
1451556Srgrimes
14649884Ssheldonhstatic void syntax __P((const char *, const char *));
14749884Ssheldonhstatic enum token t_lex __P((char *));
14849884Ssheldonhstatic int oexpr __P((enum token));
14949884Ssheldonhstatic int aexpr __P((enum token));
15049884Ssheldonhstatic int nexpr __P((enum token));
15149884Ssheldonhstatic int primary __P((enum token));
15249884Ssheldonhstatic int binop __P((void));
15349884Ssheldonhstatic int filstat __P((char *, enum token));
15449884Ssheldonhstatic int isoperand __P((void));
15549884Ssheldonhstatic int getn __P((const char *));
15649884Ssheldonhstatic int newerf __P((const char *, const char *));
15749884Ssheldonhstatic int olderf __P((const char *, const char *));
15849884Ssheldonhstatic int equalf __P((const char *, const char *));
15949884Ssheldonh
1601556Srgrimesint
1611556Srgrimesmain(argc, argv)
1621556Srgrimes	int argc;
16349884Ssheldonh	char **argv;
1641556Srgrimes{
16549884Ssheldonh	int	res;
1661556Srgrimes
16749884Ssheldonh	if (strcmp(argv[0], "[") == 0) {
1681556Srgrimes		if (strcmp(argv[--argc], "]"))
1691556Srgrimes			errx(2, "missing ]");
1701556Srgrimes		argv[argc] = NULL;
1711556Srgrimes	}
1721556Srgrimes
17350087Sgreen	/*
17450087Sgreen	 * We need to set our real user and group so that when we call
17550087Sgreen	 * access(2), it won't possibly return incorrect results.
17650087Sgreen	 */
17750087Sgreen	(void)setgid(getegid());
17850087Sgreen	(void)setuid(geteuid());
17950087Sgreen
18049884Ssheldonh	t_wp = &argv[1];
18149884Ssheldonh	res = !oexpr(t_lex(*t_wp));
1821556Srgrimes
18349884Ssheldonh	if (*t_wp != NULL && *++t_wp != NULL)
18449884Ssheldonh		syntax(*t_wp, "unexpected operator");
18549884Ssheldonh
18649884Ssheldonh	return res;
1871556Srgrimes}
1881556Srgrimes
18949884Ssheldonhstatic void
19049884Ssheldonhsyntax(op, msg)
19149884Ssheldonh	const char	*op;
19249884Ssheldonh	const char	*msg;
1931556Srgrimes{
1941556Srgrimes
19549884Ssheldonh	if (op && *op)
19649884Ssheldonh		errx(2, "%s: %s", op, msg);
19749884Ssheldonh	else
19849884Ssheldonh		errx(2, "%s", msg);
1991556Srgrimes}
2001556Srgrimes
20149884Ssheldonhstatic int
20249884Ssheldonhoexpr(n)
20349884Ssheldonh	enum token n;
2041556Srgrimes{
20549884Ssheldonh	int res;
2061556Srgrimes
20749884Ssheldonh	res = aexpr(n);
20849884Ssheldonh	if (t_lex(*++t_wp) == BOR)
20949884Ssheldonh		return oexpr(t_lex(*++t_wp)) || res;
21049884Ssheldonh	t_wp--;
21149884Ssheldonh	return res;
21249884Ssheldonh}
2134171Sache
21449884Ssheldonhstatic int
21549884Ssheldonhaexpr(n)
21649884Ssheldonh	enum token n;
21749884Ssheldonh{
21849884Ssheldonh	int res;
2191556Srgrimes
22049884Ssheldonh	res = nexpr(n);
22149884Ssheldonh	if (t_lex(*++t_wp) == BAND)
22249884Ssheldonh		return aexpr(t_lex(*++t_wp)) && res;
22349884Ssheldonh	t_wp--;
22449884Ssheldonh	return res;
2251556Srgrimes}
2261556Srgrimes
2271556Srgrimesstatic int
22849884Ssheldonhnexpr(n)
22949884Ssheldonh	enum token n;			/* token */
2301556Srgrimes{
23149884Ssheldonh	if (n == UNOT)
23249884Ssheldonh		return !nexpr(t_lex(*++t_wp));
23349884Ssheldonh	return primary(n);
2341556Srgrimes}
2351556Srgrimes
2361556Srgrimesstatic int
23749884Ssheldonhprimary(n)
23849884Ssheldonh	enum token n;
2391556Srgrimes{
24049884Ssheldonh	enum token nn;
24149884Ssheldonh	int res;
2421556Srgrimes
24349884Ssheldonh	if (n == EOI)
24449884Ssheldonh		return 0;		/* missing expression */
24549884Ssheldonh	if (n == LPAREN) {
24649884Ssheldonh		if ((nn = t_lex(*++t_wp)) == RPAREN)
24749884Ssheldonh			return 0;	/* missing expression */
24849884Ssheldonh		res = oexpr(nn);
24949884Ssheldonh		if (t_lex(*++t_wp) != RPAREN)
25049884Ssheldonh			syntax(NULL, "closing paren expected");
25149884Ssheldonh		return res;
25249884Ssheldonh	}
25349884Ssheldonh	if (t_wp_op && t_wp_op->op_type == UNOP) {
25449884Ssheldonh		/* unary expression */
25549884Ssheldonh		if (*++t_wp == NULL)
25649884Ssheldonh			syntax(t_wp_op->op_text, "argument expected");
25749884Ssheldonh		switch (n) {
25849884Ssheldonh		case STREZ:
25949884Ssheldonh			return strlen(*t_wp) == 0;
26049884Ssheldonh		case STRNZ:
26149884Ssheldonh			return strlen(*t_wp) != 0;
26249884Ssheldonh		case FILTT:
26349884Ssheldonh			return isatty(getn(*t_wp));
26449884Ssheldonh		default:
26549884Ssheldonh			return filstat(*t_wp, n);
26649884Ssheldonh		}
26749884Ssheldonh	}
2681556Srgrimes
26949884Ssheldonh	if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
27049884Ssheldonh		return binop();
27149884Ssheldonh	}
27249884Ssheldonh
27349884Ssheldonh	return strlen(*t_wp) > 0;
2741556Srgrimes}
2751556Srgrimes
2761556Srgrimesstatic int
27749884Ssheldonhbinop()
2781556Srgrimes{
27949884Ssheldonh	const char *opnd1, *opnd2;
28049884Ssheldonh	struct t_op const *op;
2811556Srgrimes
28249884Ssheldonh	opnd1 = *t_wp;
28349884Ssheldonh	(void) t_lex(*++t_wp);
28449884Ssheldonh	op = t_wp_op;
2851556Srgrimes
28649884Ssheldonh	if ((opnd2 = *++t_wp) == NULL)
28749884Ssheldonh		syntax(op->op_text, "argument expected");
28849884Ssheldonh
28949884Ssheldonh	switch (op->op_num) {
29049884Ssheldonh	case STREQ:
29149884Ssheldonh		return strcmp(opnd1, opnd2) == 0;
29249884Ssheldonh	case STRNE:
29349884Ssheldonh		return strcmp(opnd1, opnd2) != 0;
29449884Ssheldonh	case STRLT:
29549884Ssheldonh		return strcmp(opnd1, opnd2) < 0;
29649884Ssheldonh	case STRGT:
29749884Ssheldonh		return strcmp(opnd1, opnd2) > 0;
29849884Ssheldonh	case INTEQ:
29949884Ssheldonh		return getn(opnd1) == getn(opnd2);
30049884Ssheldonh	case INTNE:
30149884Ssheldonh		return getn(opnd1) != getn(opnd2);
30249884Ssheldonh	case INTGE:
30349884Ssheldonh		return getn(opnd1) >= getn(opnd2);
30449884Ssheldonh	case INTGT:
30549884Ssheldonh		return getn(opnd1) > getn(opnd2);
30649884Ssheldonh	case INTLE:
30749884Ssheldonh		return getn(opnd1) <= getn(opnd2);
30849884Ssheldonh	case INTLT:
30949884Ssheldonh		return getn(opnd1) < getn(opnd2);
31049884Ssheldonh	case FILNT:
31149884Ssheldonh		return newerf (opnd1, opnd2);
31249884Ssheldonh	case FILOT:
31349884Ssheldonh		return olderf (opnd1, opnd2);
31449884Ssheldonh	case FILEQ:
31549884Ssheldonh		return equalf (opnd1, opnd2);
31649884Ssheldonh	default:
31749884Ssheldonh		abort();
31849884Ssheldonh		/* NOTREACHED */
3191556Srgrimes	}
3201556Srgrimes}
3211556Srgrimes
32249884Ssheldonhstatic int
32349884Ssheldonhfilstat(nm, mode)
32449884Ssheldonh	char *nm;
32549884Ssheldonh	enum token mode;
3261556Srgrimes{
32749884Ssheldonh	struct stat s;
3281556Srgrimes
32949884Ssheldonh	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
33049884Ssheldonh		return 0;
3312664Scsgr
33249884Ssheldonh	switch (mode) {
33349884Ssheldonh	case FILRD:
33449884Ssheldonh		return access(nm, R_OK) == 0;
33549884Ssheldonh	case FILWR:
33649884Ssheldonh		return access(nm, W_OK) == 0;
33749884Ssheldonh	case FILEX:
33850087Sgreen		/*
33950087Sgreen		 * We cannot simply use access(2) for this specific case
34050087Sgreen		 * since it can always return false positives for root.
34150087Sgreen		 */
34250087Sgreen		if (access(nm, X_OK) != 0)
34350087Sgreen			return 0;
34450087Sgreen		if (S_ISDIR(s.st_mode) || getuid() != 0)
34550087Sgreen			return 1;
34650087Sgreen		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
34749884Ssheldonh	case FILEXIST:
34849884Ssheldonh		return access(nm, F_OK) == 0;
34949884Ssheldonh	case FILREG:
35049884Ssheldonh		return S_ISREG(s.st_mode);
35149884Ssheldonh	case FILDIR:
35249884Ssheldonh		return S_ISDIR(s.st_mode);
35349884Ssheldonh	case FILCDEV:
35449884Ssheldonh		return S_ISCHR(s.st_mode);
35549884Ssheldonh	case FILBDEV:
35649884Ssheldonh		return S_ISBLK(s.st_mode);
35749884Ssheldonh	case FILFIFO:
35849884Ssheldonh		return S_ISFIFO(s.st_mode);
35949884Ssheldonh	case FILSOCK:
36049884Ssheldonh		return S_ISSOCK(s.st_mode);
36149884Ssheldonh	case FILSYM:
36249884Ssheldonh		return S_ISLNK(s.st_mode);
36349884Ssheldonh	case FILSUID:
36449884Ssheldonh		return (s.st_mode & S_ISUID) != 0;
36549884Ssheldonh	case FILSGID:
36649884Ssheldonh		return (s.st_mode & S_ISGID) != 0;
36749884Ssheldonh	case FILSTCK:
36849884Ssheldonh		return (s.st_mode & S_ISVTX) != 0;
36949884Ssheldonh	case FILGZ:
37049884Ssheldonh		return s.st_size > (off_t)0;
37149884Ssheldonh	case FILUID:
37249884Ssheldonh		return s.st_uid == geteuid();
37349884Ssheldonh	case FILGID:
37449884Ssheldonh		return s.st_gid == getegid();
37549884Ssheldonh	default:
37649884Ssheldonh		return 1;
3772675Scsgr	}
37849884Ssheldonh}
3792675Scsgr
38049884Ssheldonhstatic enum token
38149884Ssheldonht_lex(s)
38249884Ssheldonh	char *s;
38349884Ssheldonh{
38449884Ssheldonh	struct t_op const *op = ops;
38549884Ssheldonh
38649884Ssheldonh	if (s == 0) {
38749884Ssheldonh		t_wp_op = NULL;
38849884Ssheldonh		return EOI;
38949884Ssheldonh	}
39049884Ssheldonh	while (op->op_text) {
39149884Ssheldonh		if (strcmp(s, op->op_text) == 0) {
39249884Ssheldonh			if ((op->op_type == UNOP && isoperand()) ||
39349884Ssheldonh			    (op->op_num == LPAREN && *(t_wp+1) == 0))
39449884Ssheldonh				break;
39549884Ssheldonh			t_wp_op = op;
39649884Ssheldonh			return op->op_num;
3971556Srgrimes		}
39849884Ssheldonh		op++;
3991556Srgrimes	}
40049884Ssheldonh	t_wp_op = NULL;
40149884Ssheldonh	return OPERAND;
4021556Srgrimes}
4031556Srgrimes
40449884Ssheldonhstatic int
40549884Ssheldonhisoperand()
4061556Srgrimes{
40749884Ssheldonh	struct t_op const *op = ops;
40849884Ssheldonh	char *s;
40949884Ssheldonh	char *t;
4101556Srgrimes
41149884Ssheldonh	if ((s  = *(t_wp+1)) == 0)
41249884Ssheldonh		return 1;
41349884Ssheldonh	if ((t = *(t_wp+2)) == 0)
41449884Ssheldonh		return 0;
41549884Ssheldonh	while (op->op_text) {
41649884Ssheldonh		if (strcmp(s, op->op_text) == 0)
41749884Ssheldonh			return op->op_type == BINOP &&
41849884Ssheldonh			    (t[0] != ')' || t[1] != '\0');
41949884Ssheldonh		op++;
42049884Ssheldonh	}
42149884Ssheldonh	return 0;
4221556Srgrimes}
4231556Srgrimes
42449884Ssheldonh/* atoi with error detection */
42549884Ssheldonhstatic int
42649884Ssheldonhgetn(s)
42749884Ssheldonh	const char *s;
4281556Srgrimes{
42949884Ssheldonh	char *p;
43049884Ssheldonh	long r;
4311556Srgrimes
43249884Ssheldonh	errno = 0;
43349884Ssheldonh	r = strtol(s, &p, 10);
43449884Ssheldonh
43549884Ssheldonh	if (errno != 0)
43649884Ssheldonh	  errx(2, "%s: out of range", s);
43749884Ssheldonh
43849884Ssheldonh	while (isspace((unsigned char)*p))
43949884Ssheldonh	  p++;
44049884Ssheldonh
44149884Ssheldonh	if (*p)
44249884Ssheldonh	  errx(2, "%s: bad number", s);
44349884Ssheldonh
44449884Ssheldonh	return (int) r;
4451556Srgrimes}
44649884Ssheldonh
44749884Ssheldonhstatic int
44849884Ssheldonhnewerf (f1, f2)
44949884Ssheldonh	const char *f1, *f2;
45049884Ssheldonh{
45149884Ssheldonh	struct stat b1, b2;
45249884Ssheldonh
45349884Ssheldonh	return (stat (f1, &b1) == 0 &&
45449884Ssheldonh		stat (f2, &b2) == 0 &&
45549884Ssheldonh		b1.st_mtime > b2.st_mtime);
45649884Ssheldonh}
45749884Ssheldonh
45849884Ssheldonhstatic int
45949884Ssheldonholderf (f1, f2)
46049884Ssheldonh	const char *f1, *f2;
46149884Ssheldonh{
46249884Ssheldonh	struct stat b1, b2;
46349884Ssheldonh
46449884Ssheldonh	return (stat (f1, &b1) == 0 &&
46549884Ssheldonh		stat (f2, &b2) == 0 &&
46649884Ssheldonh		b1.st_mtime < b2.st_mtime);
46749884Ssheldonh}
46849884Ssheldonh
46949884Ssheldonhstatic int
47049884Ssheldonhequalf (f1, f2)
47149884Ssheldonh	const char *f1, *f2;
47249884Ssheldonh{
47349884Ssheldonh	struct stat b1, b2;
47449884Ssheldonh
47549884Ssheldonh	return (stat (f1, &b1) == 0 &&
47649884Ssheldonh		stat (f2, &b2) == 0 &&
47749884Ssheldonh		b1.st_dev == b2.st_dev &&
47849884Ssheldonh		b1.st_ino == b2.st_ino);
47949884Ssheldonh}
480