153958Sbostic /*-
2*66643Spendry * Copyright (c) 1992, 1993, 1994
360726Sbostic * The Regents of the University of California. All rights reserved.
453835Selan *
553852Selan * This code is derived from software contributed to Berkeley by
653852Selan * Kenneth Almquist.
753852Selan *
853852Selan * %sccs.include.redist.c%
953835Selan */
1053835Selan
1153852Selan #ifndef lint
1260726Sbostic static char copyright[] =
13*66643Spendry "@(#) Copyright (c) 1992, 1993, 1994\n\
1460726Sbostic The Regents of the University of California. All rights reserved.\n";
1553958Sbostic #endif /* not lint */
1653835Selan
1753852Selan #ifndef lint
18*66643Spendry static char sccsid[] = "@(#)test.c 8.3 (Berkeley) 04/02/94";
1953958Sbostic #endif /* not lint */
2053835Selan
2153835Selan #include <sys/types.h>
2253835Selan #include <sys/stat.h>
2359571Sbostic
2459576Sbostic #include <ctype.h>
2559571Sbostic #include <err.h>
2653958Sbostic #include <errno.h>
2759571Sbostic #include <limits.h>
2859571Sbostic #include <stdio.h>
2953958Sbostic #include <stdlib.h>
3053918Selan #include <string.h>
3159576Sbostic #include <unistd.h>
3253958Sbostic
3353835Selan #include "operators.h"
3453835Selan
3553958Sbostic #define STACKSIZE 12
3653958Sbostic #define NESTINCR 16
3753835Selan
3853835Selan /* data types */
3953958Sbostic #define STRING 0
4053958Sbostic #define INTEGER 1
4153958Sbostic #define BOOLEAN 2
4253835Selan
4353958Sbostic #define IS_BANG(s) (s[0] == '!' && s[1] == '\0')
4453835Selan
4553835Selan /*
4653835Selan * This structure hold a value. The type keyword specifies the type of
4753835Selan * the value, and the union u holds the value. The value of a boolean
4853835Selan * is stored in u.num (1 = TRUE, 0 = FALSE).
4953835Selan */
5053835Selan struct value {
5153958Sbostic int type;
5253852Selan union {
5353958Sbostic char *string;
5453958Sbostic long num;
5553958Sbostic } u;
5653835Selan };
5753835Selan
5853835Selan struct operator {
5953958Sbostic short op; /* Which operator. */
6053958Sbostic short pri; /* Priority of operator. */
6153835Selan };
6253835Selan
6353835Selan struct filestat {
6453958Sbostic char *name; /* Name of file. */
6553958Sbostic int rcode; /* Return code from stat. */
6653958Sbostic struct stat stat; /* Status info on file. */
6753835Selan };
6853835Selan
6953958Sbostic static int expr_is_false __P((struct value *));
7053958Sbostic static void expr_operator __P((int, struct value *, struct filestat *));
7159571Sbostic static void get_int __P((char *, long *));
7266565Spendry static int lookup_op __P((char *, const char *const *));
7353958Sbostic static void overflow __P((void));
7453958Sbostic static int posix_binary_op __P((char **));
7553958Sbostic static int posix_unary_op __P((char **));
7653958Sbostic static void syntax __P((void));
7753835Selan
7853835Selan int
main(argc,argv)7953852Selan main(argc, argv)
8053958Sbostic int argc;
8153958Sbostic char *argv[];
8253835Selan {
8353852Selan struct operator opstack[STACKSIZE];
8453852Selan struct operator *opsp;
8553852Selan struct value valstack[STACKSIZE + 1];
8653852Selan struct value *valsp;
8753852Selan struct filestat fs;
8853958Sbostic char c, **ap, *opname, *p;
8953958Sbostic int binary, nest, op, pri, ret_val, skipping;
9053835Selan
9159571Sbostic if ((p = argv[0]) == NULL)
9259571Sbostic errx(2, "test: argc is zero");
9353958Sbostic
9457920Storek if (*p != '\0' && p[strlen(p) - 1] == '[') {
9557920Storek if (strcmp(argv[--argc], "]"))
9659571Sbostic errx(2, "missing ]");
9757920Storek argv[argc] = NULL;
9853852Selan }
9953852Selan ap = argv + 1;
10053852Selan fs.name = NULL;
10153835Selan
10253958Sbostic /*
10353958Sbostic * Test(1) implements an inherently ambiguous grammer. In order to
10453958Sbostic * assure some degree of consistency, we special case the POSIX 1003.2
10553958Sbostic * requirements to assure correct evaluation for POSIX scripts. The
10653958Sbostic * following special cases comply with POSIX P1003.2/D11.2 Section
10753958Sbostic * 4.62.4.
10853958Sbostic */
10953958Sbostic switch(argc - 1) {
11053958Sbostic case 0: /* % test */
11153958Sbostic return (1);
11253852Selan break;
11353958Sbostic case 1: /* % test arg */
11459571Sbostic return (argv[1] == NULL || *argv[1] == '\0') ? 1 : 0;
11553852Selan break;
11653958Sbostic case 2: /* % test op arg */
11753852Selan opname = argv[1];
11853852Selan if (IS_BANG(opname))
11960589Sbostic return (*argv[2] == '\0') ? 0 : 1;
12053852Selan else {
12153852Selan ret_val = posix_unary_op(&argv[1]);
12253852Selan if (ret_val >= 0)
12353958Sbostic return (ret_val);
12453852Selan }
12553852Selan break;
12653958Sbostic case 3: /* % test arg1 op arg2 */
12753852Selan if (IS_BANG(argv[1])) {
12853852Selan ret_val = posix_unary_op(&argv[1]);
12953852Selan if (ret_val >= 0)
13053958Sbostic return (!ret_val);
13153852Selan } else {
13253852Selan ret_val = posix_binary_op(&argv[1]);
13353852Selan if (ret_val >= 0)
13453958Sbostic return (ret_val);
13553852Selan }
13653852Selan break;
13753958Sbostic case 4: /* % test ! arg1 op arg2 */
13853852Selan if (IS_BANG(argv[1])) {
13953852Selan ret_val = posix_binary_op(&argv[2]);
14053852Selan if (ret_val >= 0)
14153958Sbostic return (!ret_val);
14253852Selan }
14353852Selan break;
14453852Selan default:
14553852Selan break;
14653852Selan }
14753835Selan
14853958Sbostic /*
14953958Sbostic * We use operator precedence parsing, evaluating the expression as
15053852Selan * we parse it. Parentheses are handled by bumping up the priority
15153852Selan * of operators using the variable "nest." We use the variable
15253852Selan * "skipping" to turn off evaluation temporarily for the short
15353852Selan * circuit boolean operators. (It is important do the short circuit
15453852Selan * evaluation because under NFS a stat operation can take infinitely
15553958Sbostic * long.)
15653958Sbostic */
15753852Selan opsp = opstack + STACKSIZE;
15853852Selan valsp = valstack;
15953958Sbostic nest = skipping = 0;
16053852Selan if (*ap == NULL) {
16153852Selan valstack[0].type = BOOLEAN;
16253852Selan valstack[0].u.num = 0;
16353852Selan goto done;
16453852Selan }
16553852Selan for (;;) {
16653852Selan opname = *ap++;
16753852Selan if (opname == NULL)
16853958Sbostic syntax();
16953852Selan if (opname[0] == '(' && opname[1] == '\0') {
17053852Selan nest += NESTINCR;
17153852Selan continue;
17253852Selan } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
17353852Selan if (opsp == &opstack[0])
17453958Sbostic overflow();
17553852Selan --opsp;
17653852Selan opsp->op = op;
17753852Selan opsp->pri = op_priority[op] + nest;
17853852Selan continue;
17953852Selan } else {
18053852Selan valsp->type = STRING;
18153852Selan valsp->u.string = opname;
18253852Selan valsp++;
18353852Selan }
18453852Selan for (;;) {
18553852Selan opname = *ap++;
18653852Selan if (opname == NULL) {
18753852Selan if (nest != 0)
18853958Sbostic syntax();
18953852Selan pri = 0;
19053852Selan break;
19153852Selan }
19253852Selan if (opname[0] != ')' || opname[1] != '\0') {
19353852Selan if ((op = lookup_op(opname, binary_op)) < 0)
19453958Sbostic syntax();
19553852Selan op += FIRST_BINARY_OP;
19653852Selan pri = op_priority[op] + nest;
19753852Selan break;
19853852Selan }
19953852Selan if ((nest -= NESTINCR) < 0)
20053958Sbostic syntax();
20153852Selan }
20253852Selan while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) {
20353852Selan binary = opsp->op;
20453852Selan for (;;) {
20553852Selan valsp--;
20653852Selan c = op_argflag[opsp->op];
20753852Selan if (c == OP_INT) {
20859571Sbostic if (valsp->type == STRING)
20959571Sbostic get_int(valsp->u.string,
21059571Sbostic &valsp->u.num);
21153852Selan valsp->type = INTEGER;
21253852Selan } else if (c >= OP_STRING) {
21353852Selan /* OP_STRING or OP_FILE */
21453852Selan if (valsp->type == INTEGER) {
21553958Sbostic if ((p = malloc(32)) == NULL)
21659571Sbostic err(2, NULL);
21753835Selan #ifdef SHELL
21853852Selan fmtstr(p, 32, "%d",
21953852Selan valsp->u.num);
22053835Selan #else
22153958Sbostic (void)sprintf(p,
22253958Sbostic "%d", valsp->u.num);
22353835Selan #endif
22453852Selan valsp->u.string = p;
22553852Selan } else if (valsp->type == BOOLEAN) {
22653852Selan if (valsp->u.num)
22753852Selan valsp->u.string =
22853852Selan "true";
22953852Selan else
23053852Selan valsp->u.string = "";
23153852Selan }
23253852Selan valsp->type = STRING;
23353958Sbostic if (c == OP_FILE && (fs.name == NULL ||
23453958Sbostic strcmp(fs.name, valsp->u.string))) {
23553852Selan fs.name = valsp->u.string;
23653852Selan fs.rcode =
23753852Selan stat(valsp->u.string,
23853852Selan &fs.stat);
23953852Selan }
24053852Selan }
24153852Selan if (binary < FIRST_BINARY_OP)
24253852Selan break;
24353852Selan binary = 0;
24453835Selan }
24553852Selan if (!skipping)
24653852Selan expr_operator(opsp->op, valsp, &fs);
24753852Selan else if (opsp->op == AND1 || opsp->op == OR1)
24853852Selan skipping--;
24953958Sbostic valsp++; /* push value */
25053958Sbostic opsp++; /* pop operator */
25153852Selan }
25253852Selan if (opname == NULL)
25353852Selan break;
25453852Selan if (opsp == &opstack[0])
25553958Sbostic overflow();
25653852Selan if (op == AND1 || op == AND2) {
25753852Selan op = AND1;
25853852Selan if (skipping || expr_is_false(valsp - 1))
25953852Selan skipping++;
26053852Selan }
26153852Selan if (op == OR1 || op == OR2) {
26253852Selan op = OR1;
26353852Selan if (skipping || !expr_is_false(valsp - 1))
26453852Selan skipping++;
26553852Selan }
26653852Selan opsp--;
26753852Selan opsp->op = op;
26853852Selan opsp->pri = pri;
26953852Selan }
27053958Sbostic done: return (expr_is_false(&valstack[0]));
27153835Selan }
27253835Selan
27353835Selan static int
expr_is_false(val)27453835Selan expr_is_false(val)
27553852Selan struct value *val;
27653835Selan {
27766565Spendry
27853852Selan if (val->type == STRING) {
27953852Selan if (val->u.string[0] == '\0')
28053958Sbostic return (1);
28153852Selan } else { /* INTEGER or BOOLEAN */
28253852Selan if (val->u.num == 0)
28353958Sbostic return (1);
28453852Selan }
28553958Sbostic return (0);
28653835Selan }
28753835Selan
28853835Selan
28953835Selan /*
29053835Selan * Execute an operator. Op is the operator. Sp is the stack pointer;
29153835Selan * sp[0] refers to the first operand, sp[1] refers to the second operand
29253835Selan * (if any), and the result is placed in sp[0]. The operands are converted
29353835Selan * to the type expected by the operator before expr_operator is called.
29453835Selan * Fs is a pointer to a structure which holds the value of the last call
29553835Selan * to stat, to avoid repeated stat calls on the same file.
29653835Selan */
29753835Selan static void
expr_operator(op,sp,fs)29853835Selan expr_operator(op, sp, fs)
29953958Sbostic int op;
30053835Selan struct value *sp;
30153835Selan struct filestat *fs;
30253835Selan {
30353958Sbostic int i;
30453835Selan
30553835Selan switch (op) {
30653835Selan case NOT:
30753852Selan sp->u.num = expr_is_false(sp);
30853852Selan sp->type = BOOLEAN;
30953852Selan break;
31053852Selan case ISEXIST:
31153852Selan if (fs == NULL || fs->rcode == -1)
31253852Selan goto false;
31353852Selan else
31453852Selan goto true;
31553852Selan case ISREAD:
31653958Sbostic i = S_IROTH;
31753852Selan goto permission;
31853852Selan case ISWRITE:
31953958Sbostic i = S_IWOTH;
32053852Selan goto permission;
32153852Selan case ISEXEC:
32253958Sbostic i = S_IXOTH;
32353958Sbostic permission: if (fs->stat.st_uid == geteuid())
32453852Selan i <<= 6;
32553852Selan else if (fs->stat.st_gid == getegid())
32653852Selan i <<= 3;
32753852Selan goto filebit; /* true if (stat.st_mode & i) != 0 */
32853852Selan case ISFILE:
32953852Selan i = S_IFREG;
33053852Selan goto filetype;
33153852Selan case ISDIR:
33253852Selan i = S_IFDIR;
33353852Selan goto filetype;
33453852Selan case ISCHAR:
33553852Selan i = S_IFCHR;
33653852Selan goto filetype;
33753852Selan case ISBLOCK:
33853852Selan i = S_IFBLK;
33953852Selan goto filetype;
34060597Sbostic case ISSYMLINK:
34160597Sbostic i = S_IFLNK;
34260597Sbostic (void)lstat(sp->u.string, &fs->stat);
34360597Sbostic goto filetype;
34453852Selan case ISFIFO:
34553852Selan i = S_IFIFO;
34653852Selan goto filetype;
34753958Sbostic filetype: if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0)
34853958Sbostic true: sp->u.num = 1;
34953958Sbostic else
35053958Sbostic false: sp->u.num = 0;
35153852Selan sp->type = BOOLEAN;
35253852Selan break;
35353852Selan case ISSETUID:
35453852Selan i = S_ISUID;
35553852Selan goto filebit;
35653852Selan case ISSETGID:
35753852Selan i = S_ISGID;
35853852Selan goto filebit;
35953852Selan case ISSTICKY:
36053852Selan i = S_ISVTX;
36153958Sbostic filebit: if (fs->stat.st_mode & i && fs->rcode >= 0)
36253852Selan goto true;
36353852Selan goto false;
36453852Selan case ISSIZE:
36553852Selan sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L;
36653852Selan sp->type = INTEGER;
36753852Selan break;
36853852Selan case ISTTY:
36953852Selan sp->u.num = isatty(sp->u.num);
37053852Selan sp->type = BOOLEAN;
37153852Selan break;
37253852Selan case NULSTR:
37353852Selan if (sp->u.string[0] == '\0')
37453852Selan goto true;
37553852Selan goto false;
37653852Selan case STRLEN:
37753852Selan sp->u.num = strlen(sp->u.string);
37853852Selan sp->type = INTEGER;
37953852Selan break;
38053852Selan case OR1:
38153852Selan case AND1:
38253958Sbostic /*
38353958Sbostic * These operators are mostly handled by the parser. If we
38453852Selan * get here it means that both operands were evaluated, so
38553958Sbostic * the value is the value of the second operand.
38653958Sbostic */
38753852Selan *sp = *(sp + 1);
38853852Selan break;
38953852Selan case STREQ:
39053852Selan case STRNE:
39153852Selan i = 0;
39253958Sbostic if (!strcmp(sp->u.string, (sp + 1)->u.string))
39353852Selan i++;
39453852Selan if (op == STRNE)
39553852Selan i = 1 - i;
39653852Selan sp->u.num = i;
39753852Selan sp->type = BOOLEAN;
39853852Selan break;
39953852Selan case EQ:
40053852Selan if (sp->u.num == (sp + 1)->u.num)
40153852Selan goto true;
40253852Selan goto false;
40353852Selan case NE:
40453852Selan if (sp->u.num != (sp + 1)->u.num)
40553852Selan goto true;
40653852Selan goto false;
40753852Selan case GT:
40853852Selan if (sp->u.num > (sp + 1)->u.num)
40953852Selan goto true;
41053852Selan goto false;
41153852Selan case LT:
41253852Selan if (sp->u.num < (sp + 1)->u.num)
41353852Selan goto true;
41453852Selan goto false;
41553852Selan case LE:
41653852Selan if (sp->u.num <= (sp + 1)->u.num)
41753852Selan goto true;
41853852Selan goto false;
41953852Selan case GE:
42053852Selan if (sp->u.num >= (sp + 1)->u.num)
42153852Selan goto true;
42253852Selan goto false;
42353835Selan
42453852Selan }
42553835Selan }
42653835Selan
42753835Selan static int
lookup_op(name,table)42853835Selan lookup_op(name, table)
42966565Spendry char *name;
43066565Spendry const char *const * table;
43153835Selan {
43266565Spendry const char *const * tp;
43366565Spendry const char *p;
43453958Sbostic char c;
43553835Selan
43653958Sbostic c = name[1];
43753958Sbostic for (tp = table; (p = *tp) != NULL; tp++)
43853958Sbostic if (p[1] == c && !strcmp(p, name))
43953958Sbostic return (tp - table);
44053958Sbostic return (-1);
44153835Selan }
44253835Selan
44353835Selan static int
posix_unary_op(argv)44453835Selan posix_unary_op(argv)
44553958Sbostic char **argv;
44653835Selan {
44753835Selan struct filestat fs;
44853835Selan struct value valp;
44953958Sbostic int op, c;
45053958Sbostic char *opname;
45153835Selan
45253835Selan opname = *argv;
45353852Selan if ((op = lookup_op(opname, unary_op)) < 0)
45453958Sbostic return (-1);
45553852Selan c = op_argflag[op];
45653852Selan opname = argv[1];
45753852Selan valp.u.string = opname;
45853852Selan if (c == OP_FILE) {
45953835Selan fs.name = opname;
46053835Selan fs.rcode = stat(opname, &fs.stat);
46153835Selan } else if (c != OP_STRING)
46253958Sbostic return (-1);
46353835Selan
46453835Selan expr_operator(op, &valp, &fs);
46553835Selan return (valp.u.num == 0);
46653835Selan }
46753835Selan
46853835Selan static int
posix_binary_op(argv)46953835Selan posix_binary_op(argv)
47053852Selan char **argv;
47153835Selan {
47253835Selan struct value v[2];
47353958Sbostic int op, c;
47453958Sbostic char *opname;
47553852Selan
47653835Selan opname = argv[1];
47753835Selan if ((op = lookup_op(opname, binary_op)) < 0)
47853958Sbostic return (-1);
47953835Selan op += FIRST_BINARY_OP;
48053835Selan c = op_argflag[op];
48153835Selan
48259571Sbostic if (c == OP_INT) {
48359571Sbostic get_int(argv[0], &v[0].u.num);
48459571Sbostic get_int(argv[2], &v[1].u.num);
48553918Selan } else {
48653918Selan v[0].u.string = argv[0];
48753918Selan v[1].u.string = argv[2];
48853835Selan }
48953835Selan expr_operator(op, v, NULL);
49053835Selan return (v[0].u.num == 0);
49153835Selan }
49253835Selan
49353835Selan /*
49453835Selan * Integer type checking.
49553835Selan */
49659571Sbostic static void
get_int(v,lp)49759571Sbostic get_int(v, lp)
49853958Sbostic char *v;
49959571Sbostic long *lp;
50053835Selan {
50159571Sbostic long val;
50259571Sbostic char *ep;
50353835Selan
50459571Sbostic for (; *v && isspace(*v); ++v);
50559571Sbostic if (isdigit(*v)) {
50659571Sbostic errno = 0;
50759571Sbostic val = strtol(v, &ep, 10);
50859571Sbostic if (*ep != '\0')
50959571Sbostic errx(2, "%s: trailing non-numeric characters", v);
51059571Sbostic if (errno == ERANGE) {
51159571Sbostic if (val == LONG_MIN)
51259571Sbostic errx(2, "%s: underflow", v);
51359571Sbostic if (val == LONG_MAX)
51459571Sbostic errx(2, "%s: overflow", v);
51559571Sbostic }
51659571Sbostic *lp = val;
51759571Sbostic return;
51859571Sbostic }
51959571Sbostic errx(2, "%s: expected integer", v);
52053835Selan }
52153958Sbostic
52253958Sbostic static void
syntax()52353958Sbostic syntax()
52453958Sbostic {
52566565Spendry
52659571Sbostic err(2, "syntax error");
52753958Sbostic }
52853958Sbostic
52953958Sbostic static void
overflow()53053958Sbostic overflow()
53153958Sbostic {
53266565Spendry
53359571Sbostic err(2, "expression is too complex");
53453958Sbostic }
535