test.c revision 50471
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[] =
1550471Speter  "$FreeBSD: head/bin/test/test.c 50471 1999-08-27 23:15:48Z peter $";
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>
2476883Skris#include <stdio.h>
2586619Sknu#include <stdlib.h>
261556Srgrimes#include <string.h>
271556Srgrimes#include <unistd.h>
281556Srgrimes
291556Srgrimes/* test(1) accepts the following grammar:
301556Srgrimes	oexpr	::= aexpr | aexpr "-o" oexpr ;
3186505Sknu	aexpr	::= nexpr | nexpr "-a" aexpr ;
3286505Sknu	nexpr	::= primary | "!" primary
3386505Sknu	primary	::= unary-operator operand
3486618Sknu		| operand binary-operator operand
3588084Sache		| operand
3688084Sache		| "(" oexpr ")"
3786618Sknu		;
3886618Sknu	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
3986618Sknu		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
4086618Sknu
4186618Sknu	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
4286618Sknu			"-nt"|"-ot"|"-ef";
4386618Sknu	operand ::= <any legal UNIX file name>
4486618Sknu*/
4586505Sknu
4686618Sknuenum token {
4786618Sknu	EOI,
4886618Sknu	FILRD,
4986618Sknu	FILWR,
5086505Sknu	FILEX,
5186618Sknu	FILEXIST,
5286618Sknu	FILREG,
5386618Sknu	FILDIR,
5486618Sknu	FILCDEV,
5586618Sknu	FILBDEV,
5686618Sknu	FILFIFO,
5786618Sknu	FILSOCK,
5886618Sknu	FILSYM,
5986618Sknu	FILGZ,
6086618Sknu	FILTT,
6186618Sknu	FILSUID,
6249884Ssheldonh	FILSGID,
6349884Ssheldonh	FILSTCK,
6449884Ssheldonh	FILNT,
6549884Ssheldonh	FILOT,
6649884Ssheldonh	FILEQ,
6749884Ssheldonh	FILUID,
6849884Ssheldonh	FILGID,
6949884Ssheldonh	STREZ,
7049884Ssheldonh	STRNZ,
7149884Ssheldonh	STREQ,
7249884Ssheldonh	STRNE,
731556Srgrimes	STRLT,
7449884Ssheldonh	STRGT,
7549884Ssheldonh	INTEQ,
7649884Ssheldonh	INTNE,
7749884Ssheldonh	INTGE,
781556Srgrimes	INTGT,
7949884Ssheldonh	INTLE,
8049884Ssheldonh	INTLT,
8149884Ssheldonh	UNOT,
8249884Ssheldonh	BAND,
8349884Ssheldonh	BOR,
8449884Ssheldonh	LPAREN,
8549884Ssheldonh	RPAREN,
8649884Ssheldonh	OPERAND
8749884Ssheldonh};
8849884Ssheldonh
8949884Ssheldonhenum token_types {
9049884Ssheldonh	UNOP,
9149884Ssheldonh	BINOP,
9249884Ssheldonh	BUNOP,
9349884Ssheldonh	BBINOP,
9449884Ssheldonh	PAREN
9549884Ssheldonh};
9649884Ssheldonh
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},
1201556Srgrimes	{"-L",	FILSYM,	UNOP},
1211556Srgrimes	{"-S",	FILSOCK,UNOP},
12249884Ssheldonh	{"=",	STREQ,	BINOP},
12349884Ssheldonh	{"!=",	STRNE,	BINOP},
12449884Ssheldonh	{"<",	STRLT,	BINOP},
12549884Ssheldonh	{">",	STRGT,	BINOP},
12649884Ssheldonh	{"-eq",	INTEQ,	BINOP},
12749884Ssheldonh	{"-ne",	INTNE,	BINOP},
1281556Srgrimes	{"-ge",	INTGE,	BINOP},
1291556Srgrimes	{"-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}
14149884Ssheldonh};
14249884Ssheldonh
14349884Ssheldonhstruct t_op const *t_wp_op;
14449884Ssheldonhchar **t_wp;
14549884Ssheldonh
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
16049884Ssheldonhint
16149884Ssheldonhmain(argc, argv)
16249884Ssheldonh	int argc;
16349884Ssheldonh	char **argv;
16449884Ssheldonh{
16549884Ssheldonh	int	res;
16649884Ssheldonh
16749884Ssheldonh	if (strcmp(argv[0], "[") == 0) {
16849884Ssheldonh		if (strcmp(argv[--argc], "]"))
16949884Ssheldonh			errx(2, "missing ]");
17049884Ssheldonh		argv[argc] = NULL;
17149884Ssheldonh	}
17249884Ssheldonh
17349884Ssheldonh	/* XXX work around the absence of an eaccess(2) syscall */
1741556Srgrimes	(void)setgid(getegid());
1751556Srgrimes	(void)setuid(geteuid());
17649884Ssheldonh
17749884Ssheldonh	t_wp = &argv[1];
1781556Srgrimes	res = !oexpr(t_lex(*t_wp));
17976883Skris
18076883Skris	if (*t_wp != NULL && *++t_wp != NULL)
18176883Skris		syntax(*t_wp, "unexpected operator");
18276883Skris
18376883Skris	return res;
18488471Sache}
18576883Skris
18676883Skrisstatic void
18776883Skrissyntax(op, msg)
18876883Skris	const char	*op;
18976883Skris	const char	*msg;
19076883Skris{
19176883Skris
19276883Skris	if (op && *op)
19376883Skris		errx(2, "%s: %s", op, msg);
19476883Skris	else
19549884Ssheldonh		errx(2, "%s", msg);
1961556Srgrimes}
1971556Srgrimes
1981556Srgrimesstatic int
19949884Ssheldonhoexpr(n)
2001556Srgrimes	enum token n;
20149884Ssheldonh{
20255179Ssheldonh	int res;
2031556Srgrimes
20455179Ssheldonh	res = aexpr(n);
20555179Ssheldonh	if (t_lex(*++t_wp) == BOR)
20655179Ssheldonh		return oexpr(t_lex(*++t_wp)) || res;
20755179Ssheldonh	t_wp--;
20855179Ssheldonh	return res;
20986622Sknu}
21086618Sknu
2111556Srgrimesstatic int
2121556Srgrimesaexpr(n)
2131556Srgrimes	enum token n;
21486622Sknu{
21586622Sknu	int res;
21686622Sknu
21786622Sknu	res = nexpr(n);
21888084Sache	if (t_lex(*++t_wp) == BAND)
21988084Sache		return aexpr(t_lex(*++t_wp)) && res;
22088084Sache	t_wp--;
22150302Sgreen	return res;
22250087Sgreen}
22350087Sgreen
22450087Sgreenstatic int
22549884Ssheldonhnexpr(n)
22649884Ssheldonh	enum token n;			/* token */
2271556Srgrimes{
22849884Ssheldonh	if (n == UNOT)
22949884Ssheldonh		return !nexpr(t_lex(*++t_wp));
23049884Ssheldonh	return primary(n);
23149884Ssheldonh}
2321556Srgrimes
2331556Srgrimesstatic int
23449884Ssheldonhprimary(n)
23549884Ssheldonh	enum token n;
23649884Ssheldonh{
23749884Ssheldonh	enum token nn;
2381556Srgrimes	int res;
2391556Srgrimes
24049884Ssheldonh	if (n == EOI)
24186618Sknu		return 0;		/* missing expression */
24249884Ssheldonh	if (n == LPAREN) {
24386618Sknu		if ((nn = t_lex(*++t_wp)) == RPAREN)
2441556Srgrimes			return 0;	/* missing expression */
2451556Srgrimes		res = oexpr(nn);
24649884Ssheldonh		if (t_lex(*++t_wp) != RPAREN)
24749884Ssheldonh			syntax(NULL, "closing paren expected");
24849884Ssheldonh		return res;
2491556Srgrimes	}
25049884Ssheldonh	if (t_wp_op && t_wp_op->op_type == UNOP) {
2511556Srgrimes		/* unary expression */
25249884Ssheldonh		if (*++t_wp == NULL)
25349884Ssheldonh			syntax(t_wp_op->op_text, "argument expected");
25449884Ssheldonh		switch (n) {
25549884Ssheldonh		case STREZ:
25649884Ssheldonh			return strlen(*t_wp) == 0;
25749884Ssheldonh		case STRNZ:
2584171Sache			return strlen(*t_wp) != 0;
25949884Ssheldonh		case FILTT:
26049884Ssheldonh			return isatty(getn(*t_wp));
26149884Ssheldonh		default:
26249884Ssheldonh			return filstat(*t_wp, n);
26349884Ssheldonh		}
2641556Srgrimes	}
26549884Ssheldonh
26649884Ssheldonh	if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
26749884Ssheldonh		return binop();
26849884Ssheldonh	}
26949884Ssheldonh
2701556Srgrimes	return strlen(*t_wp) > 0;
2711556Srgrimes}
2721556Srgrimes
27349884Ssheldonhstatic int
27449884Ssheldonhbinop()
2751556Srgrimes{
27649884Ssheldonh	const char *opnd1, *opnd2;
27749884Ssheldonh	struct t_op const *op;
27849884Ssheldonh
2791556Srgrimes	opnd1 = *t_wp;
2801556Srgrimes	(void) t_lex(*++t_wp);
2811556Srgrimes	op = t_wp_op;
28249884Ssheldonh
28349884Ssheldonh	if ((opnd2 = *++t_wp) == NULL)
2841556Srgrimes		syntax(op->op_text, "argument expected");
28549884Ssheldonh
28649884Ssheldonh	switch (op->op_num) {
2871556Srgrimes	case STREQ:
28849884Ssheldonh		return strcmp(opnd1, opnd2) == 0;
28949884Ssheldonh	case STRNE:
29049884Ssheldonh		return strcmp(opnd1, opnd2) != 0;
29149884Ssheldonh	case STRLT:
29249884Ssheldonh		return strcmp(opnd1, opnd2) < 0;
29349884Ssheldonh	case STRGT:
29449884Ssheldonh		return strcmp(opnd1, opnd2) > 0;
29549884Ssheldonh	case INTEQ:
29649884Ssheldonh		return getn(opnd1) == getn(opnd2);
29749884Ssheldonh	case INTNE:
29849884Ssheldonh		return getn(opnd1) != getn(opnd2);
29949884Ssheldonh	case INTGE:
30049884Ssheldonh		return getn(opnd1) >= getn(opnd2);
30149884Ssheldonh	case INTGT:
30249884Ssheldonh		return getn(opnd1) > getn(opnd2);
30349884Ssheldonh	case INTLE:
30449884Ssheldonh		return getn(opnd1) <= getn(opnd2);
30549884Ssheldonh	case INTLT:
30649884Ssheldonh		return getn(opnd1) < getn(opnd2);
30749884Ssheldonh	case FILNT:
30849884Ssheldonh		return newerf (opnd1, opnd2);
30949884Ssheldonh	case FILOT:
31049884Ssheldonh		return olderf (opnd1, opnd2);
31149884Ssheldonh	case FILEQ:
31249884Ssheldonh		return equalf (opnd1, opnd2);
3131556Srgrimes	default:
31449884Ssheldonh		abort();
31549884Ssheldonh		/* NOTREACHED */
31649884Ssheldonh	}
31749884Ssheldonh}
31849884Ssheldonh
3191556Srgrimesstatic int
3201556Srgrimesfilstat(nm, mode)
3211556Srgrimes	char *nm;
32249884Ssheldonh	enum token mode;
3231556Srgrimes{
32449884Ssheldonh	struct stat s;
32549884Ssheldonh
3261556Srgrimes	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
32749884Ssheldonh		return 0;
32849884Ssheldonh
32949884Ssheldonh	switch (mode) {
3301556Srgrimes	case FILRD:
33149884Ssheldonh		return access(nm, R_OK) == 0;
33249884Ssheldonh	case FILWR:
33349884Ssheldonh		return access(nm, W_OK) == 0;
33449884Ssheldonh	case FILEX:
33549884Ssheldonh		/* XXX work around access(2) false positives for superuser */
33649884Ssheldonh		if (access(nm, X_OK) != 0)
33749884Ssheldonh			return 0;
33849884Ssheldonh		if (S_ISDIR(s.st_mode) || getuid() != 0)
33949884Ssheldonh			return 1;
34049884Ssheldonh		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
34149884Ssheldonh	case FILEXIST:
34249884Ssheldonh		return access(nm, F_OK) == 0;
34349884Ssheldonh	case FILREG:
34462925Sse		return S_ISREG(s.st_mode);
34549884Ssheldonh	case FILDIR:
34662925Sse		return S_ISDIR(s.st_mode);
34749884Ssheldonh	case FILCDEV:
34862925Sse		return S_ISCHR(s.st_mode);
34949884Ssheldonh	case FILBDEV:
35062925Sse		return S_ISBLK(s.st_mode);
35149884Ssheldonh	case FILFIFO:
35262925Sse		return S_ISFIFO(s.st_mode);
35349884Ssheldonh	case FILSOCK:
35462925Sse		return S_ISSOCK(s.st_mode);
35549884Ssheldonh	case FILSYM:
35649884Ssheldonh		return S_ISLNK(s.st_mode);
35749884Ssheldonh	case FILSUID:
35849884Ssheldonh		return (s.st_mode & S_ISUID) != 0;
35949884Ssheldonh	case FILSGID:
36049884Ssheldonh		return (s.st_mode & S_ISGID) != 0;
36149884Ssheldonh	case FILSTCK:
36249884Ssheldonh		return (s.st_mode & S_ISVTX) != 0;
36349884Ssheldonh	case FILGZ:
3641556Srgrimes		return s.st_size > (off_t)0;
3651556Srgrimes	case FILUID:
3661556Srgrimes		return s.st_uid == geteuid();
36749884Ssheldonh	case FILGID:
36849884Ssheldonh		return s.st_gid == getegid();
36949884Ssheldonh	default:
37049884Ssheldonh		return 1;
3711556Srgrimes	}
37249884Ssheldonh}
3731556Srgrimes
37449884Ssheldonhstatic enum token
37549884Ssheldonht_lex(s)
3762664Scsgr	char *s;
37749884Ssheldonh{
37849884Ssheldonh	struct t_op const *op = ops;
37949884Ssheldonh
38049884Ssheldonh	if (s == 0) {
38149884Ssheldonh		t_wp_op = NULL;
38249884Ssheldonh		return EOI;
38350302Sgreen	}
38450087Sgreen	while (op->op_text) {
38550087Sgreen		if (strcmp(s, op->op_text) == 0) {
38650087Sgreen			if ((op->op_type == UNOP && isoperand()) ||
38750087Sgreen			    (op->op_num == LPAREN && *(t_wp+1) == 0))
38850087Sgreen				break;
38949884Ssheldonh			t_wp_op = op;
39049884Ssheldonh			return op->op_num;
39149884Ssheldonh		}
39249884Ssheldonh		op++;
39349884Ssheldonh	}
39449884Ssheldonh	t_wp_op = NULL;
39549884Ssheldonh	return OPERAND;
39649884Ssheldonh}
39749884Ssheldonh
39849884Ssheldonhstatic int
39949884Ssheldonhisoperand()
40049884Ssheldonh{
40149884Ssheldonh	struct t_op const *op = ops;
40249884Ssheldonh	char *s;
40349884Ssheldonh	char *t;
40449884Ssheldonh
40549884Ssheldonh	if ((s  = *(t_wp+1)) == 0)
40649884Ssheldonh		return 1;
40749884Ssheldonh	if ((t = *(t_wp+2)) == 0)
40849884Ssheldonh		return 0;
40949884Ssheldonh	while (op->op_text) {
41049884Ssheldonh		if (strcmp(s, op->op_text) == 0)
41149884Ssheldonh			return op->op_type == BINOP &&
41249884Ssheldonh			    (t[0] != ')' || t[1] != '\0');
41349884Ssheldonh		op++;
41449884Ssheldonh	}
41549884Ssheldonh	return 0;
41649884Ssheldonh}
41749884Ssheldonh
41849884Ssheldonh/* atoi with error detection */
4192675Scsgrstatic int
42049884Ssheldonhgetn(s)
4212675Scsgr	const char *s;
42249884Ssheldonh{
42349884Ssheldonh	char *p;
42449884Ssheldonh	long r;
42549884Ssheldonh
42649884Ssheldonh	errno = 0;
42749884Ssheldonh	r = strtol(s, &p, 10);
42849884Ssheldonh
42949884Ssheldonh	if (errno != 0)
43049884Ssheldonh	  errx(2, "%s: out of range", s);
43149884Ssheldonh
43249884Ssheldonh	while (isspace((unsigned char)*p))
43349884Ssheldonh	  p++;
43449884Ssheldonh
43549884Ssheldonh	if (*p)
43649884Ssheldonh	  errx(2, "%s: bad number", s);
43749884Ssheldonh
43849884Ssheldonh	return (int) r;
4391556Srgrimes}
44049884Ssheldonh
4411556Srgrimesstatic int
44249884Ssheldonhnewerf (f1, f2)
44349884Ssheldonh	const char *f1, *f2;
4441556Srgrimes{
4451556Srgrimes	struct stat b1, b2;
44649884Ssheldonh
44749884Ssheldonh	return (stat (f1, &b1) == 0 &&
4481556Srgrimes		stat (f2, &b2) == 0 &&
44949884Ssheldonh		b1.st_mtime > b2.st_mtime);
45049884Ssheldonh}
45149884Ssheldonh
4521556Srgrimesstatic int
45349884Ssheldonholderf (f1, f2)
45449884Ssheldonh	const char *f1, *f2;
45549884Ssheldonh{
45649884Ssheldonh	struct stat b1, b2;
45749884Ssheldonh
45849884Ssheldonh	return (stat (f1, &b1) == 0 &&
45949884Ssheldonh		stat (f2, &b2) == 0 &&
46049884Ssheldonh		b1.st_mtime < b2.st_mtime);
46149884Ssheldonh}
46249884Ssheldonh
46349884Ssheldonhstatic int
4641556Srgrimesequalf (f1, f2)
4651556Srgrimes	const char *f1, *f2;
46649884Ssheldonh{
46749884Ssheldonh	struct stat b1, b2;
46849884Ssheldonh
46949884Ssheldonh	return (stat (f1, &b1) == 0 &&
4701556Srgrimes		stat (f2, &b2) == 0 &&
47149884Ssheldonh		b1.st_dev == b2.st_dev &&
47249884Ssheldonh		b1.st_ino == b2.st_ino);
4731556Srgrimes}
47449884Ssheldonh