xref: /csrg-svn/bin/test/test.c (revision 53958)
1*53958Sbostic /*-
2*53958Sbostic  * Copyright (c) 1992 The Regents of the University of California.
353852Selan  * All rights reserved.
453835Selan  *
553852Selan  * This code is derived from software contributed to Berkeley by
653852Selan  * Kenneth Almquist.
753852Selan  *
853852Selan  * %sccs.include.redist.c%
953835Selan  */
1053835Selan 
1153852Selan #ifndef lint
12*53958Sbostic char copyright[] =
13*53958Sbostic "@(#) Copyright (c) 1992 The Regents of the University of California.\n\
1453852Selan  All rights reserved.\n";
15*53958Sbostic #endif /* not lint */
1653835Selan 
1753852Selan #ifndef lint
18*53958Sbostic static char sccsid[] = "@(#)test.c	5.1 (Berkeley) 06/08/92";
19*53958Sbostic #endif /* not lint */
2053835Selan 
2153835Selan #include <sys/types.h>
2253835Selan #include <sys/stat.h>
23*53958Sbostic #include <errno.h>
24*53958Sbostic #include <stdlib.h>
2553918Selan #include <string.h>
26*53958Sbostic #include <stdio.h>
27*53958Sbostic 
2853835Selan #include "operators.h"
2953835Selan 
30*53958Sbostic #define	STACKSIZE	12
31*53958Sbostic #define	NESTINCR	16
3253835Selan 
3353835Selan /* data types */
34*53958Sbostic #define	STRING	0
35*53958Sbostic #define	INTEGER	1
36*53958Sbostic #define	BOOLEAN	2
3753835Selan 
38*53958Sbostic #define	IS_BANG(s) (s[0] == '!' && s[1] == '\0')
3953835Selan 
4053835Selan /*
4153835Selan  * This structure hold a value.  The type keyword specifies the type of
4253835Selan  * the value, and the union u holds the value.  The value of a boolean
4353835Selan  * is stored in u.num (1 = TRUE, 0 = FALSE).
4453835Selan  */
4553835Selan struct value {
46*53958Sbostic 	int type;
4753852Selan 	union {
48*53958Sbostic 		char *string;
49*53958Sbostic 		long num;
50*53958Sbostic 	} u;
5153835Selan };
5253835Selan 
5353835Selan struct operator {
54*53958Sbostic 	short op;		/* Which operator. */
55*53958Sbostic 	short pri;		/* Priority of operator. */
5653835Selan };
5753835Selan 
5853835Selan struct filestat {
59*53958Sbostic 	char *name;		/* Name of file. */
60*53958Sbostic 	int rcode;		/* Return code from stat. */
61*53958Sbostic 	struct stat stat;	/* Status info on file. */
6253835Selan };
6353835Selan 
64*53958Sbostic static long	atol __P((const char *));
65*53958Sbostic static void	err __P((const char *, ...));
66*53958Sbostic static int	expr_is_false __P((struct value *));
67*53958Sbostic static void	expr_operator __P((int, struct value *, struct filestat *));
68*53958Sbostic static int	int_tcheck __P((char *));
69*53958Sbostic static int	lookup_op __P((char *, char *const *));
70*53958Sbostic static void	overflow __P((void));
71*53958Sbostic static int	posix_binary_op __P((char **));
72*53958Sbostic static int	posix_unary_op __P((char **));
73*53958Sbostic static void	syntax __P((void));
7453835Selan 
7553835Selan int
7653852Selan main(argc, argv)
77*53958Sbostic 	int argc;
78*53958Sbostic 	char *argv[];
7953835Selan {
8053852Selan 	struct operator opstack[STACKSIZE];
8153852Selan 	struct operator *opsp;
8253852Selan 	struct value valstack[STACKSIZE + 1];
8353852Selan 	struct value *valsp;
8453852Selan 	struct filestat fs;
85*53958Sbostic 	char  c, **ap, *opname, *p;
86*53958Sbostic 	int binary, nest, op, pri, ret_val, skipping;
8753835Selan 
88*53958Sbostic 	if (argv[0] == NULL) {
89*53958Sbostic 		err("test: argc is zero.\n");
90*53958Sbostic 		exit(2);
91*53958Sbostic 	}
92*53958Sbostic 
9353852Selan 	if (**argv == '[') {
94*53958Sbostic 		if (strcmp(argv[argc - 1], "]"))
95*53958Sbostic 			err("missing ]");
9653852Selan 		argv[argc - 1] = NULL;
9753852Selan 	}
9853852Selan 	ap = argv + 1;
9953852Selan 	fs.name = NULL;
10053835Selan 
101*53958Sbostic 	/*
102*53958Sbostic 	 * Test(1) implements an inherently ambiguous grammer.  In order to
103*53958Sbostic 	 * assure some degree of consistency, we special case the POSIX 1003.2
104*53958Sbostic 	 * requirements to assure correct evaluation for POSIX scripts.  The
105*53958Sbostic 	 * following special cases comply with POSIX P1003.2/D11.2 Section
106*53958Sbostic 	 * 4.62.4.
107*53958Sbostic 	 */
108*53958Sbostic 	switch(argc - 1) {
109*53958Sbostic 	case 0:				/* % test */
110*53958Sbostic 		return (1);
11153852Selan 		break;
112*53958Sbostic 	case 1:				/* % test arg */
11353852Selan 		return (*argv[1] == '\0') ? 1 : 0;
11453852Selan 		break;
115*53958Sbostic 	case 2:				/* % test op arg */
11653852Selan 		opname = argv[1];
11753852Selan 		if (IS_BANG(opname))
11853852Selan 			return (*argv[2] == '\0') ? 1 : 0;
11953852Selan 		else {
12053852Selan 			ret_val = posix_unary_op(&argv[1]);
12153852Selan 			if (ret_val >= 0)
122*53958Sbostic 				return (ret_val);
12353852Selan 		}
12453852Selan 		break;
125*53958Sbostic 	case 3:				/* % test arg1 op arg2 */
12653852Selan 		if (IS_BANG(argv[1])) {
12753852Selan 			ret_val = posix_unary_op(&argv[1]);
12853852Selan 			if (ret_val >= 0)
129*53958Sbostic 				return (!ret_val);
13053852Selan 		} else {
13153852Selan 			ret_val = posix_binary_op(&argv[1]);
13253852Selan 			if (ret_val >= 0)
133*53958Sbostic 				return (ret_val);
13453852Selan 		}
13553852Selan 		break;
136*53958Sbostic 	case 4:				/* % test ! arg1 op arg2 */
13753852Selan 		if (IS_BANG(argv[1])) {
13853852Selan 			ret_val = posix_binary_op(&argv[2]);
13953852Selan 			if (ret_val >= 0)
140*53958Sbostic 				return (!ret_val);
14153852Selan 		}
14253852Selan 		break;
14353852Selan 	default:
14453852Selan 		break;
14553852Selan 	}
14653835Selan 
147*53958Sbostic 	/*
148*53958Sbostic 	 * We use operator precedence parsing, evaluating the expression as
14953852Selan 	 * we parse it.  Parentheses are handled by bumping up the priority
15053852Selan 	 * of operators using the variable "nest."  We use the variable
15153852Selan 	 * "skipping" to turn off evaluation temporarily for the short
15253852Selan 	 * circuit boolean operators.  (It is important do the short circuit
15353852Selan 	 * evaluation because under NFS a stat operation can take infinitely
154*53958Sbostic 	 * long.)
155*53958Sbostic 	 */
15653852Selan 	opsp = opstack + STACKSIZE;
15753852Selan 	valsp = valstack;
158*53958Sbostic 	nest = skipping = 0;
15953852Selan 	if (*ap == NULL) {
16053852Selan 		valstack[0].type = BOOLEAN;
16153852Selan 		valstack[0].u.num = 0;
16253852Selan 		goto done;
16353852Selan 	}
16453852Selan 	for (;;) {
16553852Selan 		opname = *ap++;
16653852Selan 		if (opname == NULL)
167*53958Sbostic 			syntax();
16853852Selan 		if (opname[0] == '(' && opname[1] == '\0') {
16953852Selan 			nest += NESTINCR;
17053852Selan 			continue;
17153852Selan 		} else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
17253852Selan 			if (opsp == &opstack[0])
173*53958Sbostic 				overflow();
17453852Selan 			--opsp;
17553852Selan 			opsp->op = op;
17653852Selan 			opsp->pri = op_priority[op] + nest;
17753852Selan 			continue;
17853852Selan 		} else {
17953852Selan 			valsp->type = STRING;
18053852Selan 			valsp->u.string = opname;
18153852Selan 			valsp++;
18253852Selan 		}
18353852Selan 		for (;;) {
18453852Selan 			opname = *ap++;
18553852Selan 			if (opname == NULL) {
18653852Selan 				if (nest != 0)
187*53958Sbostic 					syntax();
18853852Selan 				pri = 0;
18953852Selan 				break;
19053852Selan 			}
19153852Selan 			if (opname[0] != ')' || opname[1] != '\0') {
19253852Selan 				if ((op = lookup_op(opname, binary_op)) < 0)
193*53958Sbostic 					syntax();
19453852Selan 				op += FIRST_BINARY_OP;
19553852Selan 				pri = op_priority[op] + nest;
19653852Selan 				break;
19753852Selan 			}
19853852Selan 			if ((nest -= NESTINCR) < 0)
199*53958Sbostic 				syntax();
20053852Selan 		}
20153852Selan 		while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) {
20253852Selan 			binary = opsp->op;
20353852Selan 			for (;;) {
20453852Selan 				valsp--;
20553852Selan 				c = op_argflag[opsp->op];
20653852Selan 				if (c == OP_INT) {
20753852Selan 					if (valsp->type == STRING &&
20853852Selan 					    int_tcheck(valsp->u.string))
20953852Selan 						valsp->u.num =
21053852Selan 						    atol(valsp->u.string);
21153852Selan 					valsp->type = INTEGER;
21253852Selan 				} else if (c >= OP_STRING) {
21353852Selan 					            /* OP_STRING or OP_FILE */
21453852Selan 					if (valsp->type == INTEGER) {
215*53958Sbostic 						if ((p = malloc(32)) == NULL)
216*53958Sbostic 							err("%s",
217*53958Sbostic 							    strerror(errno));
21853835Selan #ifdef SHELL
21953852Selan 						fmtstr(p, 32, "%d",
22053852Selan 						    valsp->u.num);
22153835Selan #else
222*53958Sbostic 						(void)sprintf(p,
223*53958Sbostic 						    "%d", valsp->u.num);
22453835Selan #endif
22553852Selan 						valsp->u.string = p;
22653852Selan 					} else if (valsp->type == BOOLEAN) {
22753852Selan 						if (valsp->u.num)
22853852Selan 							valsp->u.string =
22953852Selan 						            "true";
23053852Selan 						else
23153852Selan 							valsp->u.string = "";
23253852Selan 					}
23353852Selan 					valsp->type = STRING;
234*53958Sbostic 					if (c == OP_FILE && (fs.name == NULL ||
235*53958Sbostic 					    strcmp(fs.name, valsp->u.string))) {
23653852Selan 						fs.name = valsp->u.string;
23753852Selan 						fs.rcode =
23853852Selan 						    stat(valsp->u.string,
23953852Selan                                                     &fs.stat);
24053852Selan 					}
24153852Selan 				}
24253852Selan 				if (binary < FIRST_BINARY_OP)
24353852Selan 					break;
24453852Selan 				binary = 0;
24553835Selan 			}
24653852Selan 			if (!skipping)
24753852Selan 				expr_operator(opsp->op, valsp, &fs);
24853852Selan 			else if (opsp->op == AND1 || opsp->op == OR1)
24953852Selan 				skipping--;
250*53958Sbostic 			valsp++;		/* push value */
251*53958Sbostic 			opsp++;			/* pop operator */
25253852Selan 		}
25353852Selan 		if (opname == NULL)
25453852Selan 			break;
25553852Selan 		if (opsp == &opstack[0])
256*53958Sbostic 			overflow();
25753852Selan 		if (op == AND1 || op == AND2) {
25853852Selan 			op = AND1;
25953852Selan 			if (skipping || expr_is_false(valsp - 1))
26053852Selan 				skipping++;
26153852Selan 		}
26253852Selan 		if (op == OR1 || op == OR2) {
26353852Selan 			op = OR1;
26453852Selan 			if (skipping || !expr_is_false(valsp - 1))
26553852Selan 				skipping++;
26653852Selan 		}
26753852Selan 		opsp--;
26853852Selan 		opsp->op = op;
26953852Selan 		opsp->pri = pri;
27053852Selan 	}
271*53958Sbostic done:	return (expr_is_false(&valstack[0]));
27253835Selan }
27353835Selan 
27453835Selan static int
27553835Selan expr_is_false(val)
27653852Selan 	struct value *val;
27753835Selan {
27853852Selan 	if (val->type == STRING) {
27953852Selan 		if (val->u.string[0] == '\0')
280*53958Sbostic 			return (1);
28153852Selan 	} else {		/* INTEGER or BOOLEAN */
28253852Selan 		if (val->u.num == 0)
283*53958Sbostic 			return (1);
28453852Selan 	}
285*53958Sbostic 	return (0);
28653835Selan }
28753835Selan 
28853835Selan 
28953835Selan /*
29053835Selan  * Execute an operator.  Op is the operator.  Sp is the stack pointer;
29153835Selan  * sp[0] refers to the first operand, sp[1] refers to the second operand
29253835Selan  * (if any), and the result is placed in sp[0].  The operands are converted
29353835Selan  * to the type expected by the operator before expr_operator is called.
29453835Selan  * Fs is a pointer to a structure which holds the value of the last call
29553835Selan  * to stat, to avoid repeated stat calls on the same file.
29653835Selan  */
29753835Selan static void
29853835Selan expr_operator(op, sp, fs)
299*53958Sbostic 	int op;
30053835Selan 	struct value *sp;
30153835Selan 	struct filestat *fs;
30253835Selan {
303*53958Sbostic 	int i;
30453835Selan 
30553835Selan 	switch (op) {
30653835Selan 	case NOT:
30753852Selan 		sp->u.num = expr_is_false(sp);
30853852Selan 		sp->type = BOOLEAN;
30953852Selan 		break;
31053852Selan 	case ISEXIST:
31153852Selan 		if (fs == NULL || fs->rcode == -1)
31253852Selan 			goto false;
31353852Selan 		else
31453852Selan 			goto true;
31553852Selan 	case ISREAD:
316*53958Sbostic 		i = S_IROTH;
31753852Selan 		goto permission;
31853852Selan 	case ISWRITE:
319*53958Sbostic 		i = S_IWOTH;
32053852Selan 		goto permission;
32153852Selan 	case ISEXEC:
322*53958Sbostic 		i = S_IXOTH;
323*53958Sbostic permission:	if (fs->stat.st_uid == geteuid())
32453852Selan 			i <<= 6;
32553852Selan 		else if (fs->stat.st_gid == getegid())
32653852Selan 			i <<= 3;
32753852Selan 		goto filebit;	/* true if (stat.st_mode & i) != 0 */
32853852Selan 	case ISFILE:
32953852Selan 		i = S_IFREG;
33053852Selan 		goto filetype;
33153852Selan 	case ISDIR:
33253852Selan 		i = S_IFDIR;
33353852Selan 		goto filetype;
33453852Selan 	case ISCHAR:
33553852Selan 		i = S_IFCHR;
33653852Selan 		goto filetype;
33753852Selan 	case ISBLOCK:
33853852Selan 		i = S_IFBLK;
33953852Selan 		goto filetype;
34053852Selan 	case ISFIFO:
34153852Selan 		i = S_IFIFO;
34253852Selan 		goto filetype;
343*53958Sbostic filetype:	if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0)
344*53958Sbostic true:			sp->u.num = 1;
345*53958Sbostic 		else
346*53958Sbostic false:			sp->u.num = 0;
34753852Selan 		sp->type = BOOLEAN;
34853852Selan 		break;
34953852Selan 	case ISSETUID:
35053852Selan 		i = S_ISUID;
35153852Selan 		goto filebit;
35253852Selan 	case ISSETGID:
35353852Selan 		i = S_ISGID;
35453852Selan 		goto filebit;
35553852Selan 	case ISSTICKY:
35653852Selan 		i = S_ISVTX;
357*53958Sbostic filebit:	if (fs->stat.st_mode & i && fs->rcode >= 0)
35853852Selan 			goto true;
35953852Selan 		goto false;
36053852Selan 	case ISSIZE:
36153852Selan 		sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L;
36253852Selan 		sp->type = INTEGER;
36353852Selan 		break;
36453852Selan 	case ISTTY:
36553852Selan 		sp->u.num = isatty(sp->u.num);
36653852Selan 		sp->type = BOOLEAN;
36753852Selan 		break;
36853852Selan 	case NULSTR:
36953852Selan 		if (sp->u.string[0] == '\0')
37053852Selan 			goto true;
37153852Selan 		goto false;
37253852Selan 	case STRLEN:
37353852Selan 		sp->u.num = strlen(sp->u.string);
37453852Selan 		sp->type = INTEGER;
37553852Selan 		break;
37653852Selan 	case OR1:
37753852Selan 	case AND1:
378*53958Sbostic 		/*
379*53958Sbostic 		 * These operators are mostly handled by the parser.  If we
38053852Selan 		 * get here it means that both operands were evaluated, so
381*53958Sbostic 		 * the value is the value of the second operand.
382*53958Sbostic 		 */
38353852Selan 		*sp = *(sp + 1);
38453852Selan 		break;
38553852Selan 	case STREQ:
38653852Selan 	case STRNE:
38753852Selan 		i = 0;
388*53958Sbostic 		if (!strcmp(sp->u.string, (sp + 1)->u.string))
38953852Selan 			i++;
39053852Selan 		if (op == STRNE)
39153852Selan 			i = 1 - i;
39253852Selan 		sp->u.num = i;
39353852Selan 		sp->type = BOOLEAN;
39453852Selan 		break;
39553852Selan 	case EQ:
39653852Selan 		if (sp->u.num == (sp + 1)->u.num)
39753852Selan 			goto true;
39853852Selan 		goto false;
39953852Selan 	case NE:
40053852Selan 		if (sp->u.num != (sp + 1)->u.num)
40153852Selan 			goto true;
40253852Selan 		goto false;
40353852Selan 	case GT:
40453852Selan 		if (sp->u.num > (sp + 1)->u.num)
40553852Selan 			goto true;
40653852Selan 		goto false;
40753852Selan 	case LT:
40853852Selan 		if (sp->u.num < (sp + 1)->u.num)
40953852Selan 			goto true;
41053852Selan 		goto false;
41153852Selan 	case LE:
41253852Selan 		if (sp->u.num <= (sp + 1)->u.num)
41353852Selan 			goto true;
41453852Selan 		goto false;
41553852Selan 	case GE:
41653852Selan 		if (sp->u.num >= (sp + 1)->u.num)
41753852Selan 			goto true;
41853852Selan 		goto false;
41953835Selan 
42053852Selan 	}
42153835Selan }
42253835Selan 
42353835Selan static int
42453835Selan lookup_op(name, table)
42553852Selan 	char   *name;
42653852Selan 	char   *const * table;
42753835Selan {
42853852Selan 	register char *const * tp;
42953852Selan 	register char const *p;
430*53958Sbostic 	char c;
43153835Selan 
432*53958Sbostic 	c = name[1];
433*53958Sbostic 	for (tp = table; (p = *tp) != NULL; tp++)
434*53958Sbostic 		if (p[1] == c && !strcmp(p, name))
435*53958Sbostic 			return (tp - table);
436*53958Sbostic 	return (-1);
43753835Selan }
43853835Selan 
43953835Selan static int
44053835Selan posix_unary_op(argv)
441*53958Sbostic 	char **argv;
44253835Selan {
44353835Selan 	struct filestat fs;
44453835Selan 	struct value valp;
445*53958Sbostic 	int op, c;
446*53958Sbostic 	char *opname;
44753835Selan 
44853835Selan 	opname = *argv;
44953852Selan 	if ((op = lookup_op(opname, unary_op)) < 0)
450*53958Sbostic 		return (-1);
45153852Selan 	c = op_argflag[op];
45253852Selan 	opname = argv[1];
45353852Selan 	valp.u.string = opname;
45453852Selan 	if (c == OP_FILE) {
45553835Selan 		fs.name = opname;
45653835Selan 		fs.rcode = stat(opname, &fs.stat);
45753835Selan 	} else if (c != OP_STRING)
458*53958Sbostic 		return (-1);
45953835Selan 
46053835Selan 	expr_operator(op, &valp, &fs);
46153835Selan 	return (valp.u.num == 0);
46253835Selan }
46353835Selan 
46453835Selan static int
46553835Selan posix_binary_op(argv)
46653852Selan 	char  **argv;
46753835Selan {
46853835Selan 	struct value v[2];
469*53958Sbostic 	int op, c;
470*53958Sbostic 	char *opname;
47153852Selan 
47253835Selan 	opname = argv[1];
47353835Selan 	if ((op = lookup_op(opname, binary_op)) < 0)
474*53958Sbostic 		return (-1);
47553835Selan 	op += FIRST_BINARY_OP;
47653835Selan 	c = op_argflag[op];
47753835Selan 
47853835Selan 	if (c == OP_INT && int_tcheck(argv[0]) && int_tcheck(argv[2])) {
47953835Selan 		v[0].u.num = atol(argv[0]);
48053835Selan 		v[1].u.num = atol(argv[2]);
48153918Selan 	} else {
48253918Selan 		v[0].u.string = argv[0];
48353918Selan 		v[1].u.string = argv[2];
48453835Selan 	}
48553835Selan 	expr_operator(op, v, NULL);
48653835Selan 	return (v[0].u.num == 0);
48753835Selan }
48853835Selan 
48953835Selan /*
49053835Selan  * Integer type checking.
49153835Selan  */
49253835Selan static int
49353835Selan int_tcheck(v)
494*53958Sbostic 	char *v;
49553835Selan {
496*53958Sbostic 	char *p;
49753835Selan 
49853835Selan 	for (p = v; *p != '\0'; p++)
499*53958Sbostic 		if (!isdigit(*p))
500*53958Sbostic 			err("illegal operand \"%s\" -- expected integer.", v);
501*53958Sbostic 	return (1);
50253835Selan }
503*53958Sbostic 
504*53958Sbostic static void
505*53958Sbostic syntax()
506*53958Sbostic {
507*53958Sbostic 	err("syntax error");
508*53958Sbostic }
509*53958Sbostic 
510*53958Sbostic static void
511*53958Sbostic overflow()
512*53958Sbostic {
513*53958Sbostic 	err("expression is too complex");
514*53958Sbostic }
515*53958Sbostic 
516*53958Sbostic #if __STDC__
517*53958Sbostic #include <stdarg.h>
518*53958Sbostic #else
519*53958Sbostic #include <varargs.h>
520*53958Sbostic #endif
521*53958Sbostic 
522*53958Sbostic void
523*53958Sbostic #if __STDC__
524*53958Sbostic err(const char *fmt, ...)
525*53958Sbostic #else
526*53958Sbostic err(fmt, va_alist)
527*53958Sbostic 	char *fmt;
528*53958Sbostic         va_dcl
529*53958Sbostic #endif
530*53958Sbostic {
531*53958Sbostic 	va_list ap;
532*53958Sbostic #if __STDC__
533*53958Sbostic 	va_start(ap, fmt);
534*53958Sbostic #else
535*53958Sbostic 	va_start(ap);
536*53958Sbostic #endif
537*53958Sbostic 	(void)fprintf(stderr, "test: ");
538*53958Sbostic 	(void)vfprintf(stderr, fmt, ap);
539*53958Sbostic 	va_end(ap);
540*53958Sbostic 	(void)fprintf(stderr, "\n");
541*53958Sbostic 	exit(2);
542*53958Sbostic 	/* NOTREACHED */
543*53958Sbostic }
544