477 lines
11 KiB
C
477 lines
11 KiB
C
/*
|
|
* The expr and test commands.
|
|
*
|
|
* Copyright (C) 1989 by Kenneth Almquist. All rights reserved.
|
|
* This file is part of ash, which is distributed under the terms specified
|
|
* by the Ash General Public License. See the file named LICENSE.
|
|
*/
|
|
|
|
|
|
#include "bltin.h"
|
|
#include "operators.h"
|
|
#include <regex.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
|
|
#define STACKSIZE 12
|
|
#define NESTINCR 16
|
|
|
|
/* data types */
|
|
#define STRING 0
|
|
#define INTEGER 1
|
|
#define BOOLEAN 2
|
|
|
|
|
|
/*
|
|
* 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;
|
|
};
|
|
|
|
|
|
struct operator {
|
|
short op; /* which operator */
|
|
short pri; /* priority of operator */
|
|
};
|
|
|
|
|
|
struct filestat {
|
|
char *name; /* name of file */
|
|
int rcode; /* return code from stat */
|
|
struct stat stat; /* status info on file */
|
|
};
|
|
|
|
|
|
extern char *match_begin[10]; /* matched string */
|
|
extern short match_length[10]; /* defined in regexp.c */
|
|
extern short number_parens; /* number of \( \) pairs */
|
|
|
|
|
|
#ifdef __STDC__
|
|
static int expr_is_false(struct value *);
|
|
static void expr_operator(int, struct value *, struct filestat *);
|
|
static int lookup_op(char *, char *const*);
|
|
#else
|
|
static int expr_is_false();
|
|
static void expr_operator();
|
|
static int lookup_op();
|
|
#endif
|
|
|
|
|
|
|
|
exprcmd(argc, argv) char **argv; {
|
|
char **ap;
|
|
char *opname;
|
|
char c;
|
|
char *p;
|
|
int print;
|
|
int nest; /* parenthises nesting */
|
|
int op;
|
|
int pri;
|
|
int skipping;
|
|
int binary;
|
|
struct operator opstack[STACKSIZE];
|
|
struct operator *opsp;
|
|
struct value valstack[STACKSIZE + 1];
|
|
struct value *valsp;
|
|
struct filestat fs;
|
|
|
|
INITARGS(argv);
|
|
c = **argv;
|
|
print = 1;
|
|
if (c == 't')
|
|
print = 0;
|
|
else if (c == '[') {
|
|
if (! equal(argv[argc - 1], "]"))
|
|
error("missing ]");
|
|
argv[argc - 1] = NULL;
|
|
print = 0;
|
|
}
|
|
ap = argv + 1;
|
|
fs.name = NULL;
|
|
|
|
/*
|
|
* 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.)
|
|
*/
|
|
|
|
nest = 0;
|
|
skipping = 0;
|
|
opsp = opstack + STACKSIZE;
|
|
valsp = valstack;
|
|
if (*ap == NULL) {
|
|
valstack[0].type = BOOLEAN;
|
|
valstack[0].u.num = 0;
|
|
goto done;
|
|
}
|
|
for (;;) {
|
|
opname = *ap++;
|
|
if (opname == NULL)
|
|
syntax: error("syntax error");
|
|
if (opname[0] == '(' && opname[1] == '\0') {
|
|
nest += NESTINCR;
|
|
continue;
|
|
} else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
|
|
if (opsp == &opstack[0])
|
|
overflow: error("Expression too complex");
|
|
--opsp;
|
|
opsp->op = op;
|
|
opsp->pri = op_priority[op] + nest;
|
|
continue;
|
|
|
|
} else {
|
|
if (opname[0] == '\'') {
|
|
for (p = opname ; *++p != '\0' ; );
|
|
if (--p > opname && *p == '\'') {
|
|
*p = '\0';
|
|
opname++;
|
|
}
|
|
}
|
|
valsp->type = STRING;
|
|
valsp->u.string = opname;
|
|
valsp++;
|
|
}
|
|
for (;;) {
|
|
opname = *ap++;
|
|
if (opname == NULL) {
|
|
if (nest != 0)
|
|
goto syntax;
|
|
pri = 0;
|
|
break;
|
|
}
|
|
if (opname[0] != ')' || opname[1] != '\0') {
|
|
if ((op = lookup_op(opname, binary_op)) < 0)
|
|
goto syntax;
|
|
op += FIRST_BINARY_OP;
|
|
pri = op_priority[op] + nest;
|
|
break;
|
|
}
|
|
if ((nest -= NESTINCR) < 0)
|
|
goto 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 = atol(valsp->u.string);
|
|
valsp->type = INTEGER;
|
|
} else if (c >= OP_STRING) { /* OP_STRING or OP_FILE */
|
|
if (valsp->type == INTEGER) {
|
|
p = stalloc(32);
|
|
#ifdef SHELL
|
|
fmtstr(p, 32, "%d", valsp->u.num);
|
|
#else
|
|
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
|
|
|| ! equal(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])
|
|
goto 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:
|
|
if (print) {
|
|
if (valstack[0].type == STRING)
|
|
printf("%s\n", valstack[0].u.string);
|
|
else if (valstack[0].type == INTEGER)
|
|
printf("%ld\n", valstack[0].u.num);
|
|
else if (valstack[0].u.num != 0)
|
|
printf("true\n");
|
|
}
|
|
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, r;
|
|
struct stat st1, st2;
|
|
regex_t pat;
|
|
regmatch_t rm[2];
|
|
|
|
switch (op) {
|
|
case NOT:
|
|
sp->u.num = expr_is_false(sp);
|
|
sp->type = BOOLEAN;
|
|
break;
|
|
case EXISTS:
|
|
if (fs->rcode >= 0) goto true;
|
|
goto false;
|
|
case ISREAD:
|
|
i = 04;
|
|
goto permission;
|
|
case ISWRITE:
|
|
i = 02;
|
|
goto permission;
|
|
case ISEXEC:
|
|
i = 01;
|
|
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:
|
|
#ifdef S_IFIFO
|
|
i = S_IFIFO;
|
|
goto filetype;
|
|
#else
|
|
goto false;
|
|
#endif
|
|
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 ISSLINK:
|
|
if (lstat(fs->name, &st1) == -1)
|
|
goto false;
|
|
if (S_ISLNK(st1.st_mode))
|
|
goto true;
|
|
goto false;
|
|
case ISSIZE:
|
|
sp->u.num = fs->rcode >= 0? fs->stat.st_size : 0L;
|
|
sp->type = INTEGER;
|
|
break;
|
|
case NEWER:
|
|
if (stat(sp->u.string, &st1) != 0) {
|
|
sp->u.num = 0;
|
|
} else if (stat((sp + 1)->u.string, &st2) != 0) {
|
|
sp->u.num = 1;
|
|
} else {
|
|
sp->u.num = st1.st_mtime >= st2.st_mtime;
|
|
}
|
|
sp->type = INTEGER;
|
|
break;
|
|
case ISTTY:
|
|
sp->u.num = isatty(sp->u.num);
|
|
sp->type = BOOLEAN;
|
|
break;
|
|
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 (equal(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;
|
|
case PLUS:
|
|
sp->u.num += (sp + 1)->u.num;
|
|
break;
|
|
case MINUS:
|
|
sp->u.num -= (sp + 1)->u.num;
|
|
break;
|
|
case TIMES:
|
|
sp->u.num *= (sp + 1)->u.num;
|
|
break;
|
|
case DIVIDE:
|
|
if ((sp + 1)->u.num == 0)
|
|
error("Division by zero");
|
|
sp->u.num /= (sp + 1)->u.num;
|
|
break;
|
|
case REM:
|
|
if ((sp + 1)->u.num == 0)
|
|
error("Division by zero");
|
|
sp->u.num %= (sp + 1)->u.num;
|
|
break;
|
|
case MATCHPAT:
|
|
{
|
|
r = regcomp(&pat, (sp + 1)->u.string, 0);
|
|
if (r)
|
|
error("Bad regular expression");
|
|
if (regexec(&pat, sp->u.string, 2, rm, 0) == 0 &&
|
|
rm[0].rm_so == 0)
|
|
{
|
|
if (pat.re_nsub > 0) {
|
|
sp->u.string[rm[1].rm_eo] = '\0';
|
|
sp->u.string = sp->u.string+rm[1].rm_so;
|
|
} else {
|
|
sp->u.num = rm[0].rm_eo;
|
|
sp->type = INTEGER;
|
|
}
|
|
} else {
|
|
if (pat.re_nsub > 0) {
|
|
sp->u.string[0] = '\0';
|
|
} else {
|
|
sp->u.num = 0;
|
|
sp->type = INTEGER;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
lookup_op(name, table)
|
|
char *name;
|
|
char *const*table;
|
|
{
|
|
register char *const*tp;
|
|
register char const *p;
|
|
char c = name[1];
|
|
|
|
for (tp = table ; (p = *tp) != NULL ; tp++) {
|
|
if (p[1] == c && equal(p, name))
|
|
return tp - table;
|
|
}
|
|
return -1;
|
|
}
|