PR standards/34646
Make test(1) always use the POSIX "number of args" evaluation rules when they apply. Only fall back to the old expression evaluation when there are more than 4 args, or when the args given cannot work as a test expression using the POSIX rules. That is when the result is unspecified. Also fix old bug where a string of whitespace is considered to be a valid number (at least one digit is needed amongst it somewhere...) XXX pullup -8
This commit is contained in:
parent
6da79ee096
commit
ec9262987a
193
bin/test/test.c
193
bin/test/test.c
@ -1,4 +1,4 @@
|
||||
/* $NetBSD: test.c,v 1.41 2016/09/05 01:00:07 sevan Exp $ */
|
||||
/* $NetBSD: test.c,v 1.42 2018/09/12 23:33:31 kre Exp $ */
|
||||
|
||||
/*
|
||||
* test(1); version 7-like -- author Erik Baalbergen
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#ifndef lint
|
||||
__RCSID("$NetBSD: test.c,v 1.41 2016/09/05 01:00:07 sevan Exp $");
|
||||
__RCSID("$NetBSD: test.c,v 1.42 2018/09/12 23:33:31 kre Exp $");
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
@ -160,10 +160,13 @@ __dead static void syntax(const char *, const char *);
|
||||
static int oexpr(enum token);
|
||||
static int aexpr(enum token);
|
||||
static int nexpr(enum token);
|
||||
static struct t_op const *findop(const char *);
|
||||
static int primary(enum token);
|
||||
static int binop(void);
|
||||
static int perform_unop(enum token, const char *);
|
||||
static int perform_binop(enum token, const char *, const char *);
|
||||
static int test_access(struct stat *, mode_t);
|
||||
static int filstat(char *, enum token);
|
||||
static int filstat(const char *, enum token);
|
||||
static enum token t_lex(char *);
|
||||
static int isoperand(void);
|
||||
static long long getn(const char *);
|
||||
@ -171,6 +174,11 @@ static int newerf(const char *, const char *);
|
||||
static int olderf(const char *, const char *);
|
||||
static int equalf(const char *, const char *);
|
||||
|
||||
static int one_arg(const char *);
|
||||
static int two_arg(const char *, const char *);
|
||||
static int three_arg(const char *, const char *, const char *);
|
||||
static int four_arg(const char *, const char *, const char *, const char *);
|
||||
|
||||
#if defined(SHELL)
|
||||
extern void error(const char *, ...) __dead __printflike(1, 2);
|
||||
extern void *ckmalloc(size_t);
|
||||
@ -226,9 +234,64 @@ main(int argc, char *argv[])
|
||||
argv[argc] = NULL;
|
||||
}
|
||||
|
||||
if (argc < 2)
|
||||
/*
|
||||
* POSIX defines operations of test for up to 4 args
|
||||
* (depending upon what the args are in some cases)
|
||||
*
|
||||
* arg count does not include the command name, (but argc does)
|
||||
* nor the closing ']' when the command was '[' (removed above)
|
||||
*
|
||||
* None of the following allow -a or -o as an operator (those
|
||||
* only apply in the evaluation of unspeicified expressions)
|
||||
*
|
||||
* Note that the xxx_arg() functions return "shell" true/false
|
||||
* (0 == true, 1 == false) or -1 for "unspecified case"
|
||||
*
|
||||
* Other functions return C true/false (1 == true, 0 == false)
|
||||
*
|
||||
* Hence we simply return the result from xxx_arg(), but
|
||||
* invert the result of oexpr() below before returning it.
|
||||
*/
|
||||
switch (argc - 1) {
|
||||
case -1: /* impossible, but never mind */
|
||||
case 0: /* test $a where a='' false */
|
||||
return 1;
|
||||
|
||||
case 1: /* test "$a" */
|
||||
return one_arg(argv[1]); /* always works */
|
||||
|
||||
case 2: /* test op "$a" */
|
||||
res = two_arg(argv[1], argv[2]);
|
||||
if (res >= 0)
|
||||
return res;
|
||||
break;
|
||||
|
||||
case 3: /* test "$a" op "$b" or test ! op "$a" */
|
||||
res = three_arg(argv[1], argv[2], argv[3]);
|
||||
if (res >= 0)
|
||||
return res;
|
||||
break;
|
||||
|
||||
case 4: /* test ! "$a" op "$b" or test ( op "$a" ) */
|
||||
res = four_arg(argv[1], argv[2], argv[3], argv[4]);
|
||||
if (res >= 0)
|
||||
return res;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* All other cases produce unspecified results
|
||||
* (including cases above with small arg counts where the
|
||||
* args are not what was expected to be seen)
|
||||
*
|
||||
* We fall back to the old method, of attempting to parse
|
||||
* the expr (highly ambiguous as there is no distinction between
|
||||
* operators and operands that happen to look like operators)
|
||||
*/
|
||||
|
||||
t_wp = &argv[1];
|
||||
res = !oexpr(t_lex(*t_wp));
|
||||
|
||||
@ -246,6 +309,85 @@ syntax(const char *op, const char *msg)
|
||||
error("%s: %s", op, msg);
|
||||
else
|
||||
error("%s", msg);
|
||||
}
|
||||
|
||||
static int
|
||||
one_arg(const char *arg)
|
||||
{
|
||||
/*
|
||||
* True (exit 0, so false...) if arg is not a null string
|
||||
* False (so exit 1, so true) if it is.
|
||||
*/
|
||||
return *arg == '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
two_arg(const char *a1, const char *a2)
|
||||
{
|
||||
static struct t_op const *op;
|
||||
|
||||
if (a1[0] == '!' && a1[1] == 0)
|
||||
return !one_arg(a2);
|
||||
|
||||
op = findop(a1);
|
||||
if (op != NULL && op->op_type == UNOP)
|
||||
return !perform_unop(op->op_num, a2);
|
||||
|
||||
/*
|
||||
* an extension, but as we've entered the realm of the unspecified
|
||||
* we're allowed... test ( $a ) where a=''
|
||||
*/
|
||||
if (a1[0] == '(' && a2[0] == ')' && (a1[1] | a2[1]) == 0)
|
||||
return 1;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
three_arg(const char *a1, const char *a2, const char *a3)
|
||||
{
|
||||
static struct t_op const *op;
|
||||
int res;
|
||||
|
||||
op = findop(a2);
|
||||
if (op != NULL && op->op_type == BINOP)
|
||||
return !perform_binop(op->op_num, a1, a3);
|
||||
|
||||
if (a1[1] != '\0')
|
||||
return -1;
|
||||
|
||||
if (a1[0] == '!') {
|
||||
res = two_arg(a2, a3);
|
||||
if (res >= 0)
|
||||
res = !res;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (a1[0] == '(' && a3[0] == ')' && a3[1] == '\0')
|
||||
return one_arg(a2);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
four_arg(const char *a1, const char *a2, const char *a3, const char *a4)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (a1[1] != '\0')
|
||||
return -1;
|
||||
|
||||
if (a1[0] == '!') {
|
||||
res = three_arg(a2, a3, a4);
|
||||
if (res >= 0)
|
||||
res = !res;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (a1[0] == '(' && a4[0] == ')' && a4[1] == '\0')
|
||||
return two_arg(a2, a3);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -305,16 +447,7 @@ primary(enum token n)
|
||||
/* 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((int)getn(*t_wp));
|
||||
default:
|
||||
return filstat(*t_wp, n);
|
||||
}
|
||||
return perform_unop(n, *t_wp);
|
||||
}
|
||||
|
||||
if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
|
||||
@ -324,6 +457,21 @@ primary(enum token n)
|
||||
return strlen(*t_wp) > 0;
|
||||
}
|
||||
|
||||
static int
|
||||
perform_unop(enum token n, const char *opnd)
|
||||
{
|
||||
switch (n) {
|
||||
case STREZ:
|
||||
return strlen(opnd) == 0;
|
||||
case STRNZ:
|
||||
return strlen(opnd) != 0;
|
||||
case FILTT:
|
||||
return isatty((int)getn(opnd));
|
||||
default:
|
||||
return filstat(opnd, n);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
binop(void)
|
||||
{
|
||||
@ -337,7 +485,13 @@ binop(void)
|
||||
if ((opnd2 = *++t_wp) == NULL)
|
||||
syntax(op->op_text, "argument expected");
|
||||
|
||||
switch (op->op_num) {
|
||||
return perform_binop(op->op_num, opnd1, opnd2);
|
||||
}
|
||||
|
||||
static int
|
||||
perform_binop(enum token op_num, const char *opnd1, const char *opnd2)
|
||||
{
|
||||
switch (op_num) {
|
||||
case STREQ:
|
||||
return strcmp(opnd1, opnd2) == 0;
|
||||
case STRNE:
|
||||
@ -533,7 +687,7 @@ test_access(struct stat *sp, mode_t stmode)
|
||||
}
|
||||
|
||||
static int
|
||||
filstat(char *nm, enum token mode)
|
||||
filstat(const char *nm, enum token mode)
|
||||
{
|
||||
struct stat s;
|
||||
|
||||
@ -676,11 +830,12 @@ getn(const char *s)
|
||||
if (errno == ERANGE && (r == LLONG_MAX || r == LLONG_MIN))
|
||||
error("%s: out of range", s);
|
||||
|
||||
while (isspace((unsigned char)*p))
|
||||
p++;
|
||||
if (p != s)
|
||||
while (isspace((unsigned char)*p))
|
||||
p++;
|
||||
|
||||
if (*p || p == s)
|
||||
error("%s: bad number", s);
|
||||
error("'%s': bad number", s);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user