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