Source-Changes-HG archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
[src/trunk]: src/bin/test PR standards/34646
details: https://anonhg.NetBSD.org/src/rev/43a4f7559551
branches: trunk
changeset: 433402:43a4f7559551
user: kre <kre%NetBSD.org@localhost>
date: Wed Sep 12 23:33:31 2018 +0000
description:
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
diffstat:
bin/test/test.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 174 insertions(+), 19 deletions(-)
diffs (277 lines):
diff -r 6c50cb4a0690 -r 43a4f7559551 bin/test/test.c
--- a/bin/test/test.c Wed Sep 12 22:10:35 2018 +0000
+++ b/bin/test/test.c Wed Sep 12 23:33:31 2018 +0000
@@ -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 @@
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 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 @@
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 @@
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 @@
/* 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) {
@@ -325,6 +458,21 @@
}
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)
{
const char *opnd1, *opnd2;
@@ -337,7 +485,13 @@
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 @@
}
static int
-filstat(char *nm, enum token mode)
+filstat(const char *nm, enum token mode)
{
struct stat s;
@@ -676,11 +830,12 @@
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;
}
Home |
Main Index |
Thread Index |
Old Index