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