From 008c3a98a0729c7c73608f206caed18b6fac645a Mon Sep 17 00:00:00 2001 From: jtc Date: Thu, 30 Jun 1994 05:12:29 +0000 Subject: [PATCH] Replaced test with simpler, more feature rich, version derived from pdksh. --- bin/test/Makefile | 9 +- bin/test/test.1 | 82 +++- bin/test/test.c | 946 ++++++++++++++++++++-------------------------- 3 files changed, 480 insertions(+), 557 deletions(-) diff --git a/bin/test/Makefile b/bin/test/Makefile index d18b2c9d5f46..bf51dff5d120 100644 --- a/bin/test/Makefile +++ b/bin/test/Makefile @@ -1,14 +1,9 @@ # from: @(#)Makefile 5.1 (Berkeley) 6/8/92 -# $Id: Makefile,v 1.4 1993/08/01 05:46:39 mycroft Exp $ +# $Id: Makefile,v 1.5 1994/06/30 05:12:29 jtc Exp $ PROG= test -SRCS= test.c operators.c -CFLAGS+=-I. +SRCS= test.c LINKS= ${BINDIR}/test ${BINDIR}/[ MLINKS= test.1 '[.1' -# use this rule to if you update binary_ops, or unary_ops -make_op: - sh ${.CURDIR}/mkops - .include diff --git a/bin/test/test.1 b/bin/test/test.1 index bf579fbe86dd..b368f1b97716 100644 --- a/bin/test/test.1 +++ b/bin/test/test.1 @@ -33,7 +33,7 @@ .\" SUCH DAMAGE. .\" .\" from: @(#)test.1 6.6 (Berkeley) 6/8/92 -.\" $Id: test.1,v 1.4 1994/06/29 22:26:50 jtc Exp $ +.\" $Id: test.1,v 1.5 1994/06/30 05:12:34 jtc Exp $ .\" .Dd June 8, 1992 .Dt TEST 1 @@ -86,10 +86,10 @@ True if .Ar file exists and its set group ID flag is set. -.It Fl h Ar file +.It Fl k Ar file True if .Ar file -exists and is a symbolic link. +exists and its sticky bit is set. .It Fl n Ar string True if the length of .Ar string @@ -101,7 +101,8 @@ is a named pipe .Po Tn FIFO Pc . .It Fl r Ar file True if -.Ar file exists and is readable. +.Ar file +exists and is readable. .It Fl s Ar file True if .Ar file @@ -140,6 +141,38 @@ can be searched. True if the length of .Ar string is zero. +.It Fl L Ar file +True if +.Ar file +exists and is a symbolic link. +.It Fl O Ar file +True if +.Ar file +exists and its owner matches the effective user id of this process. +.It Fl G Ar file +True if +.Ar file +exists and its group matches the effective group id of this process. +.It Fl S Ar file +True if +.Ar file +exists and is a socket. +.It Ar file1 Fl nt Ar file2 +True if +.Ar file1 +exists and is newer than +.Ar file2 . +.It Ar file1 Fl ot Ar file2 +True if +.Ar file1 +exists and is older than +.Ar file2 . +.It Ar file1 Fl ef Ar file2 +True if +.Ar file1 +and +.Ar file2 +exist and refer to the same file. .It Ar string True if .Ar string @@ -157,6 +190,23 @@ True if the strings and .Ar \&s\&2 are not identical. +.It Ar \&s\&1 Cm \&< Ar \&s\&2 +True if string +.Ar \&s\&1 +comes before +.Ar \&s\&2 +based on the ASCII value of their characters. +.It Ar \&s\&1 Cm \&> Ar \&s\&2 +True if string +.Ar \&s\&1 +comes after +.Ar \&s\&2 +based on the ASCII value of their characters. +.It Ar \&s\&1 +True if +.Ar \&s\&1 +is not the null +string. .It Ar \&n\&1 Fl \&eq Ar \&n\&2 True if the integers .Ar \&n\&1 @@ -224,16 +274,6 @@ The operator has higher precedence than the .Fl o operator. -.Sh GRAMMAR AMBIGUITY -The -.Nm test -grammar is inherently ambiguous. In order to assure a degree of consistency, -the cases described in the -.St -p1003.2 , -section D11.2/4.62.4, standard -are evaluated consistently according to the rules specified in the -standards document. All other cases are subject to the ambiguity in the -command semantics. .Sh RETURN VALUES The .Nm test @@ -250,6 +290,16 @@ An error occurred. .Sh STANDARDS The .Nm test -function is expected to be +utility implements a superset of the .St -p1003.2 -compatible. +specification. +.Sh BUGS +The +.Nm test +grammar is inherently ambiguous. In order to assure a degree of consistency, +the cases described in +.St -p1003.2 +section 4.62.4, +are evaluated consistently according to the rules specified in the +standards document. All other cases are subject to the ambiguity in the +command semantics. diff --git a/bin/test/test.c b/bin/test/test.c index daf9bc8fc6e3..1fbe99db24a4 100644 --- a/bin/test/test.c +++ b/bin/test/test.c @@ -1,585 +1,463 @@ -/*- - * Copyright (c) 1992 The Regents of the University of California. - * All rights reserved. +/* + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. * - * This code is derived from software contributed to Berkeley by - * Kenneth Almquist. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * This program is in the Public Domain. */ #ifndef lint -char copyright[] = -"@(#) Copyright (c) 1992 The Regents of the University of California.\n\ - All rights reserved.\n"; -#endif /* not lint */ - -#ifndef lint -/*static char sccsid[] = "@(#)test.c 5.4 (Berkeley) 2/12/93";*/ -static char *rcsid = "$Id: test.c,v 1.12 1994/06/29 22:10:16 jtc Exp $"; -#endif /* not lint */ +static char *rcsid = "$Id: test.c,v 1.13 1994/06/30 05:12:38 jtc Exp $"; +#endif #include #include +#include +#include #include +#include #include #include -#include +#include -#include "operators.h" +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary ! "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; -#define STACKSIZE 12 -#define NESTINCR 16 + binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= +*/ -/* data types */ -#define STRING 0 -#define INTEGER 1 -#define BOOLEAN 2 - -#define IS_BANG(s) (s[0] == '!' && s[1] == '\0') - -/* - * This structure hold a value. The type keyword specifies the type of - * the value, and the union u holds the value. The value of a boolean - * is stored in u.num (1 = TRUE, 0 = FALSE). - */ -struct value { - int type; - union { - char *string; - long num; - } u; +enum token { + EOI, + FILRD, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + FILNT, + FILOT, + FILEQ, + FILUID, + FILGID, + STREZ, + STRNZ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT, + BAND, + BOR, + LPAREN, + RPAREN, + OPERAND }; -struct operator { - short op; /* Which operator. */ - short pri; /* Priority of operator. */ +enum token_types { + UNOP, + BINOP, + BUNOP, + BBINOP, + PAREN }; -struct filestat { - char *name; /* Name of file. */ - int rcode; /* Return code from stat. */ - struct stat stat; /* Status info on file. */ +struct t_op { + const char *op_text; + short op_num, op_type; +} const ops [] = { + {"-r", FILRD, UNOP}, + {"-w", FILWR, UNOP}, + {"-x", FILEX, UNOP}, + {"-e", FILEXIST,UNOP}, + {"-f", FILREG, UNOP}, + {"-d", FILDIR, UNOP}, + {"-c", FILCDEV,UNOP}, + {"-b", FILBDEV,UNOP}, + {"-p", FILFIFO,UNOP}, + {"-u", FILSUID,UNOP}, + {"-g", FILSGID,UNOP}, + {"-k", FILSTCK,UNOP}, + {"-s", FILGZ, UNOP}, + {"-t", FILTT, UNOP}, + {"-z", STREZ, UNOP}, + {"-n", STRNZ, UNOP}, + {"-h", FILSYM, UNOP}, /* for backwards compat */ + {"-O", FILUID, UNOP}, + {"-G", FILGID, UNOP}, + {"-L", FILSYM, UNOP}, + {"-S", FILSOCK,UNOP}, + {"=", STREQ, BINOP}, + {"!=", STRNE, BINOP}, + {"<", STRLT, BINOP}, + {">", STRGT, BINOP}, + {"-eq", INTEQ, BINOP}, + {"-ne", INTNE, BINOP}, + {"-ge", INTGE, BINOP}, + {"-gt", INTGT, BINOP}, + {"-le", INTLE, BINOP}, + {"-lt", INTLT, BINOP}, + {"-nt", FILNT, BINOP}, + {"-ot", FILOT, BINOP}, + {"-ef", FILEQ, BINOP}, + {"!", UNOT, BUNOP}, + {"-a", BAND, BBINOP}, + {"-o", BOR, BBINOP}, + {"(", LPAREN, PAREN}, + {")", RPAREN, PAREN}, + {0, 0, 0} }; -static void err __P((const char *, ...)); -static int expr_is_false __P((struct value *)); -static void expr_operator __P((int, struct value *, struct filestat *)); -static long chk_atol __P((char *)); -static int lookup_op __P((char *, char *const *)); -static void overflow __P((void)); -static int posix_binary_op __P((char **)); -static int posix_unary_op __P((char **)); -static void syntax __P((void)); +char **t_wp; +struct t_op const *t_wp_op; + +static enum token t_lex(); +static int oexpr(); +static int aexpr(); +static int nexpr(); +static int primary(); +static int filstat(); +static int getn(); +static int newerf(); +static int olderf(); +static int equalf(); +static void syntax(); int main(argc, argv) int argc; - char *argv[]; -{ - struct operator opstack[STACKSIZE]; - struct operator *opsp; - struct value valstack[STACKSIZE + 1]; - struct value *valsp; - struct filestat fs; - char c, **ap, *opname, *p; - int binary, nest, op, pri, ret_val, skipping; - - if ((p = argv[0]) == NULL) { - err("test: argc is zero.\n"); - exit(2); - } - - if (*p != '\0' && p[strlen(p) - 1] == '[') { - if (strcmp(argv[--argc], "]")) - err("missing ]"); - argv[argc] = NULL; - } - ap = argv + 1; - fs.name = NULL; - - /* - * Test(1) implements an inherently ambiguous grammer. In order to - * assure some degree of consistency, we special case the POSIX 1003.2 - * requirements to assure correct evaluation for POSIX scripts. The - * following special cases comply with POSIX P1003.2/D11.2 Section - * 4.62.4. - */ - switch(argc - 1) { - case 0: /* % test */ - return (1); - break; - case 1: /* % test arg */ - /* MIPS machine returns NULL of '[ ]' is called. */ - return (argv[1] == 0 || *argv[1] == '\0') ? 1 : 0; - break; - case 2: /* % test op arg */ - opname = argv[1]; - if (IS_BANG(opname)) - return (*argv[2] == '\0') ? 0 : 1; - else { - ret_val = posix_unary_op(&argv[1]); - if (ret_val >= 0) - return (ret_val); - } - break; - case 3: /* % test arg1 op arg2 */ - if (IS_BANG(argv[1])) { - ret_val = posix_unary_op(&argv[1]); - if (ret_val >= 0) - return (!ret_val); - } else if (lookup_op(argv[2], andor_op) < 0) { - ret_val = posix_binary_op(&argv[1]); - if (ret_val >= 0) - return (ret_val); - } - break; - case 4: /* % test ! arg1 op arg2 */ - if (IS_BANG(argv[1]) && lookup_op(argv[3], andor_op) < 0) { - ret_val = posix_binary_op(&argv[2]); - if (ret_val >= 0) - return (!ret_val); - } - break; - default: - break; - } - - /* - * We use operator precedence parsing, evaluating the expression as - * we parse it. Parentheses are handled by bumping up the priority - * of operators using the variable "nest." We use the variable - * "skipping" to turn off evaluation temporarily for the short - * circuit boolean operators. (It is important do the short circuit - * evaluation because under NFS a stat operation can take infinitely - * long.) - */ - opsp = opstack + STACKSIZE; - valsp = valstack; - nest = skipping = 0; - if (*ap == NULL) { - valstack[0].type = BOOLEAN; - valstack[0].u.num = 0; - goto done; - } - for (;;) { - opname = *ap++; - if (opname == NULL) - syntax(); - if (opname[0] == '(' && opname[1] == '\0') { - nest += NESTINCR; - continue; - } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) { - if (opsp == &opstack[0]) - overflow(); - --opsp; - opsp->op = op; - opsp->pri = op_priority[op] + nest; - continue; - } else { - valsp->type = STRING; - valsp->u.string = opname; - valsp++; - } - for (;;) { - opname = *ap++; - if (opname == NULL) { - if (nest != 0) - syntax(); - pri = 0; - break; - } - if (opname[0] != ')' || opname[1] != '\0') { - if ((op = lookup_op(opname, binary_op)) < 0) - syntax(); - op += FIRST_BINARY_OP; - pri = op_priority[op] + nest; - break; - } - if ((nest -= NESTINCR) < 0) - syntax(); - } - while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) { - binary = opsp->op; - for (;;) { - valsp--; - c = op_argflag[opsp->op]; - if (c == OP_INT) { - if (valsp->type == STRING) - valsp->u.num = - chk_atol(valsp->u.string); - valsp->type = INTEGER; - } else if (c >= OP_STRING) { - /* OP_STRING or OP_FILE */ - if (valsp->type == INTEGER) { - if ((p = malloc(32)) == NULL) - err("%s", - strerror(errno)); -#ifdef SHELL - fmtstr(p, 32, "%d", - valsp->u.num); -#else - (void)sprintf(p, - "%d", valsp->u.num); -#endif - valsp->u.string = p; - } else if (valsp->type == BOOLEAN) { - if (valsp->u.num) - valsp->u.string = - "true"; - else - valsp->u.string = ""; - } - valsp->type = STRING; - if (c == OP_FILE && (fs.name == NULL || - strcmp(fs.name, valsp->u.string))) { - fs.name = valsp->u.string; - fs.rcode = - stat(valsp->u.string, - &fs.stat); - } - } - if (binary < FIRST_BINARY_OP) - break; - binary = 0; - } - if (!skipping) - expr_operator(opsp->op, valsp, &fs); - else if (opsp->op == AND1 || opsp->op == OR1) - skipping--; - valsp++; /* push value */ - opsp++; /* pop operator */ - } - if (opname == NULL) - break; - if (opsp == &opstack[0]) - overflow(); - if (op == AND1 || op == AND2) { - op = AND1; - if (skipping || expr_is_false(valsp - 1)) - skipping++; - } - if (op == OR1 || op == OR2) { - op = OR1; - if (skipping || !expr_is_false(valsp - 1)) - skipping++; - } - opsp--; - opsp->op = op; - opsp->pri = pri; - } -done: return (expr_is_false(&valstack[0])); -} - -static int -expr_is_false(val) - struct value *val; -{ - if (val->type == STRING) { - if (val->u.string[0] == '\0') - return (1); - } else { /* INTEGER or BOOLEAN */ - if (val->u.num == 0) - return (1); - } - return (0); -} - - -/* - * Execute an operator. Op is the operator. Sp is the stack pointer; - * sp[0] refers to the first operand, sp[1] refers to the second operand - * (if any), and the result is placed in sp[0]. The operands are converted - * to the type expected by the operator before expr_operator is called. - * Fs is a pointer to a structure which holds the value of the last call - * to stat, to avoid repeated stat calls on the same file. - */ -static void -expr_operator(op, sp, fs) - int op; - struct value *sp; - struct filestat *fs; -{ - int i; - - switch (op) { - case NOT: - sp->u.num = expr_is_false(sp); - sp->type = BOOLEAN; - break; - case ISEXIST: - if (fs == NULL || fs->rcode == -1) - goto false; - else - goto true; - case ISREAD: - i = S_IROTH; - goto permission; - case ISWRITE: - i = S_IWOTH; - goto permission; - case ISEXEC: - i = S_IXOTH; -permission: if (fs->stat.st_uid == geteuid()) - i <<= 6; - else if (fs->stat.st_gid == getegid()) - i <<= 3; - goto filebit; /* true if (stat.st_mode & i) != 0 */ - case ISFILE: - i = S_IFREG; - goto filetype; - case ISDIR: - i = S_IFDIR; - goto filetype; - case ISCHAR: - i = S_IFCHR; - goto filetype; - case ISBLOCK: - i = S_IFBLK; - goto filetype; - case ISFIFO: - i = S_IFIFO; - goto filetype; -filetype: if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) -true: sp->u.num = 1; - else -false: sp->u.num = 0; - sp->type = BOOLEAN; - break; - case ISSETUID: - i = S_ISUID; - goto filebit; - case ISSETGID: - i = S_ISGID; - goto filebit; - case ISSTICKY: - i = S_ISVTX; -filebit: if (fs->stat.st_mode & i && fs->rcode >= 0) - goto true; - goto false; - case ISSIZE: - sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L; - sp->type = INTEGER; - break; - case ISTTY: - sp->u.num = isatty(sp->u.num); - sp->type = BOOLEAN; - break; - case ISLNK: - { - struct stat sb; - int rv; - - rv = lstat(fs->name, &sb); - if ((sb.st_mode & S_IFLNK) == S_IFLNK && rv >= 0) - goto true; - goto false; - } - case NULSTR: - if (sp->u.string[0] == '\0') - goto true; - goto false; - case STRLEN: - sp->u.num = strlen(sp->u.string); - sp->type = INTEGER; - break; - case OR1: - case AND1: - /* - * These operators are mostly handled by the parser. If we - * get here it means that both operands were evaluated, so - * the value is the value of the second operand. - */ - *sp = *(sp + 1); - break; - case STREQ: - case STRNE: - i = 0; - if (!strcmp(sp->u.string, (sp + 1)->u.string)) - i++; - if (op == STRNE) - i = 1 - i; - sp->u.num = i; - sp->type = BOOLEAN; - break; - case EQ: - if (sp->u.num == (sp + 1)->u.num) - goto true; - goto false; - case NE: - if (sp->u.num != (sp + 1)->u.num) - goto true; - goto false; - case GT: - if (sp->u.num > (sp + 1)->u.num) - goto true; - goto false; - case LT: - if (sp->u.num < (sp + 1)->u.num) - goto true; - goto false; - case LE: - if (sp->u.num <= (sp + 1)->u.num) - goto true; - goto false; - case GE: - if (sp->u.num >= (sp + 1)->u.num) - goto true; - goto false; - - } -} - -static int -lookup_op(name, table) - char *name; - char *const * table; -{ - register char *const * tp; - register char const *p; - char c; - - c = name[1]; - for (tp = table; (p = *tp) != NULL; tp++) - if (p[1] == c && !strcmp(p, name)) - return (tp - table); - return (-1); -} - -static int -posix_unary_op(argv) char **argv; { - struct filestat fs; - struct value valp; - int op, c; - char *opname; + int res; - opname = *argv; - if ((op = lookup_op(opname, unary_op)) < 0) - return (-1); - c = op_argflag[op]; - opname = argv[1]; - valp.u.string = opname; - if (c == OP_FILE) { - fs.name = opname; - fs.rcode = stat(opname, &fs.stat); - } else if (c != OP_STRING) - return (-1); + t_wp = argv+1; + if (strcmp(argv[0], "[") == 0) { + if (strcmp(argv[--argc], "]")) + errx(2, "missing ]"); + argv[argc] = NULL; + } - expr_operator(op, &valp, &fs); - return (valp.u.num == 0); + /* Implement special cases from POSIX.2, section 4.62.4 */ + switch (argc) { + case 1: + return 1; + case 2: + return (*argv[1] == '\0'); + case 3: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + return !(*argv[2] == '\0'); + } + } + + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) + syntax(*t_wp, "unknown operand"); + + return res; +} + +static void +syntax(op, msg) + char *op; + char *msg; +{ + if (op && *op) + errx(2, "%s: %s", op, msg); + else + errx(2, "%s", msg); } static int -posix_binary_op(argv) - char **argv; +oexpr(n) + enum token n; { - struct value v[2]; - int op, c; - char *opname; + int res; - opname = argv[1]; - if ((op = lookup_op(opname, binary_op)) < 0) - return (-1); - op += FIRST_BINARY_OP; - c = op_argflag[op]; - - if (c == OP_INT) { - v[0].u.num = chk_atol(argv[0]); - v[1].u.num = chk_atol(argv[2]); - } else { - v[0].u.string = argv[0]; - v[1].u.string = argv[2]; - } - expr_operator(op, v, NULL); - return (v[0].u.num == 0); + res = aexpr(n); + if (t_lex(*++t_wp) == BOR) + return oexpr(t_lex(*++t_wp)) || res; + t_wp--; + return res; } -/* - * Integer type checking. - */ -static long -chk_atol(v) - char *v; +static int +aexpr(n) + enum token n; +{ + int res; + + res = nexpr(n); + if (t_lex(*++t_wp) == BAND) + return aexpr(t_lex(*++t_wp)) && res; + t_wp--; + return res; +} + +static int +nexpr(n) + enum token n; /* token */ +{ + if (n == UNOT) + return !nexpr(t_lex(*++t_wp)); + return primary(n); +} + +static int +primary(n) + enum token n; +{ + register char *opnd1, *opnd2; + int res; + + if (n == EOI) + syntax(NULL, "argument expected"); + if (n == LPAREN) { + res = oexpr(t_lex(*++t_wp)); + if (t_lex(*++t_wp) != RPAREN) + syntax(NULL, "closing paren expected"); + return res; + } + if (t_wp_op && t_wp_op->op_type == UNOP) { + /* unary expression */ + if (*++t_wp == NULL) + syntax(t_wp_op->op_text, "argument expected"); + switch (n) { + case STREZ: + return strlen(*t_wp) == 0; + case STRNZ: + return strlen(*t_wp) != 0; + case FILTT: + return isatty(getn(*t_wp)); + default: + return filstat(*t_wp, n); + } + } + opnd1 = *t_wp; + (void) t_lex(*++t_wp); + if (t_wp_op && t_wp_op->op_type == BINOP) { + struct t_op const *op = t_wp_op; + + if ((opnd2 = *++t_wp) == (char *)0) + syntax(op->op_text, "argument expected"); + + switch (op->op_num) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return getn(opnd1) == getn(opnd2); + case INTNE: + return getn(opnd1) != getn(opnd2); + case INTGE: + return getn(opnd1) >= getn(opnd2); + case INTGT: + return getn(opnd1) > getn(opnd2); + case INTLE: + return getn(opnd1) <= getn(opnd2); + case INTLT: + return getn(opnd1) < getn(opnd2); + case FILNT: + return newerf (opnd1, opnd2); + case FILOT: + return olderf (opnd1, opnd2); + case FILEQ: + return equalf (opnd1, opnd2); + } + } + t_wp--; + return strlen(opnd1) > 0; +} + +static int +filstat(nm, mode) + char *nm; + enum token mode; +{ + struct stat s; + int i; + + if (mode == FILSYM) { +#ifdef S_IFLNK + if (lstat(nm, &s) == 0) { + i = S_IFLNK; + goto filetype; + } +#endif + return 0; + } + + if (stat(nm, &s) != 0) + return 0; + + switch (mode) { + case FILRD: + return access(nm, R_OK) == 0; + case FILWR: + return access(nm, W_OK) == 0; + case FILEX: + return access(nm, X_OK) == 0; + case FILEXIST: + return access(nm, F_OK) == 0; + case FILREG: + i = S_IFREG; + goto filetype; + case FILDIR: + i = S_IFDIR; + goto filetype; + case FILCDEV: + i = S_IFCHR; + goto filetype; + case FILBDEV: + i = S_IFBLK; + goto filetype; + case FILFIFO: +#ifdef S_IFIFO + i = S_IFIFO; + goto filetype; +#else + return 0; +#endif + case FILSOCK: +#ifdef S_IFSOCK + i = S_IFSOCK; + goto filetype; +#else + return 0; +#endif + case FILSUID: + i = S_ISUID; + goto filebit; + case FILSGID: + i = S_ISGID; + goto filebit; + case FILSTCK: + i = S_ISVTX; + goto filebit; + case FILGZ: + return s.st_size > 0L; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } + +filetype: + return ((s.st_mode & S_IFMT) == i); + +filebit: + return ((s.st_mode & i) != 0); +} + +static enum token +t_lex(s) + register char *s; +{ + register struct t_op const *op = ops; + + if (s == 0) { + t_wp_op = (struct t_op *)0; + return EOI; + } + while (op->op_text) { + if (strcmp(s, op->op_text) == 0) { + t_wp_op = op; + return op->op_num; + } + op++; + } + t_wp_op = (struct t_op *)0; + return OPERAND; +} + +/* atoi with error detection */ +static int +getn(s) + char *s; { char *p; long r; errno = 0; - r = strtol(v, &p, 10); + r = strtol(s, &p, 10); + if (errno != 0) - err("\"%s\" -- out of range.", v); + errx(2, "%s: out of range", s); + while (isspace(*p)) - p++; - if (*p != '\0') - err("illegal operand \"%s\" -- expected integer.", v); - return (r); + p++; + + if (*p) + errx(2, "%s: bad number", s); + + return (int) r; } -static void -syntax() +static int +newerf (f1, f2) +char *f1, *f2; { - err("syntax error"); + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_mtime > b2.st_mtime); } -static void -overflow() +static int +olderf (f1, f2) +char *f1, *f2; { - err("expression is too complex"); + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_mtime < b2.st_mtime); } -#if __STDC__ -#include -#else -#include -#endif - -void -#if __STDC__ -err(const char *fmt, ...) -#else -err(fmt, va_alist) - char *fmt; - va_dcl -#endif +static int +equalf (f1, f2) +char *f1, *f2; { - va_list ap; -#if __STDC__ - va_start(ap, fmt); -#else - va_start(ap); -#endif - (void)fprintf(stderr, "test: "); - (void)vfprintf(stderr, fmt, ap); - va_end(ap); - (void)fprintf(stderr, "\n"); - exit(2); - /* NOTREACHED */ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); }