Replaced test with simpler, more feature rich, version derived from pdksh.

This commit is contained in:
jtc 1994-06-30 05:12:29 +00:00
parent 7762f58480
commit 008c3a98a0
3 changed files with 480 additions and 557 deletions

View File

@ -1,14 +1,9 @@
# from: @(#)Makefile 5.1 (Berkeley) 6/8/92 # 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 PROG= test
SRCS= test.c operators.c SRCS= test.c
CFLAGS+=-I.
LINKS= ${BINDIR}/test ${BINDIR}/[ LINKS= ${BINDIR}/test ${BINDIR}/[
MLINKS= test.1 '[.1' MLINKS= test.1 '[.1'
# use this rule to if you update binary_ops, or unary_ops
make_op:
sh ${.CURDIR}/mkops
.include <bsd.prog.mk> .include <bsd.prog.mk>

View File

@ -33,7 +33,7 @@
.\" SUCH DAMAGE. .\" SUCH DAMAGE.
.\" .\"
.\" from: @(#)test.1 6.6 (Berkeley) 6/8/92 .\" 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 .Dd June 8, 1992
.Dt TEST 1 .Dt TEST 1
@ -86,10 +86,10 @@ True if
.Ar file .Ar file
exists and its set group ID flag exists and its set group ID flag
is set. is set.
.It Fl h Ar file .It Fl k Ar file
True if True if
.Ar file .Ar file
exists and is a symbolic link. exists and its sticky bit is set.
.It Fl n Ar string .It Fl n Ar string
True if the length of True if the length of
.Ar string .Ar string
@ -101,7 +101,8 @@ is a named pipe
.Po Tn FIFO Pc . .Po Tn FIFO Pc .
.It Fl r Ar file .It Fl r Ar file
True if True if
.Ar file exists and is readable. .Ar file
exists and is readable.
.It Fl s Ar file .It Fl s Ar file
True if True if
.Ar file .Ar file
@ -140,6 +141,38 @@ can be searched.
True if the length of True if the length of
.Ar string .Ar string
is zero. 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 .It Ar string
True if True if
.Ar string .Ar string
@ -157,6 +190,23 @@ True if the strings
and and
.Ar \&s\&2 .Ar \&s\&2
are not identical. 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 .It Ar \&n\&1 Fl \&eq Ar \&n\&2
True if the integers True if the integers
.Ar \&n\&1 .Ar \&n\&1
@ -224,16 +274,6 @@ The
operator has higher precedence than the operator has higher precedence than the
.Fl o .Fl o
operator. 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 .Sh RETURN VALUES
The The
.Nm test .Nm test
@ -250,6 +290,16 @@ An error occurred.
.Sh STANDARDS .Sh STANDARDS
The The
.Nm test .Nm test
function is expected to be utility implements a superset of the
.St -p1003.2 .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.

View File

@ -1,585 +1,463 @@
/*- /*
* Copyright (c) 1992 The Regents of the University of California. * test(1); version 7-like -- author Erik Baalbergen
* All rights reserved. * 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 * This program is in the Public Domain.
* 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.
*/ */
#ifndef lint #ifndef lint
char copyright[] = static char *rcsid = "$Id: test.c,v 1.13 1994/06/30 05:12:38 jtc Exp $";
"@(#) Copyright (c) 1992 The Regents of the University of California.\n\ #endif
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 */
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h> #include <errno.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <err.h>
#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 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
#define NESTINCR 16 "-nt"|"-ot"|"-ef";
operand ::= <any legal UNIX file name>
*/
/* data types */ enum token {
#define STRING 0 EOI,
#define INTEGER 1 FILRD,
#define BOOLEAN 2 FILWR,
FILEX,
#define IS_BANG(s) (s[0] == '!' && s[1] == '\0') FILEXIST,
FILREG,
/* FILDIR,
* This structure hold a value. The type keyword specifies the type of FILCDEV,
* the value, and the union u holds the value. The value of a boolean FILBDEV,
* is stored in u.num (1 = TRUE, 0 = FALSE). FILFIFO,
*/ FILSOCK,
struct value { FILSYM,
int type; FILGZ,
union { FILTT,
char *string; FILSUID,
long num; FILSGID,
} u; 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 { enum token_types {
short op; /* Which operator. */ UNOP,
short pri; /* Priority of operator. */ BINOP,
BUNOP,
BBINOP,
PAREN
}; };
struct filestat { struct t_op {
char *name; /* Name of file. */ const char *op_text;
int rcode; /* Return code from stat. */ short op_num, op_type;
struct stat stat; /* Status info on file. */ } 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 *, ...)); char **t_wp;
static int expr_is_false __P((struct value *)); struct t_op const *t_wp_op;
static void expr_operator __P((int, struct value *, struct filestat *));
static long chk_atol __P((char *)); static enum token t_lex();
static int lookup_op __P((char *, char *const *)); static int oexpr();
static void overflow __P((void)); static int aexpr();
static int posix_binary_op __P((char **)); static int nexpr();
static int posix_unary_op __P((char **)); static int primary();
static void syntax __P((void)); static int filstat();
static int getn();
static int newerf();
static int olderf();
static int equalf();
static void syntax();
int int
main(argc, argv) main(argc, argv)
int argc; 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; char **argv;
{ {
struct filestat fs; int res;
struct value valp;
int op, c;
char *opname;
opname = *argv; t_wp = argv+1;
if ((op = lookup_op(opname, unary_op)) < 0) if (strcmp(argv[0], "[") == 0) {
return (-1); if (strcmp(argv[--argc], "]"))
c = op_argflag[op]; errx(2, "missing ]");
opname = argv[1]; argv[argc] = NULL;
valp.u.string = opname; }
if (c == OP_FILE) {
fs.name = opname;
fs.rcode = stat(opname, &fs.stat);
} else if (c != OP_STRING)
return (-1);
expr_operator(op, &valp, &fs); /* Implement special cases from POSIX.2, section 4.62.4 */
return (valp.u.num == 0); 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 static int
posix_binary_op(argv) oexpr(n)
char **argv; enum token n;
{ {
struct value v[2]; int res;
int op, c;
char *opname;
opname = argv[1]; res = aexpr(n);
if ((op = lookup_op(opname, binary_op)) < 0) if (t_lex(*++t_wp) == BOR)
return (-1); return oexpr(t_lex(*++t_wp)) || res;
op += FIRST_BINARY_OP; t_wp--;
c = op_argflag[op]; return res;
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);
} }
/* static int
* Integer type checking. aexpr(n)
*/ enum token n;
static long {
chk_atol(v) int res;
char *v;
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; char *p;
long r; long r;
errno = 0; errno = 0;
r = strtol(v, &p, 10); r = strtol(s, &p, 10);
if (errno != 0) if (errno != 0)
err("\"%s\" -- out of range.", v); errx(2, "%s: out of range", s);
while (isspace(*p)) while (isspace(*p))
p++; p++;
if (*p != '\0')
err("illegal operand \"%s\" -- expected integer.", v); if (*p)
return (r); errx(2, "%s: bad number", s);
return (int) r;
} }
static void static int
syntax() 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 static int
overflow() 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__ static int
#include <stdarg.h> equalf (f1, f2)
#else char *f1, *f2;
#include <varargs.h>
#endif
void
#if __STDC__
err(const char *fmt, ...)
#else
err(fmt, va_alist)
char *fmt;
va_dcl
#endif
{ {
va_list ap; struct stat b1, b2;
#if __STDC__
va_start(ap, fmt); return (stat (f1, &b1) == 0 &&
#else stat (f2, &b2) == 0 &&
va_start(ap); b1.st_dev == b2.st_dev &&
#endif b1.st_ino == b2.st_ino);
(void)fprintf(stderr, "test: ");
(void)vfprintf(stderr, fmt, ap);
va_end(ap);
(void)fprintf(stderr, "\n");
exit(2);
/* NOTREACHED */
} }