test.c revision 31666
1/*-
2 * Copyright (c) 1992, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Kenneth Almquist.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 *
36 *	$Id: test.c,v 1.17 1997/02/22 14:06:25 peter Exp $
37 */
38
39#ifndef lint
40static char const copyright[] =
41"@(#) Copyright (c) 1992, 1993, 1994\n\
42	The Regents of the University of California.  All rights reserved.\n";
43#endif /* not lint */
44
45#ifndef lint
46static char const sccsid[] = "@(#)test.c	8.3 (Berkeley) 4/2/94";
47#endif /* not lint */
48
49#include <sys/param.h>
50#include <sys/stat.h>
51
52#include <ctype.h>
53#include <err.h>
54#include <errno.h>
55#include <limits.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59#include <unistd.h>
60
61#include "operators.h"
62
63#define	STACKSIZE	12
64#define	NESTINCR	16
65
66/* data types */
67#define	STRING	0
68#define	INTEGER	1
69#define	BOOLEAN	2
70
71#define	IS_BANG(s) (s[0] == '!' && s[1] == '\0')
72
73/*
74 * This structure hold a value.  The type keyword specifies the type of
75 * the value, and the union u holds the value.  The value of a boolean
76 * is stored in u.num (1 = TRUE, 0 = FALSE).
77 */
78struct value {
79	int type;
80	union {
81		char *string;
82		long num;
83	} u;
84};
85
86struct operator {
87	short op;		/* Which operator. */
88	short pri;		/* Priority of operator. */
89};
90
91struct filestat {
92	char *name;		/* Name of file. */
93	int rcode;		/* Return code from stat. */
94	struct stat stat;	/* Status info on file. */
95};
96
97static int	expr_is_false __P((struct value *));
98static void	expr_operator __P((int, struct value *, struct filestat *));
99static void	get_int __P((char *, long *));
100static int	lookup_op __P((char *, const char *const *));
101static void	overflow __P((void));
102static int	posix_binary_op __P((char **));
103static int	posix_unary_op __P((char **));
104static void	syntax __P((void));
105
106int
107main(argc, argv)
108	int argc;
109	char *argv[];
110{
111	struct operator opstack[STACKSIZE];
112	struct operator *opsp;
113	struct value valstack[STACKSIZE + 1];
114	struct value *valsp;
115	struct filestat fs;
116	char  c, **ap, *opname, *p;
117	int binary, nest, op = 0, pri, ret_val, skipping;
118
119	if ((p = argv[0]) == NULL)
120		errx(2, "test: argc is zero");
121
122	if (*p != '\0' && p[strlen(p) - 1] == '[') {
123		if (strcmp(argv[--argc], "]"))
124			errx(2, "missing ]");
125		argv[argc] = NULL;
126	}
127	ap = argv + 1;
128	fs.name = NULL;
129
130	/*
131	 * Test(1) implements an inherently ambiguous grammer.  In order to
132	 * assure some degree of consistency, we special case the POSIX 1003.2
133	 * requirements to assure correct evaluation for POSIX scripts.  The
134	 * following special cases comply with POSIX P1003.2/D11.2 Section
135	 * 4.62.4.
136	 */
137	switch(argc - 1) {
138	case 0:				/* % test */
139		return (1);
140		break;
141	case 1:				/* % test arg */
142		return (argv[1] == NULL || *argv[1] == '\0') ? 1 : 0;
143		break;
144	case 2:				/* % test op arg */
145		opname = argv[1];
146		if (IS_BANG(opname))
147			return (*argv[2] == '\0') ? 0 : 1;
148		else {
149			ret_val = posix_unary_op(&argv[1]);
150			if (ret_val >= 0)
151				return (ret_val);
152		}
153		break;
154	case 3:				/* % test arg1 op arg2 */
155		if (IS_BANG(argv[1])) {
156			ret_val = posix_unary_op(&argv[1]);
157			if (ret_val >= 0)
158				return (!ret_val);
159		} else if (lookup_op(argv[2], andor_op) < 0) {
160			ret_val = posix_binary_op(&argv[1]);
161			if (ret_val >= 0)
162				return (ret_val);
163		}
164		break;
165	case 4:				/* % test ! arg1 op arg2 */
166		if (IS_BANG(argv[1]) && lookup_op(argv[3], andor_op) < 0 ) {
167			ret_val = posix_binary_op(&argv[2]);
168			if (ret_val >= 0)
169				return (!ret_val);
170		}
171		break;
172	default:
173		break;
174	}
175
176	/*
177	 * We use operator precedence parsing, evaluating the expression as
178	 * we parse it.  Parentheses are handled by bumping up the priority
179	 * of operators using the variable "nest."  We use the variable
180	 * "skipping" to turn off evaluation temporarily for the short
181	 * circuit boolean operators.  (It is important do the short circuit
182	 * evaluation because under NFS a stat operation can take infinitely
183	 * long.)
184	 */
185	opsp = opstack + STACKSIZE;
186	valsp = valstack;
187	nest = skipping = 0;
188	if (*ap == NULL) {
189		valstack[0].type = BOOLEAN;
190		valstack[0].u.num = 0;
191		goto done;
192	}
193	for (;;) {
194		opname = *ap++;
195		if (opname == NULL)
196			syntax();
197		if (opname[0] == '(' && opname[1] == '\0') {
198			nest += NESTINCR;
199			continue;
200		} else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
201			if (opsp == &opstack[0])
202				overflow();
203			--opsp;
204			opsp->op = op;
205			opsp->pri = op_priority[op] + nest;
206			continue;
207		} else {
208			valsp->type = STRING;
209			valsp->u.string = opname;
210			valsp++;
211		}
212		for (;;) {
213			opname = *ap++;
214			if (opname == NULL) {
215				if (nest != 0)
216					syntax();
217				pri = 0;
218				break;
219			}
220			if (opname[0] != ')' || opname[1] != '\0') {
221				if ((op = lookup_op(opname, binary_op)) < 0)
222					syntax();
223				op += FIRST_BINARY_OP;
224				pri = op_priority[op] + nest;
225				break;
226			}
227			if ((nest -= NESTINCR) < 0)
228				syntax();
229		}
230		while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) {
231			binary = opsp->op;
232			for (;;) {
233				valsp--;
234				c = op_argflag[opsp->op];
235				if (c == OP_INT) {
236					if (valsp->type == STRING)
237						get_int(valsp->u.string,
238						    &valsp->u.num);
239					valsp->type = INTEGER;
240				} else if (c >= OP_STRING) {
241					            /* OP_STRING or OP_FILE */
242					if (valsp->type == INTEGER) {
243						if ((p = malloc(32)) == NULL)
244							err(2, NULL);
245#ifdef SHELL
246						fmtstr(p, 32, "%d",
247						    valsp->u.num);
248#else
249						(void)sprintf(p,
250						    "%ld", valsp->u.num);
251#endif
252						valsp->u.string = p;
253					} else if (valsp->type == BOOLEAN) {
254						if (valsp->u.num)
255							valsp->u.string =
256						            "true";
257						else
258							valsp->u.string = "";
259					}
260					valsp->type = STRING;
261					if (c == OP_FILE && (fs.name == NULL ||
262					    strcmp(fs.name, valsp->u.string))) {
263						fs.name = valsp->u.string;
264						fs.rcode =
265						    stat(valsp->u.string,
266                                                    &fs.stat);
267					}
268				}
269				if (binary < FIRST_BINARY_OP)
270					break;
271				binary = 0;
272			}
273			if (!skipping)
274				expr_operator(opsp->op, valsp, &fs);
275			else if (opsp->op == AND1 || opsp->op == OR1)
276				skipping--;
277			valsp++;		/* push value */
278			opsp++;			/* pop operator */
279		}
280		if (opname == NULL)
281			break;
282		if (opsp == &opstack[0])
283			overflow();
284		if (op == AND1 || op == AND2) {
285			op = AND1;
286			if (skipping || expr_is_false(valsp - 1))
287				skipping++;
288		}
289		if (op == OR1 || op == OR2) {
290			op = OR1;
291			if (skipping || !expr_is_false(valsp - 1))
292				skipping++;
293		}
294		opsp--;
295		opsp->op = op;
296		opsp->pri = pri;
297	}
298done:	return (expr_is_false(&valstack[0]));
299}
300
301static int
302expr_is_false(val)
303	struct value *val;
304{
305
306	if (val->type == STRING) {
307		if (val->u.string[0] == '\0')
308			return (1);
309	} else {		/* INTEGER or BOOLEAN */
310		if (val->u.num == 0)
311			return (1);
312	}
313	return (0);
314}
315
316
317/*
318 * Execute an operator.  Op is the operator.  Sp is the stack pointer;
319 * sp[0] refers to the first operand, sp[1] refers to the second operand
320 * (if any), and the result is placed in sp[0].  The operands are converted
321 * to the type expected by the operator before expr_operator is called.
322 * Fs is a pointer to a structure which holds the value of the last call
323 * to stat, to avoid repeated stat calls on the same file.
324 */
325static void
326expr_operator(op, sp, fs)
327	int op;
328	struct value *sp;
329	struct filestat *fs;
330{
331	int i;
332
333	switch (op) {
334	case NOT:
335		sp->u.num = expr_is_false(sp);
336		sp->type = BOOLEAN;
337		break;
338	case ISEXIST:
339exist:
340		if (fs == NULL || fs->rcode == -1)
341			goto false;
342		else
343			goto true;
344	case ISREAD:
345		if (geteuid() == 0)
346			goto exist;
347		i = S_IROTH;
348		goto permission;
349	case ISWRITE:
350		if (geteuid() != 0)
351			i = S_IWOTH;
352		else {
353			i = S_IWOTH|S_IWGRP|S_IWUSR;
354			goto filebit;
355		}
356		goto permission;
357	case ISEXEC:
358		if (geteuid() != 0) {
359			i = S_IXOTH;
360permission:		if (fs->stat.st_uid == geteuid())
361				i <<= 6;
362			else {
363				gid_t grlist[NGROUPS];
364				int ngroups, j;
365
366				ngroups = getgroups(NGROUPS, grlist);
367				for (j = 0; j < ngroups; j++)
368					if (fs->stat.st_gid == grlist[j]) {
369						i <<= 3;
370						goto filebit;
371					}
372			}
373		} else
374			i = S_IXOTH|S_IXGRP|S_IXUSR;
375		goto filebit;	/* true if (stat.st_mode & i) != 0 */
376	case ISFILE:
377		i = S_IFREG;
378		goto filetype;
379	case ISDIR:
380		i = S_IFDIR;
381		goto filetype;
382	case ISCHAR:
383		i = S_IFCHR;
384		goto filetype;
385	case ISBLOCK:
386		i = S_IFBLK;
387		goto filetype;
388	case ISSYMLINK:
389		i = S_IFLNK;
390		fs->rcode = lstat(sp->u.string, &fs->stat);
391		goto filetype;
392	case ISFIFO:
393		i = S_IFIFO;
394		goto filetype;
395filetype:	if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0)
396true:			sp->u.num = 1;
397		else
398false:			sp->u.num = 0;
399		sp->type = BOOLEAN;
400		break;
401	case ISSETUID:
402		i = S_ISUID;
403		goto filebit;
404	case ISSETGID:
405		i = S_ISGID;
406		goto filebit;
407	case ISSTICKY:
408		i = S_ISVTX;
409filebit:	if (fs->stat.st_mode & i && fs->rcode >= 0)
410			goto true;
411		goto false;
412	case ISSIZE:
413		sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L;
414		sp->type = INTEGER;
415		break;
416	case ISTTY:
417		sp->u.num = isatty(sp->u.num);
418		sp->type = BOOLEAN;
419		break;
420	case NULSTR:
421		if (sp->u.string[0] == '\0')
422			goto true;
423		goto false;
424	case STRLEN:
425		sp->u.num = strlen(sp->u.string);
426		sp->type = INTEGER;
427		break;
428	case OR1:
429	case AND1:
430		/*
431		 * These operators are mostly handled by the parser.  If we
432		 * get here it means that both operands were evaluated, so
433		 * the value is the value of the second operand.
434		 */
435		*sp = *(sp + 1);
436		break;
437	case STREQ:
438	case STRNE:
439		i = 0;
440		if (!strcmp(sp->u.string, (sp + 1)->u.string))
441			i++;
442		if (op == STRNE)
443			i = 1 - i;
444		sp->u.num = i;
445		sp->type = BOOLEAN;
446		break;
447	case EQ:
448		if (sp->u.num == (sp + 1)->u.num)
449			goto true;
450		goto false;
451	case NE:
452		if (sp->u.num != (sp + 1)->u.num)
453			goto true;
454		goto false;
455	case GT:
456		if (sp->u.num > (sp + 1)->u.num)
457			goto true;
458		goto false;
459	case LT:
460		if (sp->u.num < (sp + 1)->u.num)
461			goto true;
462		goto false;
463	case LE:
464		if (sp->u.num <= (sp + 1)->u.num)
465			goto true;
466		goto false;
467	case GE:
468		if (sp->u.num >= (sp + 1)->u.num)
469			goto true;
470		goto false;
471
472	}
473}
474
475static int
476lookup_op(name, table)
477	char *name;
478	const char *const * table;
479{
480	const char *const * tp;
481	const char *p;
482	char c;
483
484	c = name[1];
485	for (tp = table; (p = *tp) != NULL; tp++)
486		if (p[1] == c && !strcmp(p, name))
487			return (tp - table);
488	return (-1);
489}
490
491static int
492posix_unary_op(argv)
493	char **argv;
494{
495	struct filestat fs;
496	struct value valp;
497	int op, c;
498	char *opname;
499
500	opname = *argv;
501	if ((op = lookup_op(opname, unary_op)) < 0)
502		return (-1);
503	c = op_argflag[op];
504	opname = argv[1];
505	valp.u.string = opname;
506	if (c == OP_FILE) {
507		fs.name = opname;
508		fs.rcode = stat(opname, &fs.stat);
509	} else if (c != OP_STRING)
510		return (-1);
511
512	expr_operator(op, &valp, &fs);
513	return (valp.u.num == 0);
514}
515
516static int
517posix_binary_op(argv)
518	char  **argv;
519{
520	struct value v[2];
521	int op, c;
522	char *opname;
523
524	opname = argv[1];
525	if ((op = lookup_op(opname, binary_op)) < 0)
526		return (-1);
527	op += FIRST_BINARY_OP;
528	c = op_argflag[op];
529
530	if (c == OP_INT) {
531		get_int(argv[0], &v[0].u.num);
532		get_int(argv[2], &v[1].u.num);
533	} else {
534		v[0].u.string = argv[0];
535		v[1].u.string = argv[2];
536	}
537	expr_operator(op, v, NULL);
538	return (v[0].u.num == 0);
539}
540
541/*
542 * Integer type checking.
543 */
544static void
545get_int(v, lp)
546	char *v;
547	long *lp;
548{
549	long val;
550	char *ep;
551
552	for (; *v && isspace(*v); ++v);
553
554	if(!*v) {
555		*lp = 0;
556		return;
557	}
558
559	if (isdigit(*v) || ((*v == '-' || *v == '+') && isdigit(*(v+1)))) {
560		errno = 0;
561		val = strtol(v, &ep, 10);
562		if (*ep != '\0')
563			errx(2, "%s: trailing non-numeric characters", v);
564		if (errno == ERANGE) {
565			if (val == LONG_MIN)
566				errx(2, "%s: underflow", v);
567			if (val == LONG_MAX)
568				errx(2, "%s: overflow", v);
569		}
570		*lp = val;
571		return;
572	}
573	errx(2, "%s: expected integer", v);
574}
575
576static void
577syntax()
578{
579
580	errx(2, "syntax error");
581}
582
583static void
584overflow()
585{
586
587	errx(2, "expression is too complex");
588}
589