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