153958Sbostic /*- 253958Sbostic * Copyright (c) 1992 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 1253958Sbostic char copyright[] = 1353958Sbostic "@(#) Copyright (c) 1992 The Regents of the University of California.\n\ 1453852Selan All rights reserved.\n"; 1553958Sbostic #endif /* not lint */ 1653835Selan 1753852Selan #ifndef lint 18*57920Storek static char sccsid[] = "@(#)test.c 5.3 (Berkeley) 02/10/93"; 1953958Sbostic #endif /* not lint */ 2053835Selan 2153835Selan #include <sys/types.h> 2253835Selan #include <sys/stat.h> 2353958Sbostic #include <errno.h> 2453958Sbostic #include <stdlib.h> 2553918Selan #include <string.h> 2653958Sbostic #include <stdio.h> 2753958Sbostic 2853835Selan #include "operators.h" 2953835Selan 3053958Sbostic #define STACKSIZE 12 3153958Sbostic #define NESTINCR 16 3253835Selan 3353835Selan /* data types */ 3453958Sbostic #define STRING 0 3553958Sbostic #define INTEGER 1 3653958Sbostic #define BOOLEAN 2 3753835Selan 3853958Sbostic #define IS_BANG(s) (s[0] == '!' && s[1] == '\0') 3953835Selan 4053835Selan /* 4153835Selan * This structure hold a value. The type keyword specifies the type of 4253835Selan * the value, and the union u holds the value. The value of a boolean 4353835Selan * is stored in u.num (1 = TRUE, 0 = FALSE). 4453835Selan */ 4553835Selan struct value { 4653958Sbostic int type; 4753852Selan union { 4853958Sbostic char *string; 4953958Sbostic long num; 5053958Sbostic } u; 5153835Selan }; 5253835Selan 5353835Selan struct operator { 5453958Sbostic short op; /* Which operator. */ 5553958Sbostic short pri; /* Priority of operator. */ 5653835Selan }; 5753835Selan 5853835Selan struct filestat { 5953958Sbostic char *name; /* Name of file. */ 6053958Sbostic int rcode; /* Return code from stat. */ 6153958Sbostic struct stat stat; /* Status info on file. */ 6253835Selan }; 6353835Selan 6453958Sbostic static void err __P((const char *, ...)); 6553958Sbostic static int expr_is_false __P((struct value *)); 6653958Sbostic static void expr_operator __P((int, struct value *, struct filestat *)); 6753958Sbostic static int int_tcheck __P((char *)); 6853958Sbostic static int lookup_op __P((char *, char *const *)); 6953958Sbostic static void overflow __P((void)); 7053958Sbostic static int posix_binary_op __P((char **)); 7153958Sbostic static int posix_unary_op __P((char **)); 7253958Sbostic static void syntax __P((void)); 7353835Selan 7453835Selan int 7553852Selan main(argc, argv) 7653958Sbostic int argc; 7753958Sbostic char *argv[]; 7853835Selan { 7953852Selan struct operator opstack[STACKSIZE]; 8053852Selan struct operator *opsp; 8153852Selan struct value valstack[STACKSIZE + 1]; 8253852Selan struct value *valsp; 8353852Selan struct filestat fs; 8453958Sbostic char c, **ap, *opname, *p; 8553958Sbostic int binary, nest, op, pri, ret_val, skipping; 8653835Selan 87*57920Storek if ((p = argv[0]) == NULL) { 8853958Sbostic err("test: argc is zero.\n"); 8953958Sbostic exit(2); 9053958Sbostic } 9153958Sbostic 92*57920Storek if (*p != '\0' && p[strlen(p) - 1] == '[') { 93*57920Storek if (strcmp(argv[--argc], "]")) 9453958Sbostic err("missing ]"); 95*57920Storek argv[argc] = NULL; 9653852Selan } 9753852Selan ap = argv + 1; 9853852Selan fs.name = NULL; 9953835Selan 10053958Sbostic /* 10153958Sbostic * Test(1) implements an inherently ambiguous grammer. In order to 10253958Sbostic * assure some degree of consistency, we special case the POSIX 1003.2 10353958Sbostic * requirements to assure correct evaluation for POSIX scripts. The 10453958Sbostic * following special cases comply with POSIX P1003.2/D11.2 Section 10553958Sbostic * 4.62.4. 10653958Sbostic */ 10753958Sbostic switch(argc - 1) { 10853958Sbostic case 0: /* % test */ 10953958Sbostic return (1); 11053852Selan break; 11153958Sbostic case 1: /* % test arg */ 11253852Selan return (*argv[1] == '\0') ? 1 : 0; 11353852Selan break; 11453958Sbostic case 2: /* % test op arg */ 11553852Selan opname = argv[1]; 11653852Selan if (IS_BANG(opname)) 11753852Selan return (*argv[2] == '\0') ? 1 : 0; 11853852Selan else { 11953852Selan ret_val = posix_unary_op(&argv[1]); 12053852Selan if (ret_val >= 0) 12153958Sbostic return (ret_val); 12253852Selan } 12353852Selan break; 12453958Sbostic case 3: /* % test arg1 op arg2 */ 12553852Selan if (IS_BANG(argv[1])) { 12653852Selan ret_val = posix_unary_op(&argv[1]); 12753852Selan if (ret_val >= 0) 12853958Sbostic return (!ret_val); 12953852Selan } else { 13053852Selan ret_val = posix_binary_op(&argv[1]); 13153852Selan if (ret_val >= 0) 13253958Sbostic return (ret_val); 13353852Selan } 13453852Selan break; 13553958Sbostic case 4: /* % test ! arg1 op arg2 */ 13653852Selan if (IS_BANG(argv[1])) { 13753852Selan ret_val = posix_binary_op(&argv[2]); 13853852Selan if (ret_val >= 0) 13953958Sbostic return (!ret_val); 14053852Selan } 14153852Selan break; 14253852Selan default: 14353852Selan break; 14453852Selan } 14553835Selan 14653958Sbostic /* 14753958Sbostic * We use operator precedence parsing, evaluating the expression as 14853852Selan * we parse it. Parentheses are handled by bumping up the priority 14953852Selan * of operators using the variable "nest." We use the variable 15053852Selan * "skipping" to turn off evaluation temporarily for the short 15153852Selan * circuit boolean operators. (It is important do the short circuit 15253852Selan * evaluation because under NFS a stat operation can take infinitely 15353958Sbostic * long.) 15453958Sbostic */ 15553852Selan opsp = opstack + STACKSIZE; 15653852Selan valsp = valstack; 15753958Sbostic nest = skipping = 0; 15853852Selan if (*ap == NULL) { 15953852Selan valstack[0].type = BOOLEAN; 16053852Selan valstack[0].u.num = 0; 16153852Selan goto done; 16253852Selan } 16353852Selan for (;;) { 16453852Selan opname = *ap++; 16553852Selan if (opname == NULL) 16653958Sbostic syntax(); 16753852Selan if (opname[0] == '(' && opname[1] == '\0') { 16853852Selan nest += NESTINCR; 16953852Selan continue; 17053852Selan } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) { 17153852Selan if (opsp == &opstack[0]) 17253958Sbostic overflow(); 17353852Selan --opsp; 17453852Selan opsp->op = op; 17553852Selan opsp->pri = op_priority[op] + nest; 17653852Selan continue; 17753852Selan } else { 17853852Selan valsp->type = STRING; 17953852Selan valsp->u.string = opname; 18053852Selan valsp++; 18153852Selan } 18253852Selan for (;;) { 18353852Selan opname = *ap++; 18453852Selan if (opname == NULL) { 18553852Selan if (nest != 0) 18653958Sbostic syntax(); 18753852Selan pri = 0; 18853852Selan break; 18953852Selan } 19053852Selan if (opname[0] != ')' || opname[1] != '\0') { 19153852Selan if ((op = lookup_op(opname, binary_op)) < 0) 19253958Sbostic syntax(); 19353852Selan op += FIRST_BINARY_OP; 19453852Selan pri = op_priority[op] + nest; 19553852Selan break; 19653852Selan } 19753852Selan if ((nest -= NESTINCR) < 0) 19853958Sbostic syntax(); 19953852Selan } 20053852Selan while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) { 20153852Selan binary = opsp->op; 20253852Selan for (;;) { 20353852Selan valsp--; 20453852Selan c = op_argflag[opsp->op]; 20553852Selan if (c == OP_INT) { 20653852Selan if (valsp->type == STRING && 20753852Selan int_tcheck(valsp->u.string)) 20853852Selan valsp->u.num = 20953852Selan atol(valsp->u.string); 21053852Selan valsp->type = INTEGER; 21153852Selan } else if (c >= OP_STRING) { 21253852Selan /* OP_STRING or OP_FILE */ 21353852Selan if (valsp->type == INTEGER) { 21453958Sbostic if ((p = malloc(32)) == NULL) 21553958Sbostic err("%s", 21653958Sbostic strerror(errno)); 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 27453835Selan expr_is_false(val) 27553852Selan struct value *val; 27653835Selan { 27753852Selan if (val->type == STRING) { 27853852Selan if (val->u.string[0] == '\0') 27953958Sbostic return (1); 28053852Selan } else { /* INTEGER or BOOLEAN */ 28153852Selan if (val->u.num == 0) 28253958Sbostic return (1); 28353852Selan } 28453958Sbostic return (0); 28553835Selan } 28653835Selan 28753835Selan 28853835Selan /* 28953835Selan * Execute an operator. Op is the operator. Sp is the stack pointer; 29053835Selan * sp[0] refers to the first operand, sp[1] refers to the second operand 29153835Selan * (if any), and the result is placed in sp[0]. The operands are converted 29253835Selan * to the type expected by the operator before expr_operator is called. 29353835Selan * Fs is a pointer to a structure which holds the value of the last call 29453835Selan * to stat, to avoid repeated stat calls on the same file. 29553835Selan */ 29653835Selan static void 29753835Selan expr_operator(op, sp, fs) 29853958Sbostic int op; 29953835Selan struct value *sp; 30053835Selan struct filestat *fs; 30153835Selan { 30253958Sbostic int i; 30353835Selan 30453835Selan switch (op) { 30553835Selan case NOT: 30653852Selan sp->u.num = expr_is_false(sp); 30753852Selan sp->type = BOOLEAN; 30853852Selan break; 30953852Selan case ISEXIST: 31053852Selan if (fs == NULL || fs->rcode == -1) 31153852Selan goto false; 31253852Selan else 31353852Selan goto true; 31453852Selan case ISREAD: 31553958Sbostic i = S_IROTH; 31653852Selan goto permission; 31753852Selan case ISWRITE: 31853958Sbostic i = S_IWOTH; 31953852Selan goto permission; 32053852Selan case ISEXEC: 32153958Sbostic i = S_IXOTH; 32253958Sbostic permission: if (fs->stat.st_uid == geteuid()) 32353852Selan i <<= 6; 32453852Selan else if (fs->stat.st_gid == getegid()) 32553852Selan i <<= 3; 32653852Selan goto filebit; /* true if (stat.st_mode & i) != 0 */ 32753852Selan case ISFILE: 32853852Selan i = S_IFREG; 32953852Selan goto filetype; 33053852Selan case ISDIR: 33153852Selan i = S_IFDIR; 33253852Selan goto filetype; 33353852Selan case ISCHAR: 33453852Selan i = S_IFCHR; 33553852Selan goto filetype; 33653852Selan case ISBLOCK: 33753852Selan i = S_IFBLK; 33853852Selan goto filetype; 33953852Selan case ISFIFO: 34053852Selan i = S_IFIFO; 34153852Selan goto filetype; 34253958Sbostic filetype: if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) 34353958Sbostic true: sp->u.num = 1; 34453958Sbostic else 34553958Sbostic false: sp->u.num = 0; 34653852Selan sp->type = BOOLEAN; 34753852Selan break; 34853852Selan case ISSETUID: 34953852Selan i = S_ISUID; 35053852Selan goto filebit; 35153852Selan case ISSETGID: 35253852Selan i = S_ISGID; 35353852Selan goto filebit; 35453852Selan case ISSTICKY: 35553852Selan i = S_ISVTX; 35653958Sbostic filebit: if (fs->stat.st_mode & i && fs->rcode >= 0) 35753852Selan goto true; 35853852Selan goto false; 35953852Selan case ISSIZE: 36053852Selan sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L; 36153852Selan sp->type = INTEGER; 36253852Selan break; 36353852Selan case ISTTY: 36453852Selan sp->u.num = isatty(sp->u.num); 36553852Selan sp->type = BOOLEAN; 36653852Selan break; 36753852Selan case NULSTR: 36853852Selan if (sp->u.string[0] == '\0') 36953852Selan goto true; 37053852Selan goto false; 37153852Selan case STRLEN: 37253852Selan sp->u.num = strlen(sp->u.string); 37353852Selan sp->type = INTEGER; 37453852Selan break; 37553852Selan case OR1: 37653852Selan case AND1: 37753958Sbostic /* 37853958Sbostic * These operators are mostly handled by the parser. If we 37953852Selan * get here it means that both operands were evaluated, so 38053958Sbostic * the value is the value of the second operand. 38153958Sbostic */ 38253852Selan *sp = *(sp + 1); 38353852Selan break; 38453852Selan case STREQ: 38553852Selan case STRNE: 38653852Selan i = 0; 38753958Sbostic if (!strcmp(sp->u.string, (sp + 1)->u.string)) 38853852Selan i++; 38953852Selan if (op == STRNE) 39053852Selan i = 1 - i; 39153852Selan sp->u.num = i; 39253852Selan sp->type = BOOLEAN; 39353852Selan break; 39453852Selan case EQ: 39553852Selan if (sp->u.num == (sp + 1)->u.num) 39653852Selan goto true; 39753852Selan goto false; 39853852Selan case NE: 39953852Selan if (sp->u.num != (sp + 1)->u.num) 40053852Selan goto true; 40153852Selan goto false; 40253852Selan case GT: 40353852Selan if (sp->u.num > (sp + 1)->u.num) 40453852Selan goto true; 40553852Selan goto false; 40653852Selan case LT: 40753852Selan if (sp->u.num < (sp + 1)->u.num) 40853852Selan goto true; 40953852Selan goto false; 41053852Selan case LE: 41153852Selan if (sp->u.num <= (sp + 1)->u.num) 41253852Selan goto true; 41353852Selan goto false; 41453852Selan case GE: 41553852Selan if (sp->u.num >= (sp + 1)->u.num) 41653852Selan goto true; 41753852Selan goto false; 41853835Selan 41953852Selan } 42053835Selan } 42153835Selan 42253835Selan static int 42353835Selan lookup_op(name, table) 42453852Selan char *name; 42553852Selan char *const * table; 42653835Selan { 42753852Selan register char *const * tp; 42853852Selan register char const *p; 42953958Sbostic char c; 43053835Selan 43153958Sbostic c = name[1]; 43253958Sbostic for (tp = table; (p = *tp) != NULL; tp++) 43353958Sbostic if (p[1] == c && !strcmp(p, name)) 43453958Sbostic return (tp - table); 43553958Sbostic return (-1); 43653835Selan } 43753835Selan 43853835Selan static int 43953835Selan posix_unary_op(argv) 44053958Sbostic char **argv; 44153835Selan { 44253835Selan struct filestat fs; 44353835Selan struct value valp; 44453958Sbostic int op, c; 44553958Sbostic char *opname; 44653835Selan 44753835Selan opname = *argv; 44853852Selan if ((op = lookup_op(opname, unary_op)) < 0) 44953958Sbostic return (-1); 45053852Selan c = op_argflag[op]; 45153852Selan opname = argv[1]; 45253852Selan valp.u.string = opname; 45353852Selan if (c == OP_FILE) { 45453835Selan fs.name = opname; 45553835Selan fs.rcode = stat(opname, &fs.stat); 45653835Selan } else if (c != OP_STRING) 45753958Sbostic return (-1); 45853835Selan 45953835Selan expr_operator(op, &valp, &fs); 46053835Selan return (valp.u.num == 0); 46153835Selan } 46253835Selan 46353835Selan static int 46453835Selan posix_binary_op(argv) 46553852Selan char **argv; 46653835Selan { 46753835Selan struct value v[2]; 46853958Sbostic int op, c; 46953958Sbostic char *opname; 47053852Selan 47153835Selan opname = argv[1]; 47253835Selan if ((op = lookup_op(opname, binary_op)) < 0) 47353958Sbostic return (-1); 47453835Selan op += FIRST_BINARY_OP; 47553835Selan c = op_argflag[op]; 47653835Selan 47753835Selan if (c == OP_INT && int_tcheck(argv[0]) && int_tcheck(argv[2])) { 47853835Selan v[0].u.num = atol(argv[0]); 47953835Selan v[1].u.num = atol(argv[2]); 48053918Selan } else { 48153918Selan v[0].u.string = argv[0]; 48253918Selan v[1].u.string = argv[2]; 48353835Selan } 48453835Selan expr_operator(op, v, NULL); 48553835Selan return (v[0].u.num == 0); 48653835Selan } 48753835Selan 48853835Selan /* 48953835Selan * Integer type checking. 49053835Selan */ 49153835Selan static int 49253835Selan int_tcheck(v) 49353958Sbostic char *v; 49453835Selan { 49553958Sbostic char *p; 49653835Selan 49753835Selan for (p = v; *p != '\0'; p++) 49853958Sbostic if (!isdigit(*p)) 49953958Sbostic err("illegal operand \"%s\" -- expected integer.", v); 50053958Sbostic return (1); 50153835Selan } 50253958Sbostic 50353958Sbostic static void 50453958Sbostic syntax() 50553958Sbostic { 50653958Sbostic err("syntax error"); 50753958Sbostic } 50853958Sbostic 50953958Sbostic static void 51053958Sbostic overflow() 51153958Sbostic { 51253958Sbostic err("expression is too complex"); 51353958Sbostic } 51453958Sbostic 51553958Sbostic #if __STDC__ 51653958Sbostic #include <stdarg.h> 51753958Sbostic #else 51853958Sbostic #include <varargs.h> 51953958Sbostic #endif 52053958Sbostic 52153958Sbostic void 52253958Sbostic #if __STDC__ 52353958Sbostic err(const char *fmt, ...) 52453958Sbostic #else 52553958Sbostic err(fmt, va_alist) 52653958Sbostic char *fmt; 52753958Sbostic va_dcl 52853958Sbostic #endif 52953958Sbostic { 53053958Sbostic va_list ap; 53153958Sbostic #if __STDC__ 53253958Sbostic va_start(ap, fmt); 53353958Sbostic #else 53453958Sbostic va_start(ap); 53553958Sbostic #endif 53653958Sbostic (void)fprintf(stderr, "test: "); 53753958Sbostic (void)vfprintf(stderr, fmt, ap); 53853958Sbostic va_end(ap); 53953958Sbostic (void)fprintf(stderr, "\n"); 54053958Sbostic exit(2); 54153958Sbostic /* NOTREACHED */ 54253958Sbostic } 543