xref: /freebsd/bin/test/test.c (revision 33b77e2decd50e53798014b70bf7ca3bdc4c0c7e)
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
40 static 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
46 static 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  */
78 struct value {
79 	int type;
80 	union {
81 		char *string;
82 		long num;
83 	} u;
84 };
85 
86 struct operator {
87 	short op;		/* Which operator. */
88 	short pri;		/* Priority of operator. */
89 };
90 
91 struct filestat {
92 	char *name;		/* Name of file. */
93 	int rcode;		/* Return code from stat. */
94 	struct stat stat;	/* Status info on file. */
95 };
96 
97 static int	expr_is_false __P((struct value *));
98 static void	expr_operator __P((int, struct value *, struct filestat *));
99 static void	get_int __P((char *, long *));
100 static int	lookup_op __P((char *, const char *const *));
101 static void	overflow __P((void));
102 static int	posix_binary_op __P((char **));
103 static int	posix_unary_op __P((char **));
104 static void	syntax __P((void));
105 
106 int
107 main(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 	}
298 done:	return (expr_is_false(&valstack[0]));
299 }
300 
301 static int
302 expr_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  */
325 static void
326 expr_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:
339 exist:
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;
360 permission:		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;
395 filetype:	if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0)
396 true:			sp->u.num = 1;
397 		else
398 false:			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;
409 filebit:	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 
475 static int
476 lookup_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 
491 static int
492 posix_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 
516 static int
517 posix_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  */
544 static void
545 get_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 
576 static void
577 syntax()
578 {
579 
580 	errx(2, "syntax error");
581 }
582 
583 static void
584 overflow()
585 {
586 
587 	errx(2, "expression is too complex");
588 }
589