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