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