xref: /csrg-svn/bin/test/test.c (revision 53918)
153835Selan /*
253852Selan  * Copyright (c) 1988 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
1253859Selan static char copyright[] =
1353852Selan "@(#) Copyright (c) 1988 The Regents of the University of California.\n\
1453852Selan  All rights reserved.\n";
1553852Selan #endif				/* not lint */
1653835Selan 
1753852Selan #ifndef lint
18*53918Selan static char sccsid[] = "@(#)test.c	1.4 (Berkeley) 06/05/92";
1953852Selan #endif				/* not lint */
2053835Selan 
2153835Selan #include <stdio.h>
2253835Selan #include <errno.h>
2353835Selan #include <sys/types.h>
2453835Selan #include <sys/stat.h>
25*53918Selan #include <string.h>
2653835Selan #include "operators.h"
2753835Selan #include "extern.h"
2853835Selan 
2953835Selan #define STACKSIZE 12
3053835Selan #define NESTINCR 16
3153835Selan 
3253835Selan /* data types */
3353835Selan #define STRING 0
3453835Selan #define INTEGER 1
3553835Selan #define BOOLEAN 2
3653835Selan 
3753835Selan #define INITARGS(argv)	if (argv[0] == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else
3853835Selan 
3953835Selan #define IS_BANG(s) (s[0] == '!' && s[1] == '\0')
4053835Selan #define equal(s1, s2)   (strcmp(s1, s2) == 0)
4153835Selan 
4253835Selan /*
4353835Selan  * This structure hold a value.  The type keyword specifies the type of
4453835Selan  * the value, and the union u holds the value.  The value of a boolean
4553835Selan  * is stored in u.num (1 = TRUE, 0 = FALSE).
4653835Selan  */
4753835Selan 
4853835Selan struct value {
4953852Selan 	int     type;
5053852Selan 	union {
5153852Selan 		char   *string;
5253852Selan 		long    num;
5353852Selan 	}       u;
5453835Selan };
5553835Selan 
5653835Selan struct operator {
5753852Selan 	short   op;		/* which operator */
5853852Selan 	short   pri;		/* priority of operator */
5953835Selan };
6053835Selan 
6153835Selan struct filestat {
6253852Selan 	char   *name;		/* name of file */
6353852Selan 	int     rcode;		/* return code from stat */
6453852Selan 	struct stat stat;	/* status info on file */
6553835Selan };
6653835Selan 
6753835Selan static int expr_is_false __P((struct value *));
6853835Selan static void expr_operator __P((int, struct value *, struct filestat *));
6953852Selan static int lookup_op __P((char *, char *const *));
7053835Selan static long atol __P((const char *));
7153835Selan static int posix_binary_op __P((char **));
7253835Selan static int posix_unary_op __P((char **));
7353835Selan static int int_tcheck __P((char *));
7453835Selan 
7553835Selan int
7653852Selan main(argc, argv)
7753852Selan 	int     argc;
7853852Selan 	char  **argv;
7953835Selan {
8053852Selan 	char  **ap;
8153852Selan 	char   *opname;
8253852Selan 	char    c;
8353852Selan 	char   *p;
8453852Selan 	int     nest;		/* parentheses nesting */
8553852Selan 	int     op;
8653852Selan 	int     pri;
8753852Selan 	int     skipping;
8853852Selan 	int     binary;
8953852Selan 	struct operator opstack[STACKSIZE];
9053852Selan 	struct operator *opsp;
9153852Selan 	struct value valstack[STACKSIZE + 1];
9253852Selan 	struct value *valsp;
9353852Selan 	struct filestat fs;
9453852Selan 	int     ret_val;
9553835Selan 
9653852Selan 	INITARGS(argv);
9753852Selan 	if (**argv == '[') {
9853852Selan 		if (!equal(argv[argc - 1], "]"))
9953852Selan 			error("missing ]");
10053852Selan 		argv[argc - 1] = NULL;
10153852Selan 	}
10253852Selan 	ap = argv + 1;
10353852Selan 	fs.name = NULL;
10453835Selan 
10553852Selan 	/* Test(1) implements an inherently ambiguous grammer.  In order to
10653852Selan 	 * assure some degree of consistency, we special case the POSIX
10753852Selan 	 * requirements to assure correct evaluation for POSIX following
10853852Selan 	 * scripts.  The following special cases comply with POSIX
10953852Selan 	 * P1003.2/D11.2 Section 4.62.4. */
11053852Selan 	switch (argc - 1) {
11153852Selan 	case 0:		/* % test */
11253852Selan 		return 1;
11353852Selan 		break;
11453852Selan 	case 1:		/* % test arg */
11553852Selan 		return (*argv[1] == '\0') ? 1 : 0;
11653852Selan 		break;
11753852Selan 	case 2:		/* % test op arg */
11853852Selan 		opname = argv[1];
11953852Selan 		if (IS_BANG(opname))
12053852Selan 			return (*argv[2] == '\0') ? 1 : 0;
12153852Selan 		else {
12253852Selan 			ret_val = posix_unary_op(&argv[1]);
12353852Selan 			if (ret_val >= 0)
12453852Selan 				return ret_val;
12553852Selan 		}
12653852Selan 		break;
12753852Selan 	case 3:		/* % test arg1 op arg2 */
12853852Selan 		if (IS_BANG(argv[1])) {
12953852Selan 			ret_val = posix_unary_op(&argv[1]);
13053852Selan 			if (ret_val >= 0)
13153852Selan 				return !ret_val;
13253852Selan 		} else {
13353852Selan 			ret_val = posix_binary_op(&argv[1]);
13453852Selan 			if (ret_val >= 0)
13553852Selan 				return ret_val;
13653852Selan 		}
13753852Selan 		break;
13853852Selan 	case 4:		/* % test ! arg1 op arg2 */
13953852Selan 		if (IS_BANG(argv[1])) {
14053852Selan 			ret_val = posix_binary_op(&argv[2]);
14153852Selan 			if (ret_val >= 0)
14253852Selan 				return !ret_val;
14353852Selan 		}
14453852Selan 		break;
14553852Selan 	default:
14653852Selan 		break;
14753852Selan 	}
14853835Selan 
14953852Selan 	/* We use operator precedence parsing, evaluating the expression as
15053852Selan 	 * we parse it.  Parentheses are handled by bumping up the priority
15153852Selan 	 * of operators using the variable "nest."  We use the variable
15253852Selan 	 * "skipping" to turn off evaluation temporarily for the short
15353852Selan 	 * circuit boolean operators.  (It is important do the short circuit
15453852Selan 	 * evaluation because under NFS a stat operation can take infinitely
15553852Selan 	 * long.) */
15653835Selan 
15753852Selan 	opsp = opstack + STACKSIZE;
15853852Selan 	valsp = valstack;
15953852Selan 	nest = 0;
16053852Selan 	skipping = 0;
16153852Selan 	if (*ap == NULL) {
16253852Selan 		valstack[0].type = BOOLEAN;
16353852Selan 		valstack[0].u.num = 0;
16453852Selan 		goto done;
16553852Selan 	}
16653852Selan 	for (;;) {
16753852Selan 		opname = *ap++;
16853852Selan 		if (opname == NULL)
16953852Selan 			goto syntax;
17053852Selan 		if (opname[0] == '(' && opname[1] == '\0') {
17153852Selan 			nest += NESTINCR;
17253852Selan 			continue;
17353852Selan 		} else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
17453852Selan 			if (opsp == &opstack[0])
17553852Selan 				goto overflow;
17653852Selan 			--opsp;
17753852Selan 			opsp->op = op;
17853852Selan 			opsp->pri = op_priority[op] + nest;
17953852Selan 			continue;
18053835Selan 
18153852Selan 		} else {
18253852Selan 			valsp->type = STRING;
18353852Selan 			valsp->u.string = opname;
18453852Selan 			valsp++;
18553852Selan 		}
18653852Selan 		for (;;) {
18753852Selan 			opname = *ap++;
18853852Selan 			if (opname == NULL) {
18953852Selan 				if (nest != 0)
19053852Selan 					goto syntax;
19153852Selan 				pri = 0;
19253852Selan 				break;
19353852Selan 			}
19453852Selan 			if (opname[0] != ')' || opname[1] != '\0') {
19553852Selan 				if ((op = lookup_op(opname, binary_op)) < 0)
19653852Selan 					goto syntax;
19753852Selan 				op += FIRST_BINARY_OP;
19853852Selan 				pri = op_priority[op] + nest;
19953852Selan 				break;
20053852Selan 			}
20153852Selan 			if ((nest -= NESTINCR) < 0)
20253852Selan 				goto syntax;
20353852Selan 		}
20453852Selan 		while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) {
20553852Selan 			binary = opsp->op;
20653852Selan 			for (;;) {
20753852Selan 				valsp--;
20853852Selan 				c = op_argflag[opsp->op];
20953852Selan 				if (c == OP_INT) {
21053852Selan 					if (valsp->type == STRING &&
21153852Selan 					    int_tcheck(valsp->u.string))
21253852Selan 						valsp->u.num =
21353852Selan 						    atol(valsp->u.string);
21453852Selan 					valsp->type = INTEGER;
21553852Selan 				} else if (c >= OP_STRING) {
21653852Selan 					            /* OP_STRING or OP_FILE */
21753852Selan 					if (valsp->type == INTEGER) {
21853852Selan 						p = stalloc(32);
21953835Selan #ifdef SHELL
22053852Selan 						fmtstr(p, 32, "%d",
22153852Selan 						    valsp->u.num);
22253835Selan #else
22353852Selan 						sprintf(p, "%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;
23453852Selan 					if (c == OP_FILE
23553852Selan 					    && (fs.name == NULL
23653852Selan 						|| !equal(fs.name,
23753852Selan                                                     valsp->u.string))) {
23853852Selan 						fs.name = valsp->u.string;
23953852Selan 						fs.rcode =
24053852Selan 						    stat(valsp->u.string,
24153852Selan                                                     &fs.stat);
24253852Selan 					}
24353852Selan 				}
24453852Selan 				if (binary < FIRST_BINARY_OP)
24553852Selan 					break;
24653852Selan 				binary = 0;
24753835Selan 			}
24853852Selan 			if (!skipping)
24953852Selan 				expr_operator(opsp->op, valsp, &fs);
25053852Selan 			else if (opsp->op == AND1 || opsp->op == OR1)
25153852Selan 				skipping--;
25253852Selan 			valsp++;/* push value */
25353852Selan 			opsp++;	/* pop operator */
25453852Selan 		}
25553852Selan 		if (opname == NULL)
25653852Selan 			break;
25753852Selan 		if (opsp == &opstack[0])
25853852Selan 			goto overflow;
25953852Selan 		if (op == AND1 || op == AND2) {
26053852Selan 			op = AND1;
26153852Selan 			if (skipping || expr_is_false(valsp - 1))
26253852Selan 				skipping++;
26353852Selan 		}
26453852Selan 		if (op == OR1 || op == OR2) {
26553852Selan 			op = OR1;
26653852Selan 			if (skipping || !expr_is_false(valsp - 1))
26753852Selan 				skipping++;
26853852Selan 		}
26953852Selan 		opsp--;
27053852Selan 		opsp->op = op;
27153852Selan 		opsp->pri = pri;
27253852Selan 	}
27353835Selan done:
27453852Selan 	return expr_is_false(&valstack[0]);
27553835Selan 
27653852Selan syntax:   error("syntax error");
27753852Selan overflow: error("Expression too complex");
27853835Selan 
27953835Selan }
28053835Selan 
28153835Selan 
28253835Selan static int
28353835Selan expr_is_false(val)
28453852Selan 	struct value *val;
28553835Selan {
28653852Selan 	if (val->type == STRING) {
28753852Selan 		if (val->u.string[0] == '\0')
28853852Selan 			return 1;
28953852Selan 	} else {		/* INTEGER or BOOLEAN */
29053852Selan 		if (val->u.num == 0)
29153852Selan 			return 1;
29253852Selan 	}
29353852Selan 	return 0;
29453835Selan }
29553835Selan 
29653835Selan 
29753835Selan /*
29853835Selan  * Execute an operator.  Op is the operator.  Sp is the stack pointer;
29953835Selan  * sp[0] refers to the first operand, sp[1] refers to the second operand
30053835Selan  * (if any), and the result is placed in sp[0].  The operands are converted
30153835Selan  * to the type expected by the operator before expr_operator is called.
30253835Selan  * Fs is a pointer to a structure which holds the value of the last call
30353835Selan  * to stat, to avoid repeated stat calls on the same file.
30453835Selan  */
30553835Selan 
30653835Selan static void
30753835Selan expr_operator(op, sp, fs)
30853852Selan 	int     op;
30953835Selan 	struct value *sp;
31053835Selan 	struct filestat *fs;
31153835Selan {
31253852Selan 	int     i;
31353835Selan 
31453835Selan 	switch (op) {
31553835Selan 	case NOT:
31653852Selan 		sp->u.num = expr_is_false(sp);
31753852Selan 		sp->type = BOOLEAN;
31853852Selan 		break;
31953852Selan 	case ISEXIST:
32053852Selan 		if (fs == NULL || fs->rcode == -1)
32153852Selan 			goto false;
32253852Selan 		else
32353852Selan 			goto true;
32453852Selan 	case ISREAD:
32553852Selan 		i = 04;
32653852Selan 		goto permission;
32753852Selan 	case ISWRITE:
32853852Selan 		i = 02;
32953852Selan 		goto permission;
33053852Selan 	case ISEXEC:
33153852Selan 		i = 01;
33253835Selan permission:
33353852Selan 		if (fs->stat.st_uid == geteuid())
33453852Selan 			i <<= 6;
33553852Selan 		else if (fs->stat.st_gid == getegid())
33653852Selan 			i <<= 3;
33753852Selan 		goto filebit;	/* true if (stat.st_mode & i) != 0 */
33853852Selan 	case ISFILE:
33953852Selan 		i = S_IFREG;
34053852Selan 		goto filetype;
34153852Selan 	case ISDIR:
34253852Selan 		i = S_IFDIR;
34353852Selan 		goto filetype;
34453852Selan 	case ISCHAR:
34553852Selan 		i = S_IFCHR;
34653852Selan 		goto filetype;
34753852Selan 	case ISBLOCK:
34853852Selan 		i = S_IFBLK;
34953852Selan 		goto filetype;
35053852Selan 	case ISFIFO:
35153835Selan #ifdef S_IFIFO
35253852Selan 		i = S_IFIFO;
35353852Selan 		goto filetype;
35453835Selan #else
35553852Selan 		goto false;
35653835Selan #endif
35753835Selan filetype:
35853852Selan 		if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) {
35953852Selan 	true:
36053852Selan 			sp->u.num = 1;
36153852Selan 		} else {
36253852Selan 	false:
36353852Selan 			sp->u.num = 0;
36453852Selan 		}
36553852Selan 		sp->type = BOOLEAN;
36653852Selan 		break;
36753852Selan 	case ISSETUID:
36853852Selan 		i = S_ISUID;
36953852Selan 		goto filebit;
37053852Selan 	case ISSETGID:
37153852Selan 		i = S_ISGID;
37253852Selan 		goto filebit;
37353852Selan 	case ISSTICKY:
37453852Selan 		i = S_ISVTX;
37553835Selan filebit:
37653852Selan 		if (fs->stat.st_mode & i && fs->rcode >= 0)
37753852Selan 			goto true;
37853852Selan 		goto false;
37953852Selan 	case ISSIZE:
38053852Selan 		sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L;
38153852Selan 		sp->type = INTEGER;
38253852Selan 		break;
38353852Selan 	case ISTTY:
38453852Selan 		sp->u.num = isatty(sp->u.num);
38553852Selan 		sp->type = BOOLEAN;
38653852Selan 		break;
38753852Selan 	case NULSTR:
38853852Selan 		if (sp->u.string[0] == '\0')
38953852Selan 			goto true;
39053852Selan 		goto false;
39153852Selan 	case STRLEN:
39253852Selan 		sp->u.num = strlen(sp->u.string);
39353852Selan 		sp->type = INTEGER;
39453852Selan 		break;
39553852Selan 	case OR1:
39653852Selan 	case AND1:
39753852Selan 		/* These operators are mostly handled by the parser.  If we
39853852Selan 		 * get here it means that both operands were evaluated, so
39953852Selan 		 * the value is the value of the second operand. */
40053852Selan 		*sp = *(sp + 1);
40153852Selan 		break;
40253852Selan 	case STREQ:
40353852Selan 	case STRNE:
40453852Selan 		i = 0;
40553852Selan 		if (equal(sp->u.string, (sp + 1)->u.string))
40653852Selan 			i++;
40753852Selan 		if (op == STRNE)
40853852Selan 			i = 1 - i;
40953852Selan 		sp->u.num = i;
41053852Selan 		sp->type = BOOLEAN;
41153852Selan 		break;
41253852Selan 	case EQ:
41353852Selan 		if (sp->u.num == (sp + 1)->u.num)
41453852Selan 			goto true;
41553852Selan 		goto false;
41653852Selan 	case NE:
41753852Selan 		if (sp->u.num != (sp + 1)->u.num)
41853852Selan 			goto true;
41953852Selan 		goto false;
42053852Selan 	case GT:
42153852Selan 		if (sp->u.num > (sp + 1)->u.num)
42253852Selan 			goto true;
42353852Selan 		goto false;
42453852Selan 	case LT:
42553852Selan 		if (sp->u.num < (sp + 1)->u.num)
42653852Selan 			goto true;
42753852Selan 		goto false;
42853852Selan 	case LE:
42953852Selan 		if (sp->u.num <= (sp + 1)->u.num)
43053852Selan 			goto true;
43153852Selan 		goto false;
43253852Selan 	case GE:
43353852Selan 		if (sp->u.num >= (sp + 1)->u.num)
43453852Selan 			goto true;
43553852Selan 		goto false;
43653835Selan 
43753852Selan 	}
43853835Selan }
43953835Selan 
44053835Selan 
44153835Selan static int
44253835Selan lookup_op(name, table)
44353852Selan 	char   *name;
44453852Selan 	char   *const * table;
44553835Selan {
44653852Selan 	register char *const * tp;
44753852Selan 	register char const *p;
44853852Selan 	char    c = name[1];
44953835Selan 
45053852Selan 	for (tp = table; (p = *tp) != NULL; tp++) {
45153852Selan 		if (p[1] == c && equal(p, name))
45253852Selan 			return tp - table;
45353852Selan 	}
45453852Selan 	return -1;
45553835Selan }
45653835Selan 
45753835Selan static int
45853835Selan posix_unary_op(argv)
45953852Selan 	char  **argv;
46053835Selan {
46153852Selan 	int     op, c;
46253852Selan 	char   *opname;
46353835Selan 	struct filestat fs;
46453835Selan 	struct value valp;
46553835Selan 
46653835Selan 	opname = *argv;
46753852Selan 	if ((op = lookup_op(opname, unary_op)) < 0)
46853835Selan 		return -1;
46953852Selan 	c = op_argflag[op];
47053852Selan 	opname = argv[1];
47153852Selan 	valp.u.string = opname;
47253852Selan 	if (c == OP_FILE) {
47353835Selan 		fs.name = opname;
47453835Selan 		fs.rcode = stat(opname, &fs.stat);
47553835Selan 	} else if (c != OP_STRING)
47653835Selan 		return -1;
47753835Selan 
47853835Selan 	expr_operator(op, &valp, &fs);
47953835Selan 	return (valp.u.num == 0);
48053835Selan }
48153835Selan 
48253835Selan 
48353835Selan static int
48453835Selan posix_binary_op(argv)
48553852Selan 	char  **argv;
48653835Selan {
48753852Selan 	int     op, c;
48853852Selan 	char   *opname;
48953835Selan 	struct value v[2];
49053852Selan 
49153835Selan 	opname = argv[1];
49253835Selan 	if ((op = lookup_op(opname, binary_op)) < 0)
49353835Selan 		return -1;
49453835Selan 	op += FIRST_BINARY_OP;
49553835Selan 	c = op_argflag[op];
49653835Selan 
49753835Selan 	if (c == OP_INT && int_tcheck(argv[0]) && int_tcheck(argv[2])) {
49853835Selan 		v[0].u.num = atol(argv[0]);
49953835Selan 		v[1].u.num = atol(argv[2]);
500*53918Selan 	} else {
501*53918Selan 		v[0].u.string = argv[0];
502*53918Selan 		v[1].u.string = argv[2];
50353835Selan 	}
50453835Selan 	expr_operator(op, v, NULL);
50553835Selan 	return (v[0].u.num == 0);
50653835Selan }
50753835Selan 
50853835Selan /*
50953835Selan  * Integer type checking.
51053835Selan  */
51153835Selan static int
51253835Selan int_tcheck(v)
51353852Selan 	char   *v;
51453835Selan {
51553852Selan 	char   *p;
51653852Selan 	char    outbuf[512];
51753835Selan 
51853835Selan 	for (p = v; *p != '\0'; p++)
51953835Selan 		if (*p < '0' || *p > '9') {
52053852Selan 			snprintf(outbuf, sizeof(outbuf),
52153852Selan 			  "Illegal operand \"%s\" -- expected integer.", v);
52253852Selan 			error(outbuf);
52353835Selan 		}
52453835Selan 	return 1;
52553835Selan }
526