153835Selan /* 253852Selan * Copyright (c) 1988 The Regents of the University of California. 353852Selan * 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 12*53859Selan static char copyright[] = 1353852Selan "@(#) Copyright (c) 1988 The Regents of the University of California.\n\ 1453852Selan All rights reserved.\n"; 1553852Selan #endif /* not lint */ 1653835Selan 1753852Selan #ifndef lint 18*53859Selan static char sccsid[] = "@(#)test.c 1.3 (Berkeley) 06/03/92"; 1953852Selan #endif /* not lint */ 2053835Selan 2153835Selan #include <stdio.h> 2253835Selan #include <errno.h> 2353835Selan #include <sys/types.h> 2453835Selan #include <sys/stat.h> 2553835Selan #include "operators.h" 2653835Selan #include "extern.h" 2753835Selan 2853835Selan #define STACKSIZE 12 2953835Selan #define NESTINCR 16 3053835Selan 3153835Selan /* data types */ 3253835Selan #define STRING 0 3353835Selan #define INTEGER 1 3453835Selan #define BOOLEAN 2 3553835Selan 3653835Selan #define INITARGS(argv) if (argv[0] == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else 3753835Selan 3853835Selan #define IS_BANG(s) (s[0] == '!' && s[1] == '\0') 3953835Selan #define equal(s1, s2) (strcmp(s1, s2) == 0) 4053835Selan 4153835Selan /* 4253835Selan * This structure hold a value. The type keyword specifies the type of 4353835Selan * the value, and the union u holds the value. The value of a boolean 4453835Selan * is stored in u.num (1 = TRUE, 0 = FALSE). 4553835Selan */ 4653835Selan 4753835Selan struct value { 4853852Selan int type; 4953852Selan union { 5053852Selan char *string; 5153852Selan long num; 5253852Selan } u; 5353835Selan }; 5453835Selan 5553835Selan struct operator { 5653852Selan short op; /* which operator */ 5753852Selan short pri; /* priority of operator */ 5853835Selan }; 5953835Selan 6053835Selan struct filestat { 6153852Selan char *name; /* name of file */ 6253852Selan int rcode; /* return code from stat */ 6353852Selan struct stat stat; /* status info on file */ 6453835Selan }; 6553835Selan 6653835Selan static int expr_is_false __P((struct value *)); 6753835Selan static void expr_operator __P((int, struct value *, struct filestat *)); 6853852Selan static int lookup_op __P((char *, char *const *)); 6953835Selan static long atol __P((const char *)); 7053835Selan static int posix_binary_op __P((char **)); 7153835Selan static int posix_unary_op __P((char **)); 7253835Selan static int int_tcheck __P((char *)); 7353835Selan 7453835Selan int 7553852Selan main(argc, argv) 7653852Selan int argc; 7753852Selan char **argv; 7853835Selan { 7953852Selan char **ap; 8053852Selan char *opname; 8153852Selan char c; 8253852Selan char *p; 8353852Selan int nest; /* parentheses nesting */ 8453852Selan int op; 8553852Selan int pri; 8653852Selan int skipping; 8753852Selan int binary; 8853852Selan struct operator opstack[STACKSIZE]; 8953852Selan struct operator *opsp; 9053852Selan struct value valstack[STACKSIZE + 1]; 9153852Selan struct value *valsp; 9253852Selan struct filestat fs; 9353852Selan int ret_val; 9453835Selan 9553852Selan INITARGS(argv); 9653852Selan if (**argv == '[') { 9753852Selan if (!equal(argv[argc - 1], "]")) 9853852Selan error("missing ]"); 9953852Selan argv[argc - 1] = NULL; 10053852Selan } 10153852Selan ap = argv + 1; 10253852Selan fs.name = NULL; 10353835Selan 10453852Selan /* Test(1) implements an inherently ambiguous grammer. In order to 10553852Selan * assure some degree of consistency, we special case the POSIX 10653852Selan * requirements to assure correct evaluation for POSIX following 10753852Selan * scripts. The following special cases comply with POSIX 10853852Selan * P1003.2/D11.2 Section 4.62.4. */ 10953852Selan switch (argc - 1) { 11053852Selan case 0: /* % test */ 11153852Selan return 1; 11253852Selan break; 11353852Selan case 1: /* % test arg */ 11453852Selan return (*argv[1] == '\0') ? 1 : 0; 11553852Selan break; 11653852Selan case 2: /* % test op arg */ 11753852Selan opname = argv[1]; 11853852Selan if (IS_BANG(opname)) 11953852Selan return (*argv[2] == '\0') ? 1 : 0; 12053852Selan else { 12153852Selan ret_val = posix_unary_op(&argv[1]); 12253852Selan if (ret_val >= 0) 12353852Selan return ret_val; 12453852Selan } 12553852Selan break; 12653852Selan 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) 13053852Selan return !ret_val; 13153852Selan } else { 13253852Selan ret_val = posix_binary_op(&argv[1]); 13353852Selan if (ret_val >= 0) 13453852Selan return ret_val; 13553852Selan } 13653852Selan break; 13753852Selan 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) 14153852Selan return !ret_val; 14253852Selan } 14353852Selan break; 14453852Selan default: 14553852Selan break; 14653852Selan } 14753835Selan 14853852Selan /* We use operator precedence parsing, evaluating the expression as 14953852Selan * we parse it. Parentheses are handled by bumping up the priority 15053852Selan * of operators using the variable "nest." We use the variable 15153852Selan * "skipping" to turn off evaluation temporarily for the short 15253852Selan * circuit boolean operators. (It is important do the short circuit 15353852Selan * evaluation because under NFS a stat operation can take infinitely 15453852Selan * long.) */ 15553835Selan 15653852Selan opsp = opstack + STACKSIZE; 15753852Selan valsp = valstack; 15853852Selan nest = 0; 15953852Selan 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) 16853852Selan goto 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]) 17453852Selan goto overflow; 17553852Selan --opsp; 17653852Selan opsp->op = op; 17753852Selan opsp->pri = op_priority[op] + nest; 17853852Selan continue; 17953835Selan 18053852Selan } else { 18153852Selan valsp->type = STRING; 18253852Selan valsp->u.string = opname; 18353852Selan valsp++; 18453852Selan } 18553852Selan for (;;) { 18653852Selan opname = *ap++; 18753852Selan if (opname == NULL) { 18853852Selan if (nest != 0) 18953852Selan goto syntax; 19053852Selan pri = 0; 19153852Selan break; 19253852Selan } 19353852Selan if (opname[0] != ')' || opname[1] != '\0') { 19453852Selan if ((op = lookup_op(opname, binary_op)) < 0) 19553852Selan goto syntax; 19653852Selan op += FIRST_BINARY_OP; 19753852Selan pri = op_priority[op] + nest; 19853852Selan break; 19953852Selan } 20053852Selan if ((nest -= NESTINCR) < 0) 20153852Selan goto syntax; 20253852Selan } 20353852Selan while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) { 20453852Selan binary = opsp->op; 20553852Selan for (;;) { 20653852Selan valsp--; 20753852Selan c = op_argflag[opsp->op]; 20853852Selan if (c == OP_INT) { 20953852Selan if (valsp->type == STRING && 21053852Selan int_tcheck(valsp->u.string)) 21153852Selan valsp->u.num = 21253852Selan atol(valsp->u.string); 21353852Selan valsp->type = INTEGER; 21453852Selan } else if (c >= OP_STRING) { 21553852Selan /* OP_STRING or OP_FILE */ 21653852Selan if (valsp->type == INTEGER) { 21753852Selan p = stalloc(32); 21853835Selan #ifdef SHELL 21953852Selan fmtstr(p, 32, "%d", 22053852Selan valsp->u.num); 22153835Selan #else 22253852Selan sprintf(p, "%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; 23353852Selan if (c == OP_FILE 23453852Selan && (fs.name == NULL 23553852Selan || !equal(fs.name, 23653852Selan valsp->u.string))) { 23753852Selan fs.name = valsp->u.string; 23853852Selan fs.rcode = 23953852Selan stat(valsp->u.string, 24053852Selan &fs.stat); 24153852Selan } 24253852Selan } 24353852Selan if (binary < FIRST_BINARY_OP) 24453852Selan break; 24553852Selan binary = 0; 24653835Selan } 24753852Selan if (!skipping) 24853852Selan expr_operator(opsp->op, valsp, &fs); 24953852Selan else if (opsp->op == AND1 || opsp->op == OR1) 25053852Selan skipping--; 25153852Selan valsp++;/* push value */ 25253852Selan opsp++; /* pop operator */ 25353852Selan } 25453852Selan if (opname == NULL) 25553852Selan break; 25653852Selan if (opsp == &opstack[0]) 25753852Selan goto overflow; 25853852Selan if (op == AND1 || op == AND2) { 25953852Selan op = AND1; 26053852Selan if (skipping || expr_is_false(valsp - 1)) 26153852Selan skipping++; 26253852Selan } 26353852Selan if (op == OR1 || op == OR2) { 26453852Selan op = OR1; 26553852Selan if (skipping || !expr_is_false(valsp - 1)) 26653852Selan skipping++; 26753852Selan } 26853852Selan opsp--; 26953852Selan opsp->op = op; 27053852Selan opsp->pri = pri; 27153852Selan } 27253835Selan done: 27353852Selan return expr_is_false(&valstack[0]); 27453835Selan 27553852Selan syntax: error("syntax error"); 27653852Selan overflow: error("Expression too complex"); 27753835Selan 27853835Selan } 27953835Selan 28053835Selan 28153835Selan static int 28253835Selan expr_is_false(val) 28353852Selan struct value *val; 28453835Selan { 28553852Selan if (val->type == STRING) { 28653852Selan if (val->u.string[0] == '\0') 28753852Selan return 1; 28853852Selan } else { /* INTEGER or BOOLEAN */ 28953852Selan if (val->u.num == 0) 29053852Selan return 1; 29153852Selan } 29253852Selan return 0; 29353835Selan } 29453835Selan 29553835Selan 29653835Selan /* 29753835Selan * Execute an operator. Op is the operator. Sp is the stack pointer; 29853835Selan * sp[0] refers to the first operand, sp[1] refers to the second operand 29953835Selan * (if any), and the result is placed in sp[0]. The operands are converted 30053835Selan * to the type expected by the operator before expr_operator is called. 30153835Selan * Fs is a pointer to a structure which holds the value of the last call 30253835Selan * to stat, to avoid repeated stat calls on the same file. 30353835Selan */ 30453835Selan 30553835Selan static void 30653835Selan expr_operator(op, sp, fs) 30753852Selan int op; 30853835Selan struct value *sp; 30953835Selan struct filestat *fs; 31053835Selan { 31153852Selan int i; 31253835Selan 31353835Selan switch (op) { 31453835Selan case NOT: 31553852Selan sp->u.num = expr_is_false(sp); 31653852Selan sp->type = BOOLEAN; 31753852Selan break; 31853852Selan case ISEXIST: 31953852Selan if (fs == NULL || fs->rcode == -1) 32053852Selan goto false; 32153852Selan else 32253852Selan goto true; 32353852Selan case ISREAD: 32453852Selan i = 04; 32553852Selan goto permission; 32653852Selan case ISWRITE: 32753852Selan i = 02; 32853852Selan goto permission; 32953852Selan case ISEXEC: 33053852Selan i = 01; 33153835Selan permission: 33253852Selan if (fs->stat.st_uid == geteuid()) 33353852Selan i <<= 6; 33453852Selan else if (fs->stat.st_gid == getegid()) 33553852Selan i <<= 3; 33653852Selan goto filebit; /* true if (stat.st_mode & i) != 0 */ 33753852Selan case ISFILE: 33853852Selan i = S_IFREG; 33953852Selan goto filetype; 34053852Selan case ISDIR: 34153852Selan i = S_IFDIR; 34253852Selan goto filetype; 34353852Selan case ISCHAR: 34453852Selan i = S_IFCHR; 34553852Selan goto filetype; 34653852Selan case ISBLOCK: 34753852Selan i = S_IFBLK; 34853852Selan goto filetype; 34953852Selan case ISFIFO: 35053835Selan #ifdef S_IFIFO 35153852Selan i = S_IFIFO; 35253852Selan goto filetype; 35353835Selan #else 35453852Selan goto false; 35553835Selan #endif 35653835Selan filetype: 35753852Selan if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) { 35853852Selan true: 35953852Selan sp->u.num = 1; 36053852Selan } else { 36153852Selan false: 36253852Selan sp->u.num = 0; 36353852Selan } 36453852Selan sp->type = BOOLEAN; 36553852Selan break; 36653852Selan case ISSETUID: 36753852Selan i = S_ISUID; 36853852Selan goto filebit; 36953852Selan case ISSETGID: 37053852Selan i = S_ISGID; 37153852Selan goto filebit; 37253852Selan case ISSTICKY: 37353852Selan i = S_ISVTX; 37453835Selan filebit: 37553852Selan if (fs->stat.st_mode & i && fs->rcode >= 0) 37653852Selan goto true; 37753852Selan goto false; 37853852Selan case ISSIZE: 37953852Selan sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L; 38053852Selan sp->type = INTEGER; 38153852Selan break; 38253852Selan case ISTTY: 38353852Selan sp->u.num = isatty(sp->u.num); 38453852Selan sp->type = BOOLEAN; 38553852Selan break; 38653852Selan case NULSTR: 38753852Selan if (sp->u.string[0] == '\0') 38853852Selan goto true; 38953852Selan goto false; 39053852Selan case STRLEN: 39153852Selan sp->u.num = strlen(sp->u.string); 39253852Selan sp->type = INTEGER; 39353852Selan break; 39453852Selan case OR1: 39553852Selan case AND1: 39653852Selan /* These operators are mostly handled by the parser. If we 39753852Selan * get here it means that both operands were evaluated, so 39853852Selan * the value is the value of the second operand. */ 39953852Selan *sp = *(sp + 1); 40053852Selan break; 40153852Selan case STREQ: 40253852Selan case STRNE: 40353852Selan i = 0; 40453852Selan if (equal(sp->u.string, (sp + 1)->u.string)) 40553852Selan i++; 40653852Selan if (op == STRNE) 40753852Selan i = 1 - i; 40853852Selan sp->u.num = i; 40953852Selan sp->type = BOOLEAN; 41053852Selan break; 41153852Selan case EQ: 41253852Selan if (sp->u.num == (sp + 1)->u.num) 41353852Selan goto true; 41453852Selan goto false; 41553852Selan case NE: 41653852Selan if (sp->u.num != (sp + 1)->u.num) 41753852Selan goto true; 41853852Selan goto false; 41953852Selan case GT: 42053852Selan if (sp->u.num > (sp + 1)->u.num) 42153852Selan goto true; 42253852Selan goto false; 42353852Selan case LT: 42453852Selan if (sp->u.num < (sp + 1)->u.num) 42553852Selan goto true; 42653852Selan goto false; 42753852Selan case LE: 42853852Selan if (sp->u.num <= (sp + 1)->u.num) 42953852Selan goto true; 43053852Selan goto false; 43153852Selan case GE: 43253852Selan if (sp->u.num >= (sp + 1)->u.num) 43353852Selan goto true; 43453852Selan goto false; 43553835Selan 43653852Selan } 43753835Selan } 43853835Selan 43953835Selan 44053835Selan static int 44153835Selan lookup_op(name, table) 44253852Selan char *name; 44353852Selan char *const * table; 44453835Selan { 44553852Selan register char *const * tp; 44653852Selan register char const *p; 44753852Selan char c = name[1]; 44853835Selan 44953852Selan for (tp = table; (p = *tp) != NULL; tp++) { 45053852Selan if (p[1] == c && equal(p, name)) 45153852Selan return tp - table; 45253852Selan } 45353852Selan return -1; 45453835Selan } 45553835Selan 45653835Selan static int 45753835Selan posix_unary_op(argv) 45853852Selan char **argv; 45953835Selan { 46053852Selan int op, c; 46153852Selan char *opname; 46253835Selan struct filestat fs; 46353835Selan struct value valp; 46453835Selan 46553835Selan opname = *argv; 46653852Selan if ((op = lookup_op(opname, unary_op)) < 0) 46753835Selan return -1; 46853852Selan c = op_argflag[op]; 46953852Selan opname = argv[1]; 47053852Selan valp.u.string = opname; 47153852Selan if (c == OP_FILE) { 47253835Selan fs.name = opname; 47353835Selan fs.rcode = stat(opname, &fs.stat); 47453835Selan } else if (c != OP_STRING) 47553835Selan return -1; 47653835Selan 47753835Selan expr_operator(op, &valp, &fs); 47853835Selan return (valp.u.num == 0); 47953835Selan } 48053835Selan 48153835Selan 48253835Selan static int 48353835Selan posix_binary_op(argv) 48453852Selan char **argv; 48553835Selan { 48653852Selan int op, c; 48753852Selan char *opname; 48853835Selan struct value v[2]; 48953852Selan 49053835Selan opname = argv[1]; 49153835Selan if ((op = lookup_op(opname, binary_op)) < 0) 49253835Selan return -1; 49353835Selan op += FIRST_BINARY_OP; 49453835Selan c = op_argflag[op]; 49553835Selan 49653835Selan if (c == OP_INT && int_tcheck(argv[0]) && int_tcheck(argv[2])) { 49753835Selan v[0].u.num = atol(argv[0]); 49853835Selan v[1].u.num = atol(argv[2]); 49953835Selan } 50053835Selan expr_operator(op, v, NULL); 50153835Selan return (v[0].u.num == 0); 50253835Selan } 50353835Selan 50453835Selan /* 50553835Selan * Integer type checking. 50653835Selan */ 50753835Selan static int 50853835Selan int_tcheck(v) 50953852Selan char *v; 51053835Selan { 51153852Selan char *p; 51253852Selan char outbuf[512]; 51353835Selan 51453835Selan for (p = v; *p != '\0'; p++) 51553835Selan if (*p < '0' || *p > '9') { 51653852Selan snprintf(outbuf, sizeof(outbuf), 51753852Selan "Illegal operand \"%s\" -- expected integer.", v); 51853852Selan error(outbuf); 51953835Selan } 52053835Selan return 1; 52153835Selan } 522