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