From owner-freebsd-standards@FreeBSD.ORG Sat Apr 4 14:00:03 2009 Return-Path: Delivered-To: freebsd-standards@hub.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 9B4C31065672 for ; Sat, 4 Apr 2009 14:00:03 +0000 (UTC) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (freefall.freebsd.org [IPv6:2001:4f8:fff6::28]) by mx1.freebsd.org (Postfix) with ESMTP id 74EE38FC0C for ; Sat, 4 Apr 2009 14:00:03 +0000 (UTC) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (gnats@localhost [127.0.0.1]) by freefall.freebsd.org (8.14.3/8.14.3) with ESMTP id n34E03gS021833 for ; Sat, 4 Apr 2009 14:00:03 GMT (envelope-from gnats@freefall.freebsd.org) Received: (from gnats@localhost) by freefall.freebsd.org (8.14.3/8.14.3/Submit) id n34E03S3021832; Sat, 4 Apr 2009 14:00:03 GMT (envelope-from gnats) Resent-Date: Sat, 4 Apr 2009 14:00:03 GMT Resent-Message-Id: <200904041400.n34E03S3021832@freefall.freebsd.org> Resent-From: FreeBSD-gnats-submit@FreeBSD.org (GNATS Filer) Resent-To: freebsd-standards@FreeBSD.org Resent-Reply-To: FreeBSD-gnats-submit@FreeBSD.org, Jilles Tjoelker Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 3AE3C106566B for ; Sat, 4 Apr 2009 13:50:46 +0000 (UTC) (envelope-from jilles@stack.nl) Received: from mx1.stack.nl (unknown [IPv6:2001:610:1108:5000::149]) by mx1.freebsd.org (Postfix) with ESMTP id DDB7C8FC18 for ; Sat, 4 Apr 2009 13:50:45 +0000 (UTC) (envelope-from jilles@stack.nl) Received: by mx1.stack.nl (Postfix, from userid 65534) id 3F5673F7BA; Sat, 4 Apr 2009 15:50:45 +0200 (CEST) Received: from snail.stack.nl (snail.stack.nl [IPv6:2001:610:1108:5010::131]) by mx1.stack.nl (Postfix) with ESMTP id D906A3F5E6 for ; Sat, 4 Apr 2009 15:50:35 +0200 (CEST) Received: by snail.stack.nl (Postfix, from userid 1677) id 3C6E5228A2; Sat, 4 Apr 2009 15:50:23 +0200 (CEST) Message-Id: <20090404135023.3C6E5228A2@snail.stack.nl> Date: Sat, 4 Apr 2009 15:50:23 +0200 (CEST) From: Jilles Tjoelker To: FreeBSD-gnats-submit@FreeBSD.org X-Send-Pr-Version: 3.113 Cc: Subject: standards/133369: test(1) with 3 or 4 arguments X-BeenThere: freebsd-standards@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list Reply-To: Jilles Tjoelker List-Id: Standards compliance List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 04 Apr 2009 14:00:04 -0000 >Number: 133369 >Category: standards >Synopsis: test(1) with 3 or 4 arguments >Confidential: no >Severity: serious >Priority: medium >Responsible: freebsd-standards >State: open >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Sat Apr 04 14:00:03 UTC 2009 >Closed-Date: >Last-Modified: >Originator: Jilles Tjoelker >Release: FreeBSD 7.2-PRERELEASE i386 >Organization: MCGV Stack >Environment: >Description: test(1) does not follow the rules in POSIX exactly if there are 3 or 4 arguments, different from what the man page says. In particular, different from what POSIX prescribes, test "$a" = "$b" does not work properly for all values of $a and $b. /bin/test and the test builtin in /bin/sh use the same code. >How-To-Repeat: An example, more tests are included in the patch. Input: /bin/test \( = \); echo $? Expected result: 1 Actual result: 0 According to POSIX, the rule about the second argument being a binary operator has priority above the rule about the first argument being '(' and the third argument being ')'. >Fix: Apply this patch. It includes additional test cases in TEST.sh. --- test-posixfixes.patch begins here --- --- src/bin/test/test.c.orig 2005-01-10 09:39:26.000000000 +0100 +++ src/bin/test/test.c 2009-04-02 02:28:28.000000000 +0200 @@ -163,6 +163,7 @@ struct t_op const *t_wp_op; int nargc; char **t_wp; +int parenlevel; static int aexpr(enum token); static int binop(void); @@ -171,7 +172,9 @@ static int getn(const char *); static intmax_t getq(const char *); static int intcmp(const char *, const char *); -static int isoperand(void); +static int isunopoperand(void); +static int islparenoperand(void); +static int isrparenoperand(void); static int newerf(const char *, const char *); static int nexpr(enum token); static int oexpr(enum token); @@ -205,7 +208,14 @@ #endif nargc = argc; t_wp = &argv[1]; - res = !oexpr(t_lex(*t_wp)); + parenlevel = 0; + if (nargc == 4 && strcmp(*t_wp, "!") == 0) { + /* Things like ! "" -o x do not fit in the normal grammar. */ + --nargc; + ++t_wp; + res = oexpr(t_lex(*t_wp)); + } else + res = !oexpr(t_lex(*t_wp)); if (--nargc > 0) syntax(*t_wp, "unexpected operator"); @@ -268,12 +278,16 @@ if (n == EOI) return 0; /* missing expression */ if (n == LPAREN) { + parenlevel++; if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == - RPAREN) + RPAREN) { + parenlevel--; return 0; /* missing expression */ + } res = oexpr(nn); if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) syntax(NULL, "closing paren expected"); + parenlevel--; return res; } if (t_wp_op && t_wp_op->op_type == UNOP) { @@ -410,8 +424,10 @@ } while (op->op_text) { if (strcmp(s, op->op_text) == 0) { - if ((op->op_type == UNOP && isoperand()) || - (op->op_num == LPAREN && nargc == 1)) + if (((op->op_type == UNOP || op->op_type == BUNOP) + && isunopoperand()) || + (op->op_num == LPAREN && islparenoperand()) || + (op->op_num == RPAREN && isrparenoperand())) break; t_wp_op = op; return op->op_num; @@ -423,7 +439,7 @@ } static int -isoperand(void) +isunopoperand(void) { struct t_op const *op = ops; char *s; @@ -431,19 +447,53 @@ if (nargc == 1) return 1; - if (nargc == 2) - return 0; s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; t = *(t_wp + 2); while (op->op_text) { if (strcmp(s, op->op_text) == 0) return op->op_type == BINOP && - (t[0] != ')' || t[1] != '\0'); + (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); + op++; + } + return 0; +} + +static int +islparenoperand(void) +{ + struct t_op const *op = ops; + char *s; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + if (nargc != 3) + return 0; + while (op->op_text) { + if (strcmp(s, op->op_text) == 0) + return op->op_type == BINOP; op++; } return 0; } +static int +isrparenoperand(void) +{ + char *s; + + if (nargc == 1) + return 0; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + return 0; +} + /* atoi with error detection */ static int getn(const char *s) --- src/bin/test/TEST.sh.orig 2005-01-10 09:39:26.000000000 +0100 +++ src/bin/test/TEST.sh 2009-04-03 22:53:28.000000000 +0200 @@ -133,5 +133,35 @@ t 1 '""' t 0 '! ""' +t 0 '!' +t 0 '\(' +t 0 '\)' + +t 1 '\( = \)' +t 0 '\( != \)' +t 0 '\( ! \)' +t 0 '\( \( \)' +t 0 '\( \) \)' +t 0 '! = !' +t 1 '! != !' +t 1 '-n = \)' +t 0 '! != \)' +t 1 '! = a' +t 0 '! != -n' +t 0 '! -c /etc/passwd' + +t 0 '! \( = \)' +t 1 '! \( != \)' +t 1 '! = = =' +t 0 '! = = \)' +t 0 '! "" -o ""' +t 1 '! "x" -o ""' +t 1 '! "" -o "x"' +t 1 '! "x" -o "x"' +t 0 '\( -f /etc/passwd \)' +t 1 '\( ! = \)' +t 0 '\( ! "" \)' +t 1 '\( ! -e \)' + echo "" echo "Syntax errors: $ERROR Failed: $FAILED" --- test-posixfixes.patch ends here --- >Release-Note: >Audit-Trail: >Unformatted: