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 1253859Selan 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*53918Selan static char sccsid[] = "@(#)test.c 1.4 (Berkeley) 06/05/92"; 1953852Selan #endif /* not lint */ 2053835Selan 2153835Selan #include <stdio.h> 2253835Selan #include <errno.h> 2353835Selan #include <sys/types.h> 2453835Selan #include <sys/stat.h> 25*53918Selan #include <string.h> 2653835Selan #include "operators.h" 2753835Selan #include "extern.h" 2853835Selan 2953835Selan #define STACKSIZE 12 3053835Selan #define NESTINCR 16 3153835Selan 3253835Selan /* data types */ 3353835Selan #define STRING 0 3453835Selan #define INTEGER 1 3553835Selan #define BOOLEAN 2 3653835Selan 3753835Selan #define INITARGS(argv) if (argv[0] == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else 3853835Selan 3953835Selan #define IS_BANG(s) (s[0] == '!' && s[1] == '\0') 4053835Selan #define equal(s1, s2) (strcmp(s1, s2) == 0) 4153835Selan 4253835Selan /* 4353835Selan * This structure hold a value. The type keyword specifies the type of 4453835Selan * the value, and the union u holds the value. The value of a boolean 4553835Selan * is stored in u.num (1 = TRUE, 0 = FALSE). 4653835Selan */ 4753835Selan 4853835Selan struct value { 4953852Selan int type; 5053852Selan union { 5153852Selan char *string; 5253852Selan long num; 5353852Selan } u; 5453835Selan }; 5553835Selan 5653835Selan struct operator { 5753852Selan short op; /* which operator */ 5853852Selan short pri; /* priority of operator */ 5953835Selan }; 6053835Selan 6153835Selan struct filestat { 6253852Selan char *name; /* name of file */ 6353852Selan int rcode; /* return code from stat */ 6453852Selan struct stat stat; /* status info on file */ 6553835Selan }; 6653835Selan 6753835Selan static int expr_is_false __P((struct value *)); 6853835Selan static void expr_operator __P((int, struct value *, struct filestat *)); 6953852Selan static int lookup_op __P((char *, char *const *)); 7053835Selan static long atol __P((const char *)); 7153835Selan static int posix_binary_op __P((char **)); 7253835Selan static int posix_unary_op __P((char **)); 7353835Selan static int int_tcheck __P((char *)); 7453835Selan 7553835Selan int 7653852Selan main(argc, argv) 7753852Selan int argc; 7853852Selan char **argv; 7953835Selan { 8053852Selan char **ap; 8153852Selan char *opname; 8253852Selan char c; 8353852Selan char *p; 8453852Selan int nest; /* parentheses nesting */ 8553852Selan int op; 8653852Selan int pri; 8753852Selan int skipping; 8853852Selan int binary; 8953852Selan struct operator opstack[STACKSIZE]; 9053852Selan struct operator *opsp; 9153852Selan struct value valstack[STACKSIZE + 1]; 9253852Selan struct value *valsp; 9353852Selan struct filestat fs; 9453852Selan int ret_val; 9553835Selan 9653852Selan INITARGS(argv); 9753852Selan if (**argv == '[') { 9853852Selan if (!equal(argv[argc - 1], "]")) 9953852Selan error("missing ]"); 10053852Selan argv[argc - 1] = NULL; 10153852Selan } 10253852Selan ap = argv + 1; 10353852Selan fs.name = NULL; 10453835Selan 10553852Selan /* Test(1) implements an inherently ambiguous grammer. In order to 10653852Selan * assure some degree of consistency, we special case the POSIX 10753852Selan * requirements to assure correct evaluation for POSIX following 10853852Selan * scripts. The following special cases comply with POSIX 10953852Selan * P1003.2/D11.2 Section 4.62.4. */ 11053852Selan switch (argc - 1) { 11153852Selan case 0: /* % test */ 11253852Selan return 1; 11353852Selan break; 11453852Selan case 1: /* % test arg */ 11553852Selan return (*argv[1] == '\0') ? 1 : 0; 11653852Selan break; 11753852Selan case 2: /* % test op arg */ 11853852Selan opname = argv[1]; 11953852Selan if (IS_BANG(opname)) 12053852Selan return (*argv[2] == '\0') ? 1 : 0; 12153852Selan else { 12253852Selan ret_val = posix_unary_op(&argv[1]); 12353852Selan if (ret_val >= 0) 12453852Selan return ret_val; 12553852Selan } 12653852Selan break; 12753852Selan case 3: /* % test arg1 op arg2 */ 12853852Selan if (IS_BANG(argv[1])) { 12953852Selan ret_val = posix_unary_op(&argv[1]); 13053852Selan if (ret_val >= 0) 13153852Selan return !ret_val; 13253852Selan } else { 13353852Selan ret_val = posix_binary_op(&argv[1]); 13453852Selan if (ret_val >= 0) 13553852Selan return ret_val; 13653852Selan } 13753852Selan break; 13853852Selan case 4: /* % test ! arg1 op arg2 */ 13953852Selan if (IS_BANG(argv[1])) { 14053852Selan ret_val = posix_binary_op(&argv[2]); 14153852Selan if (ret_val >= 0) 14253852Selan return !ret_val; 14353852Selan } 14453852Selan break; 14553852Selan default: 14653852Selan break; 14753852Selan } 14853835Selan 14953852Selan /* 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 15553852Selan * long.) */ 15653835Selan 15753852Selan opsp = opstack + STACKSIZE; 15853852Selan valsp = valstack; 15953852Selan nest = 0; 16053852Selan skipping = 0; 16153852Selan if (*ap == NULL) { 16253852Selan valstack[0].type = BOOLEAN; 16353852Selan valstack[0].u.num = 0; 16453852Selan goto done; 16553852Selan } 16653852Selan for (;;) { 16753852Selan opname = *ap++; 16853852Selan if (opname == NULL) 16953852Selan goto syntax; 17053852Selan if (opname[0] == '(' && opname[1] == '\0') { 17153852Selan nest += NESTINCR; 17253852Selan continue; 17353852Selan } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) { 17453852Selan if (opsp == &opstack[0]) 17553852Selan goto overflow; 17653852Selan --opsp; 17753852Selan opsp->op = op; 17853852Selan opsp->pri = op_priority[op] + nest; 17953852Selan continue; 18053835Selan 18153852Selan } else { 18253852Selan valsp->type = STRING; 18353852Selan valsp->u.string = opname; 18453852Selan valsp++; 18553852Selan } 18653852Selan for (;;) { 18753852Selan opname = *ap++; 18853852Selan if (opname == NULL) { 18953852Selan if (nest != 0) 19053852Selan goto syntax; 19153852Selan pri = 0; 19253852Selan break; 19353852Selan } 19453852Selan if (opname[0] != ')' || opname[1] != '\0') { 19553852Selan if ((op = lookup_op(opname, binary_op)) < 0) 19653852Selan goto syntax; 19753852Selan op += FIRST_BINARY_OP; 19853852Selan pri = op_priority[op] + nest; 19953852Selan break; 20053852Selan } 20153852Selan if ((nest -= NESTINCR) < 0) 20253852Selan goto syntax; 20353852Selan } 20453852Selan while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) { 20553852Selan binary = opsp->op; 20653852Selan for (;;) { 20753852Selan valsp--; 20853852Selan c = op_argflag[opsp->op]; 20953852Selan if (c == OP_INT) { 21053852Selan if (valsp->type == STRING && 21153852Selan int_tcheck(valsp->u.string)) 21253852Selan valsp->u.num = 21353852Selan atol(valsp->u.string); 21453852Selan valsp->type = INTEGER; 21553852Selan } else if (c >= OP_STRING) { 21653852Selan /* OP_STRING or OP_FILE */ 21753852Selan if (valsp->type == INTEGER) { 21853852Selan p = stalloc(32); 21953835Selan #ifdef SHELL 22053852Selan fmtstr(p, 32, "%d", 22153852Selan valsp->u.num); 22253835Selan #else 22353852Selan sprintf(p, "%d", valsp->u.num); 22453835Selan #endif 22553852Selan valsp->u.string = p; 22653852Selan } else if (valsp->type == BOOLEAN) { 22753852Selan if (valsp->u.num) 22853852Selan valsp->u.string = 22953852Selan "true"; 23053852Selan else 23153852Selan valsp->u.string = ""; 23253852Selan } 23353852Selan valsp->type = STRING; 23453852Selan if (c == OP_FILE 23553852Selan && (fs.name == NULL 23653852Selan || !equal(fs.name, 23753852Selan valsp->u.string))) { 23853852Selan fs.name = valsp->u.string; 23953852Selan fs.rcode = 24053852Selan stat(valsp->u.string, 24153852Selan &fs.stat); 24253852Selan } 24353852Selan } 24453852Selan if (binary < FIRST_BINARY_OP) 24553852Selan break; 24653852Selan binary = 0; 24753835Selan } 24853852Selan if (!skipping) 24953852Selan expr_operator(opsp->op, valsp, &fs); 25053852Selan else if (opsp->op == AND1 || opsp->op == OR1) 25153852Selan skipping--; 25253852Selan valsp++;/* push value */ 25353852Selan opsp++; /* pop operator */ 25453852Selan } 25553852Selan if (opname == NULL) 25653852Selan break; 25753852Selan if (opsp == &opstack[0]) 25853852Selan goto overflow; 25953852Selan if (op == AND1 || op == AND2) { 26053852Selan op = AND1; 26153852Selan if (skipping || expr_is_false(valsp - 1)) 26253852Selan skipping++; 26353852Selan } 26453852Selan if (op == OR1 || op == OR2) { 26553852Selan op = OR1; 26653852Selan if (skipping || !expr_is_false(valsp - 1)) 26753852Selan skipping++; 26853852Selan } 26953852Selan opsp--; 27053852Selan opsp->op = op; 27153852Selan opsp->pri = pri; 27253852Selan } 27353835Selan done: 27453852Selan return expr_is_false(&valstack[0]); 27553835Selan 27653852Selan syntax: error("syntax error"); 27753852Selan overflow: error("Expression too complex"); 27853835Selan 27953835Selan } 28053835Selan 28153835Selan 28253835Selan static int 28353835Selan expr_is_false(val) 28453852Selan struct value *val; 28553835Selan { 28653852Selan if (val->type == STRING) { 28753852Selan if (val->u.string[0] == '\0') 28853852Selan return 1; 28953852Selan } else { /* INTEGER or BOOLEAN */ 29053852Selan if (val->u.num == 0) 29153852Selan return 1; 29253852Selan } 29353852Selan return 0; 29453835Selan } 29553835Selan 29653835Selan 29753835Selan /* 29853835Selan * Execute an operator. Op is the operator. Sp is the stack pointer; 29953835Selan * sp[0] refers to the first operand, sp[1] refers to the second operand 30053835Selan * (if any), and the result is placed in sp[0]. The operands are converted 30153835Selan * to the type expected by the operator before expr_operator is called. 30253835Selan * Fs is a pointer to a structure which holds the value of the last call 30353835Selan * to stat, to avoid repeated stat calls on the same file. 30453835Selan */ 30553835Selan 30653835Selan static void 30753835Selan expr_operator(op, sp, fs) 30853852Selan int op; 30953835Selan struct value *sp; 31053835Selan struct filestat *fs; 31153835Selan { 31253852Selan int i; 31353835Selan 31453835Selan switch (op) { 31553835Selan case NOT: 31653852Selan sp->u.num = expr_is_false(sp); 31753852Selan sp->type = BOOLEAN; 31853852Selan break; 31953852Selan case ISEXIST: 32053852Selan if (fs == NULL || fs->rcode == -1) 32153852Selan goto false; 32253852Selan else 32353852Selan goto true; 32453852Selan case ISREAD: 32553852Selan i = 04; 32653852Selan goto permission; 32753852Selan case ISWRITE: 32853852Selan i = 02; 32953852Selan goto permission; 33053852Selan case ISEXEC: 33153852Selan i = 01; 33253835Selan permission: 33353852Selan if (fs->stat.st_uid == geteuid()) 33453852Selan i <<= 6; 33553852Selan else if (fs->stat.st_gid == getegid()) 33653852Selan i <<= 3; 33753852Selan goto filebit; /* true if (stat.st_mode & i) != 0 */ 33853852Selan case ISFILE: 33953852Selan i = S_IFREG; 34053852Selan goto filetype; 34153852Selan case ISDIR: 34253852Selan i = S_IFDIR; 34353852Selan goto filetype; 34453852Selan case ISCHAR: 34553852Selan i = S_IFCHR; 34653852Selan goto filetype; 34753852Selan case ISBLOCK: 34853852Selan i = S_IFBLK; 34953852Selan goto filetype; 35053852Selan case ISFIFO: 35153835Selan #ifdef S_IFIFO 35253852Selan i = S_IFIFO; 35353852Selan goto filetype; 35453835Selan #else 35553852Selan goto false; 35653835Selan #endif 35753835Selan filetype: 35853852Selan if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) { 35953852Selan true: 36053852Selan sp->u.num = 1; 36153852Selan } else { 36253852Selan false: 36353852Selan sp->u.num = 0; 36453852Selan } 36553852Selan sp->type = BOOLEAN; 36653852Selan break; 36753852Selan case ISSETUID: 36853852Selan i = S_ISUID; 36953852Selan goto filebit; 37053852Selan case ISSETGID: 37153852Selan i = S_ISGID; 37253852Selan goto filebit; 37353852Selan case ISSTICKY: 37453852Selan i = S_ISVTX; 37553835Selan filebit: 37653852Selan if (fs->stat.st_mode & i && fs->rcode >= 0) 37753852Selan goto true; 37853852Selan goto false; 37953852Selan case ISSIZE: 38053852Selan sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L; 38153852Selan sp->type = INTEGER; 38253852Selan break; 38353852Selan case ISTTY: 38453852Selan sp->u.num = isatty(sp->u.num); 38553852Selan sp->type = BOOLEAN; 38653852Selan break; 38753852Selan case NULSTR: 38853852Selan if (sp->u.string[0] == '\0') 38953852Selan goto true; 39053852Selan goto false; 39153852Selan case STRLEN: 39253852Selan sp->u.num = strlen(sp->u.string); 39353852Selan sp->type = INTEGER; 39453852Selan break; 39553852Selan case OR1: 39653852Selan case AND1: 39753852Selan /* These operators are mostly handled by the parser. If we 39853852Selan * get here it means that both operands were evaluated, so 39953852Selan * the value is the value of the second operand. */ 40053852Selan *sp = *(sp + 1); 40153852Selan break; 40253852Selan case STREQ: 40353852Selan case STRNE: 40453852Selan i = 0; 40553852Selan if (equal(sp->u.string, (sp + 1)->u.string)) 40653852Selan i++; 40753852Selan if (op == STRNE) 40853852Selan i = 1 - i; 40953852Selan sp->u.num = i; 41053852Selan sp->type = BOOLEAN; 41153852Selan break; 41253852Selan case EQ: 41353852Selan if (sp->u.num == (sp + 1)->u.num) 41453852Selan goto true; 41553852Selan goto false; 41653852Selan case NE: 41753852Selan if (sp->u.num != (sp + 1)->u.num) 41853852Selan goto true; 41953852Selan goto false; 42053852Selan case GT: 42153852Selan if (sp->u.num > (sp + 1)->u.num) 42253852Selan goto true; 42353852Selan goto false; 42453852Selan case LT: 42553852Selan if (sp->u.num < (sp + 1)->u.num) 42653852Selan goto true; 42753852Selan goto false; 42853852Selan case LE: 42953852Selan if (sp->u.num <= (sp + 1)->u.num) 43053852Selan goto true; 43153852Selan goto false; 43253852Selan case GE: 43353852Selan if (sp->u.num >= (sp + 1)->u.num) 43453852Selan goto true; 43553852Selan goto false; 43653835Selan 43753852Selan } 43853835Selan } 43953835Selan 44053835Selan 44153835Selan static int 44253835Selan lookup_op(name, table) 44353852Selan char *name; 44453852Selan char *const * table; 44553835Selan { 44653852Selan register char *const * tp; 44753852Selan register char const *p; 44853852Selan char c = name[1]; 44953835Selan 45053852Selan for (tp = table; (p = *tp) != NULL; tp++) { 45153852Selan if (p[1] == c && equal(p, name)) 45253852Selan return tp - table; 45353852Selan } 45453852Selan return -1; 45553835Selan } 45653835Selan 45753835Selan static int 45853835Selan posix_unary_op(argv) 45953852Selan char **argv; 46053835Selan { 46153852Selan int op, c; 46253852Selan char *opname; 46353835Selan struct filestat fs; 46453835Selan struct value valp; 46553835Selan 46653835Selan opname = *argv; 46753852Selan if ((op = lookup_op(opname, unary_op)) < 0) 46853835Selan return -1; 46953852Selan c = op_argflag[op]; 47053852Selan opname = argv[1]; 47153852Selan valp.u.string = opname; 47253852Selan if (c == OP_FILE) { 47353835Selan fs.name = opname; 47453835Selan fs.rcode = stat(opname, &fs.stat); 47553835Selan } else if (c != OP_STRING) 47653835Selan return -1; 47753835Selan 47853835Selan expr_operator(op, &valp, &fs); 47953835Selan return (valp.u.num == 0); 48053835Selan } 48153835Selan 48253835Selan 48353835Selan static int 48453835Selan posix_binary_op(argv) 48553852Selan char **argv; 48653835Selan { 48753852Selan int op, c; 48853852Selan char *opname; 48953835Selan struct value v[2]; 49053852Selan 49153835Selan opname = argv[1]; 49253835Selan if ((op = lookup_op(opname, binary_op)) < 0) 49353835Selan return -1; 49453835Selan op += FIRST_BINARY_OP; 49553835Selan c = op_argflag[op]; 49653835Selan 49753835Selan if (c == OP_INT && int_tcheck(argv[0]) && int_tcheck(argv[2])) { 49853835Selan v[0].u.num = atol(argv[0]); 49953835Selan v[1].u.num = atol(argv[2]); 500*53918Selan } else { 501*53918Selan v[0].u.string = argv[0]; 502*53918Selan v[1].u.string = argv[2]; 50353835Selan } 50453835Selan expr_operator(op, v, NULL); 50553835Selan return (v[0].u.num == 0); 50653835Selan } 50753835Selan 50853835Selan /* 50953835Selan * Integer type checking. 51053835Selan */ 51153835Selan static int 51253835Selan int_tcheck(v) 51353852Selan char *v; 51453835Selan { 51553852Selan char *p; 51653852Selan char outbuf[512]; 51753835Selan 51853835Selan for (p = v; *p != '\0'; p++) 51953835Selan if (*p < '0' || *p > '9') { 52053852Selan snprintf(outbuf, sizeof(outbuf), 52153852Selan "Illegal operand \"%s\" -- expected integer.", v); 52253852Selan error(outbuf); 52353835Selan } 52453835Selan return 1; 52553835Selan } 526