xref: /csrg-svn/bin/test/test.c (revision 53852)
153835Selan /*
2*53852Selan  * Copyright (c) 1988 The Regents of the University of California.
3*53852Selan  * All rights reserved.
453835Selan  *
5*53852Selan  * This code is derived from software contributed to Berkeley by
6*53852Selan  * Kenneth Almquist.
7*53852Selan  *
8*53852Selan  * %sccs.include.redist.c%
953835Selan  */
1053835Selan 
11*53852Selan #ifndef lint
12*53852Selan char    copyright[] =
13*53852Selan "@(#) Copyright (c) 1988 The Regents of the University of California.\n\
14*53852Selan  All rights reserved.\n";
15*53852Selan #endif				/* not lint */
1653835Selan 
17*53852Selan #ifndef lint
18*53852Selan static char sccsid[] = "@(#)test.c	1.2 (Berkeley) 06/03/92";
19*53852Selan #endif				/* not lint */
2053835Selan 
2153835Selan #include <stdio.h>
2253835Selan #include <errno.h>
2353835Selan #include <sys/types.h>
2453835Selan #include <sys/stat.h>
2553835Selan #include "operators.h"
2653835Selan #include "extern.h"
2753835Selan 
2853835Selan #define STACKSIZE 12
2953835Selan #define NESTINCR 16
3053835Selan 
3153835Selan /* data types */
3253835Selan #define STRING 0
3353835Selan #define INTEGER 1
3453835Selan #define BOOLEAN 2
3553835Selan 
3653835Selan #define INITARGS(argv)	if (argv[0] == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else
3753835Selan 
3853835Selan #define IS_BANG(s) (s[0] == '!' && s[1] == '\0')
3953835Selan #define equal(s1, s2)   (strcmp(s1, s2) == 0)
4053835Selan 
4153835Selan /*
4253835Selan  * This structure hold a value.  The type keyword specifies the type of
4353835Selan  * the value, and the union u holds the value.  The value of a boolean
4453835Selan  * is stored in u.num (1 = TRUE, 0 = FALSE).
4553835Selan  */
4653835Selan 
4753835Selan struct value {
48*53852Selan 	int     type;
49*53852Selan 	union {
50*53852Selan 		char   *string;
51*53852Selan 		long    num;
52*53852Selan 	}       u;
5353835Selan };
5453835Selan 
5553835Selan struct operator {
56*53852Selan 	short   op;		/* which operator */
57*53852Selan 	short   pri;		/* priority of operator */
5853835Selan };
5953835Selan 
6053835Selan struct filestat {
61*53852Selan 	char   *name;		/* name of file */
62*53852Selan 	int     rcode;		/* return code from stat */
63*53852Selan 	struct stat stat;	/* status info on file */
6453835Selan };
6553835Selan 
6653835Selan static int expr_is_false __P((struct value *));
6753835Selan static void expr_operator __P((int, struct value *, struct filestat *));
68*53852Selan static int lookup_op __P((char *, char *const *));
6953835Selan static long atol __P((const char *));
7053835Selan static int posix_binary_op __P((char **));
7153835Selan static int posix_unary_op __P((char **));
7253835Selan static int int_tcheck __P((char *));
7353835Selan 
7453835Selan int
75*53852Selan main(argc, argv)
76*53852Selan 	int     argc;
77*53852Selan 	char  **argv;
7853835Selan {
79*53852Selan 	char  **ap;
80*53852Selan 	char   *opname;
81*53852Selan 	char    c;
82*53852Selan 	char   *p;
83*53852Selan 	int     nest;		/* parentheses nesting */
84*53852Selan 	int     op;
85*53852Selan 	int     pri;
86*53852Selan 	int     skipping;
87*53852Selan 	int     binary;
88*53852Selan 	struct operator opstack[STACKSIZE];
89*53852Selan 	struct operator *opsp;
90*53852Selan 	struct value valstack[STACKSIZE + 1];
91*53852Selan 	struct value *valsp;
92*53852Selan 	struct filestat fs;
93*53852Selan 	int     ret_val;
9453835Selan 
95*53852Selan 	INITARGS(argv);
96*53852Selan 	if (**argv == '[') {
97*53852Selan 		if (!equal(argv[argc - 1], "]"))
98*53852Selan 			error("missing ]");
99*53852Selan 		argv[argc - 1] = NULL;
100*53852Selan 	}
101*53852Selan 	ap = argv + 1;
102*53852Selan 	fs.name = NULL;
10353835Selan 
104*53852Selan 	/* Test(1) implements an inherently ambiguous grammer.  In order to
105*53852Selan 	 * assure some degree of consistency, we special case the POSIX
106*53852Selan 	 * requirements to assure correct evaluation for POSIX following
107*53852Selan 	 * scripts.  The following special cases comply with POSIX
108*53852Selan 	 * P1003.2/D11.2 Section 4.62.4. */
109*53852Selan 	switch (argc - 1) {
110*53852Selan 	case 0:		/* % test */
111*53852Selan 		return 1;
112*53852Selan 		break;
113*53852Selan 	case 1:		/* % test arg */
114*53852Selan 		return (*argv[1] == '\0') ? 1 : 0;
115*53852Selan 		break;
116*53852Selan 	case 2:		/* % test op arg */
117*53852Selan 		opname = argv[1];
118*53852Selan 		if (IS_BANG(opname))
119*53852Selan 			return (*argv[2] == '\0') ? 1 : 0;
120*53852Selan 		else {
121*53852Selan 			ret_val = posix_unary_op(&argv[1]);
122*53852Selan 			if (ret_val >= 0)
123*53852Selan 				return ret_val;
124*53852Selan 		}
125*53852Selan 		break;
126*53852Selan 	case 3:		/* % test arg1 op arg2 */
127*53852Selan 		if (IS_BANG(argv[1])) {
128*53852Selan 			ret_val = posix_unary_op(&argv[1]);
129*53852Selan 			if (ret_val >= 0)
130*53852Selan 				return !ret_val;
131*53852Selan 		} else {
132*53852Selan 			ret_val = posix_binary_op(&argv[1]);
133*53852Selan 			if (ret_val >= 0)
134*53852Selan 				return ret_val;
135*53852Selan 		}
136*53852Selan 		break;
137*53852Selan 	case 4:		/* % test ! arg1 op arg2 */
138*53852Selan 		if (IS_BANG(argv[1])) {
139*53852Selan 			ret_val = posix_binary_op(&argv[2]);
140*53852Selan 			if (ret_val >= 0)
141*53852Selan 				return !ret_val;
142*53852Selan 		}
143*53852Selan 		break;
144*53852Selan 	default:
145*53852Selan 		break;
146*53852Selan 	}
14753835Selan 
148*53852Selan 	/* We use operator precedence parsing, evaluating the expression as
149*53852Selan 	 * we parse it.  Parentheses are handled by bumping up the priority
150*53852Selan 	 * of operators using the variable "nest."  We use the variable
151*53852Selan 	 * "skipping" to turn off evaluation temporarily for the short
152*53852Selan 	 * circuit boolean operators.  (It is important do the short circuit
153*53852Selan 	 * evaluation because under NFS a stat operation can take infinitely
154*53852Selan 	 * long.) */
15553835Selan 
156*53852Selan 	opsp = opstack + STACKSIZE;
157*53852Selan 	valsp = valstack;
158*53852Selan 	nest = 0;
159*53852Selan 	skipping = 0;
160*53852Selan 	if (*ap == NULL) {
161*53852Selan 		valstack[0].type = BOOLEAN;
162*53852Selan 		valstack[0].u.num = 0;
163*53852Selan 		goto done;
164*53852Selan 	}
165*53852Selan 	for (;;) {
166*53852Selan 		opname = *ap++;
167*53852Selan 		if (opname == NULL)
168*53852Selan 			goto syntax;
169*53852Selan 		if (opname[0] == '(' && opname[1] == '\0') {
170*53852Selan 			nest += NESTINCR;
171*53852Selan 			continue;
172*53852Selan 		} else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
173*53852Selan 			if (opsp == &opstack[0])
174*53852Selan 				goto overflow;
175*53852Selan 			--opsp;
176*53852Selan 			opsp->op = op;
177*53852Selan 			opsp->pri = op_priority[op] + nest;
178*53852Selan 			continue;
17953835Selan 
180*53852Selan 		} else {
181*53852Selan 			valsp->type = STRING;
182*53852Selan 			valsp->u.string = opname;
183*53852Selan 			valsp++;
184*53852Selan 		}
185*53852Selan 		for (;;) {
186*53852Selan 			opname = *ap++;
187*53852Selan 			if (opname == NULL) {
188*53852Selan 				if (nest != 0)
189*53852Selan 					goto syntax;
190*53852Selan 				pri = 0;
191*53852Selan 				break;
192*53852Selan 			}
193*53852Selan 			if (opname[0] != ')' || opname[1] != '\0') {
194*53852Selan 				if ((op = lookup_op(opname, binary_op)) < 0)
195*53852Selan 					goto syntax;
196*53852Selan 				op += FIRST_BINARY_OP;
197*53852Selan 				pri = op_priority[op] + nest;
198*53852Selan 				break;
199*53852Selan 			}
200*53852Selan 			if ((nest -= NESTINCR) < 0)
201*53852Selan 				goto syntax;
202*53852Selan 		}
203*53852Selan 		while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) {
204*53852Selan 			binary = opsp->op;
205*53852Selan 			for (;;) {
206*53852Selan 				valsp--;
207*53852Selan 				c = op_argflag[opsp->op];
208*53852Selan 				if (c == OP_INT) {
209*53852Selan 					if (valsp->type == STRING &&
210*53852Selan 					    int_tcheck(valsp->u.string))
211*53852Selan 						valsp->u.num =
212*53852Selan 						    atol(valsp->u.string);
213*53852Selan 					valsp->type = INTEGER;
214*53852Selan 				} else if (c >= OP_STRING) {
215*53852Selan 					            /* OP_STRING or OP_FILE */
216*53852Selan 					if (valsp->type == INTEGER) {
217*53852Selan 						p = stalloc(32);
21853835Selan #ifdef SHELL
219*53852Selan 						fmtstr(p, 32, "%d",
220*53852Selan 						    valsp->u.num);
22153835Selan #else
222*53852Selan 						sprintf(p, "%d", valsp->u.num);
22353835Selan #endif
224*53852Selan 						valsp->u.string = p;
225*53852Selan 					} else if (valsp->type == BOOLEAN) {
226*53852Selan 						if (valsp->u.num)
227*53852Selan 							valsp->u.string =
228*53852Selan 						            "true";
229*53852Selan 						else
230*53852Selan 							valsp->u.string = "";
231*53852Selan 					}
232*53852Selan 					valsp->type = STRING;
233*53852Selan 					if (c == OP_FILE
234*53852Selan 					    && (fs.name == NULL
235*53852Selan 						|| !equal(fs.name,
236*53852Selan                                                     valsp->u.string))) {
237*53852Selan 						fs.name = valsp->u.string;
238*53852Selan 						fs.rcode =
239*53852Selan 						    stat(valsp->u.string,
240*53852Selan                                                     &fs.stat);
241*53852Selan 					}
242*53852Selan 				}
243*53852Selan 				if (binary < FIRST_BINARY_OP)
244*53852Selan 					break;
245*53852Selan 				binary = 0;
24653835Selan 			}
247*53852Selan 			if (!skipping)
248*53852Selan 				expr_operator(opsp->op, valsp, &fs);
249*53852Selan 			else if (opsp->op == AND1 || opsp->op == OR1)
250*53852Selan 				skipping--;
251*53852Selan 			valsp++;/* push value */
252*53852Selan 			opsp++;	/* pop operator */
253*53852Selan 		}
254*53852Selan 		if (opname == NULL)
255*53852Selan 			break;
256*53852Selan 		if (opsp == &opstack[0])
257*53852Selan 			goto overflow;
258*53852Selan 		if (op == AND1 || op == AND2) {
259*53852Selan 			op = AND1;
260*53852Selan 			if (skipping || expr_is_false(valsp - 1))
261*53852Selan 				skipping++;
262*53852Selan 		}
263*53852Selan 		if (op == OR1 || op == OR2) {
264*53852Selan 			op = OR1;
265*53852Selan 			if (skipping || !expr_is_false(valsp - 1))
266*53852Selan 				skipping++;
267*53852Selan 		}
268*53852Selan 		opsp--;
269*53852Selan 		opsp->op = op;
270*53852Selan 		opsp->pri = pri;
271*53852Selan 	}
27253835Selan done:
273*53852Selan 	return expr_is_false(&valstack[0]);
27453835Selan 
275*53852Selan syntax:   error("syntax error");
276*53852Selan overflow: error("Expression too complex");
27753835Selan 
27853835Selan }
27953835Selan 
28053835Selan 
28153835Selan static int
28253835Selan expr_is_false(val)
283*53852Selan 	struct value *val;
28453835Selan {
285*53852Selan 	if (val->type == STRING) {
286*53852Selan 		if (val->u.string[0] == '\0')
287*53852Selan 			return 1;
288*53852Selan 	} else {		/* INTEGER or BOOLEAN */
289*53852Selan 		if (val->u.num == 0)
290*53852Selan 			return 1;
291*53852Selan 	}
292*53852Selan 	return 0;
29353835Selan }
29453835Selan 
29553835Selan 
29653835Selan /*
29753835Selan  * Execute an operator.  Op is the operator.  Sp is the stack pointer;
29853835Selan  * sp[0] refers to the first operand, sp[1] refers to the second operand
29953835Selan  * (if any), and the result is placed in sp[0].  The operands are converted
30053835Selan  * to the type expected by the operator before expr_operator is called.
30153835Selan  * Fs is a pointer to a structure which holds the value of the last call
30253835Selan  * to stat, to avoid repeated stat calls on the same file.
30353835Selan  */
30453835Selan 
30553835Selan static void
30653835Selan expr_operator(op, sp, fs)
307*53852Selan 	int     op;
30853835Selan 	struct value *sp;
30953835Selan 	struct filestat *fs;
31053835Selan {
311*53852Selan 	int     i;
31253835Selan 
31353835Selan 	switch (op) {
31453835Selan 	case NOT:
315*53852Selan 		sp->u.num = expr_is_false(sp);
316*53852Selan 		sp->type = BOOLEAN;
317*53852Selan 		break;
318*53852Selan 	case ISEXIST:
319*53852Selan 		if (fs == NULL || fs->rcode == -1)
320*53852Selan 			goto false;
321*53852Selan 		else
322*53852Selan 			goto true;
323*53852Selan 	case ISREAD:
324*53852Selan 		i = 04;
325*53852Selan 		goto permission;
326*53852Selan 	case ISWRITE:
327*53852Selan 		i = 02;
328*53852Selan 		goto permission;
329*53852Selan 	case ISEXEC:
330*53852Selan 		i = 01;
33153835Selan permission:
332*53852Selan 		if (fs->stat.st_uid == geteuid())
333*53852Selan 			i <<= 6;
334*53852Selan 		else if (fs->stat.st_gid == getegid())
335*53852Selan 			i <<= 3;
336*53852Selan 		goto filebit;	/* true if (stat.st_mode & i) != 0 */
337*53852Selan 	case ISFILE:
338*53852Selan 		i = S_IFREG;
339*53852Selan 		goto filetype;
340*53852Selan 	case ISDIR:
341*53852Selan 		i = S_IFDIR;
342*53852Selan 		goto filetype;
343*53852Selan 	case ISCHAR:
344*53852Selan 		i = S_IFCHR;
345*53852Selan 		goto filetype;
346*53852Selan 	case ISBLOCK:
347*53852Selan 		i = S_IFBLK;
348*53852Selan 		goto filetype;
349*53852Selan 	case ISFIFO:
35053835Selan #ifdef S_IFIFO
351*53852Selan 		i = S_IFIFO;
352*53852Selan 		goto filetype;
35353835Selan #else
354*53852Selan 		goto false;
35553835Selan #endif
35653835Selan filetype:
357*53852Selan 		if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) {
358*53852Selan 	true:
359*53852Selan 			sp->u.num = 1;
360*53852Selan 		} else {
361*53852Selan 	false:
362*53852Selan 			sp->u.num = 0;
363*53852Selan 		}
364*53852Selan 		sp->type = BOOLEAN;
365*53852Selan 		break;
366*53852Selan 	case ISSETUID:
367*53852Selan 		i = S_ISUID;
368*53852Selan 		goto filebit;
369*53852Selan 	case ISSETGID:
370*53852Selan 		i = S_ISGID;
371*53852Selan 		goto filebit;
372*53852Selan 	case ISSTICKY:
373*53852Selan 		i = S_ISVTX;
37453835Selan filebit:
375*53852Selan 		if (fs->stat.st_mode & i && fs->rcode >= 0)
376*53852Selan 			goto true;
377*53852Selan 		goto false;
378*53852Selan 	case ISSIZE:
379*53852Selan 		sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L;
380*53852Selan 		sp->type = INTEGER;
381*53852Selan 		break;
382*53852Selan 	case ISTTY:
383*53852Selan 		sp->u.num = isatty(sp->u.num);
384*53852Selan 		sp->type = BOOLEAN;
385*53852Selan 		break;
386*53852Selan 	case NULSTR:
387*53852Selan 		if (sp->u.string[0] == '\0')
388*53852Selan 			goto true;
389*53852Selan 		goto false;
390*53852Selan 	case STRLEN:
391*53852Selan 		sp->u.num = strlen(sp->u.string);
392*53852Selan 		sp->type = INTEGER;
393*53852Selan 		break;
394*53852Selan 	case OR1:
395*53852Selan 	case AND1:
396*53852Selan 		/* These operators are mostly handled by the parser.  If we
397*53852Selan 		 * get here it means that both operands were evaluated, so
398*53852Selan 		 * the value is the value of the second operand. */
399*53852Selan 		*sp = *(sp + 1);
400*53852Selan 		break;
401*53852Selan 	case STREQ:
402*53852Selan 	case STRNE:
403*53852Selan 		i = 0;
404*53852Selan 		if (equal(sp->u.string, (sp + 1)->u.string))
405*53852Selan 			i++;
406*53852Selan 		if (op == STRNE)
407*53852Selan 			i = 1 - i;
408*53852Selan 		sp->u.num = i;
409*53852Selan 		sp->type = BOOLEAN;
410*53852Selan 		break;
411*53852Selan 	case EQ:
412*53852Selan 		if (sp->u.num == (sp + 1)->u.num)
413*53852Selan 			goto true;
414*53852Selan 		goto false;
415*53852Selan 	case NE:
416*53852Selan 		if (sp->u.num != (sp + 1)->u.num)
417*53852Selan 			goto true;
418*53852Selan 		goto false;
419*53852Selan 	case GT:
420*53852Selan 		if (sp->u.num > (sp + 1)->u.num)
421*53852Selan 			goto true;
422*53852Selan 		goto false;
423*53852Selan 	case LT:
424*53852Selan 		if (sp->u.num < (sp + 1)->u.num)
425*53852Selan 			goto true;
426*53852Selan 		goto false;
427*53852Selan 	case LE:
428*53852Selan 		if (sp->u.num <= (sp + 1)->u.num)
429*53852Selan 			goto true;
430*53852Selan 		goto false;
431*53852Selan 	case GE:
432*53852Selan 		if (sp->u.num >= (sp + 1)->u.num)
433*53852Selan 			goto true;
434*53852Selan 		goto false;
43553835Selan 
436*53852Selan 	}
43753835Selan }
43853835Selan 
43953835Selan 
44053835Selan static int
44153835Selan lookup_op(name, table)
442*53852Selan 	char   *name;
443*53852Selan 	char   *const * table;
44453835Selan {
445*53852Selan 	register char *const * tp;
446*53852Selan 	register char const *p;
447*53852Selan 	char    c = name[1];
44853835Selan 
449*53852Selan 	for (tp = table; (p = *tp) != NULL; tp++) {
450*53852Selan 		if (p[1] == c && equal(p, name))
451*53852Selan 			return tp - table;
452*53852Selan 	}
453*53852Selan 	return -1;
45453835Selan }
45553835Selan 
45653835Selan static int
45753835Selan posix_unary_op(argv)
458*53852Selan 	char  **argv;
45953835Selan {
460*53852Selan 	int     op, c;
461*53852Selan 	char   *opname;
46253835Selan 	struct filestat fs;
46353835Selan 	struct value valp;
46453835Selan 
46553835Selan 	opname = *argv;
466*53852Selan 	if ((op = lookup_op(opname, unary_op)) < 0)
46753835Selan 		return -1;
468*53852Selan 	c = op_argflag[op];
469*53852Selan 	opname = argv[1];
470*53852Selan 	valp.u.string = opname;
471*53852Selan 	if (c == OP_FILE) {
47253835Selan 		fs.name = opname;
47353835Selan 		fs.rcode = stat(opname, &fs.stat);
47453835Selan 	} else if (c != OP_STRING)
47553835Selan 		return -1;
47653835Selan 
47753835Selan 	expr_operator(op, &valp, &fs);
47853835Selan 	return (valp.u.num == 0);
47953835Selan }
48053835Selan 
48153835Selan 
48253835Selan static int
48353835Selan posix_binary_op(argv)
484*53852Selan 	char  **argv;
48553835Selan {
486*53852Selan 	int     op, c;
487*53852Selan 	char   *opname;
48853835Selan 	struct value v[2];
489*53852Selan 
49053835Selan 	opname = argv[1];
49153835Selan 	if ((op = lookup_op(opname, binary_op)) < 0)
49253835Selan 		return -1;
49353835Selan 	op += FIRST_BINARY_OP;
49453835Selan 	c = op_argflag[op];
49553835Selan 
49653835Selan 	if (c == OP_INT && int_tcheck(argv[0]) && int_tcheck(argv[2])) {
49753835Selan 		v[0].u.num = atol(argv[0]);
49853835Selan 		v[1].u.num = atol(argv[2]);
49953835Selan 	}
50053835Selan 	expr_operator(op, v, NULL);
50153835Selan 	return (v[0].u.num == 0);
50253835Selan }
50353835Selan 
50453835Selan /*
50553835Selan  * Integer type checking.
50653835Selan  */
50753835Selan static int
50853835Selan int_tcheck(v)
509*53852Selan 	char   *v;
51053835Selan {
511*53852Selan 	char   *p;
512*53852Selan 	char    outbuf[512];
51353835Selan 
51453835Selan 	for (p = v; *p != '\0'; p++)
51553835Selan 		if (*p < '0' || *p > '9') {
516*53852Selan 			snprintf(outbuf, sizeof(outbuf),
517*53852Selan 			  "Illegal operand \"%s\" -- expected integer.", v);
518*53852Selan 			error(outbuf);
51953835Selan 		}
52053835Selan 	return 1;
52153835Selan }
522