]> Zhao Yanbai Git Server - minix.git/commitdiff
Upgrading test 75/275/2
authorLionel Sambuc <lionel@minix3.org>
Wed, 30 Jan 2013 14:53:50 +0000 (15:53 +0100)
committerLionel Sambuc <lionel@minix3.org>
Fri, 1 Feb 2013 10:41:47 +0000 (11:41 +0100)
Change-Id: I1cd8e24475030989c95ba60b4e7462dacf945b9f

bin/Makefile
bin/test/Makefile [new file with mode: 0644]
bin/test/TEST.csh [new file with mode: 0644]
bin/test/test.1 [new file with mode: 0644]
bin/test/test.c [new file with mode: 0644]
releasetools/nbsd_ports

index 23adf7694b6d1482ad4c08a9d60b5a4add4ba87a..01654c543c7688988d3d9f0a35434e8ac6b194e7 100644 (file)
@@ -2,6 +2,6 @@
 #      @(#)Makefile    8.1 (Berkeley) 5/31/93
 
 SUBDIR=        cat date ed \
-       mkdir pax rm rmdir
+       mkdir pax rm rmdir test
 
 .include <bsd.subdir.mk>
diff --git a/bin/test/Makefile b/bin/test/Makefile
new file mode 100644 (file)
index 0000000..315b6c8
--- /dev/null
@@ -0,0 +1,9 @@
+#      $NetBSD: Makefile,v 1.11 2007/06/22 03:24:16 simonb Exp $
+#      @(#)Makefile    8.1 (Berkeley) 5/31/93
+
+PROG=  test
+SRCS=   test.c
+LINKS= ${BINDIR}/test ${BINDIR}/[
+MLINKS=        test.1 [.1
+
+.include <bsd.prog.mk>
diff --git a/bin/test/TEST.csh b/bin/test/TEST.csh
new file mode 100644 (file)
index 0000000..672e1a0
--- /dev/null
@@ -0,0 +1,138 @@
+#      $NetBSD: TEST.csh,v 1.2 1995/03/21 07:03:59 cgd Exp $
+#      @(#)TEST.csh    5.2 (Berkeley) 4/30/93
+
+#alias t '/usr/src/bin/test/obj/test \!*; echo $status'
+alias t '/bin/test \!*; echo $status'
+
+echo 't -b /dev/ttyp2'
+t -b /dev/ttyp2
+echo 't -b /dev/jb1a'
+t -b /dev/jb1a
+
+echo 't -c test.c'
+t -c test.c
+echo 't -c /dev/tty'
+t -c /dev/tty
+
+echo 't -d test.c'
+t -d test.c
+echo 't -d /etc'
+t -d /etc
+
+echo 't -e noexist'
+t -e noexist
+echo 't -e test.c'
+t -e test.c
+
+echo 't -f noexist'
+t -f noexist
+echo 't -f /dev/tty'
+t -f /dev/tty
+echo 't -f test.c'
+t -f test.c
+
+echo 't -g test.c'
+t -g test.c
+echo 't -g /bin/ps'
+t -g /bin/ps
+
+echo 't -n ""'
+t -n ""
+echo 't -n "hello"'
+t -n "hello"
+
+echo 't -p test.c'
+t -p test.c
+
+echo 't -r noexist'
+t -r noexist
+echo 't -r /etc/master.passwd'
+t -r /etc/master.passwd
+echo 't -r test.c'
+t -r test.c
+
+echo 't -s noexist'
+t -s noexist
+echo 't -s /dev/null'
+t -s /dev/null
+echo 't -s test.c'
+t -s test.c
+
+echo 't -t 20'
+t -t 20
+echo 't -t 0'
+t -t 0
+
+echo 't -u test.c'
+t -u test.c
+echo 't -u /bin/rcp'
+t -u /bin/rcp
+
+echo 't -w noexist'
+t -w noexist
+echo 't -w /etc/master.passwd'
+t -w /etc/master.passwd
+echo 't -w /dev/null'
+t -w /dev/null
+
+echo 't -x noexist'
+t -x noexist
+echo 't -x /bin/ps'
+t -x /bin/ps
+echo 't -x /etc/motd'
+t -x /etc/motd
+
+echo 't -z ""'
+t -z ""
+echo 't -z "foo"'
+t -z "foo"
+
+echo 't "foo"'
+t "foo"
+echo 't ""'
+t ""
+
+echo 't "hello" = "hello"'
+t "hello" = "hello"
+echo 't "hello" = "goodbye"'
+t "hello" = "goodbye"
+
+echo 't "hello" != "hello"'
+t "hello" != "hello"
+echo 't "hello" != "goodbye"'
+t "hello" != "goodbye"
+
+echo 't 200 -eq 200'
+t 200 -eq 200
+echo 't 34 -eq 222'
+t 34 -eq 222
+
+echo 't 200 -ne 200'
+t 200 -ne 200
+echo 't 34 -ne 222'
+t 34 -ne 222
+
+echo 't 200 -gt 200'
+t 200 -gt 200
+echo 't 340 -gt 222'
+t 340 -gt 222
+
+echo 't 200 -ge 200'
+t 200 -ge 200
+echo 't 34 -ge 222'
+t 34 -ge 222
+
+echo 't 200 -lt 200'
+t 200 -lt 200
+echo 't 34 -lt 222'
+t 34 -lt 222
+
+echo 't 200 -le 200'
+t 200 -le 200
+echo 't 340 -le 222'
+t 340 -le 222
+
+echo 't 700 -le 1000 -a -n "1" -a "20" = "20"'
+t 700 -le 1000 -a -n "1" -a "20" = "20"
+echo 't ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)'
+t ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)
diff --git a/bin/test/test.1 b/bin/test/test.1
new file mode 100644 (file)
index 0000000..091b7c6
--- /dev/null
@@ -0,0 +1,325 @@
+.\"    $NetBSD: test.1,v 1.27 2009/11/10 18:19:46 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\"    The Regents of the University of California.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" 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. 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.
+.\"
+.\"     @(#)test.1     8.1 (Berkeley) 5/31/93
+.\"
+.Dd November 10, 2009
+.Dt TEST 1
+.Os
+.Sh NAME
+.Nm test ,
+.Nm \&[
+.Nd condition evaluation utility
+.Sh SYNOPSIS
+.Nm test
+.Ar expression
+.Nm \&[
+.Ar expression Cm \&]
+.Sh DESCRIPTION
+The
+.Nm test
+utility evaluates
+.Ar expression
+and, if it evaluates
+to true, returns a zero (true) exit status; otherwise
+it returns 1 (false).
+If
+.Ar expression
+is not given,
+.Nm test
+also
+returns 1 (false).
+.Pp
+All operators and flags are separate arguments to the
+.Nm test
+utility.
+.Pp
+The following primaries are used to construct
+.Ar expression :
+.Bl -tag -width Ar
+.It Fl b Ar file
+True if
+.Ar file
+exists and is a block special
+file.
+.It Fl c Ar file
+True if
+.Ar file
+exists and is a character
+special file.
+.It Fl d Ar file
+True if
+.Ar file
+exists and is a directory.
+.It Fl e Ar file
+True if
+.Ar file
+exists (regardless of type).
+.It Fl f Ar file
+True if
+.Ar file
+exists and is a regular file.
+.It Fl g Ar file
+True if
+.Ar file
+exists and its set group ID flag
+is set.
+.It Fl h Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+.It Fl k Ar file
+True if
+.Ar file
+exists and its sticky bit is set.
+.It Fl n Ar string
+True if the length of
+.Ar string
+is nonzero.
+.It Fl p Ar file
+True if
+.Ar file
+exists and is a named pipe
+.Po Tn FIFO Pc .
+.It Fl r Ar file
+True if
+.Ar file
+exists and is readable.
+.It Fl s Ar file
+True if
+.Ar file
+exists and has a size greater
+than zero.
+.It Fl t Ar file_descriptor
+True if the file whose file descriptor number
+is
+.Ar file_descriptor
+is open and is associated with a terminal.
+.It Fl u Ar file
+True if
+.Ar file
+exists and its set user ID flag
+is set.
+.It Fl w Ar file
+True if
+.Ar file
+exists and is writable.
+True
+indicates only that the write flag is on.
+The file is not writable on a read-only file
+system even if this test indicates true.
+.It Fl x Ar file
+True if
+.Ar file
+exists and is executable.
+True
+indicates only that the execute flag is on.
+If
+.Ar file
+is a directory, true indicates that
+.Ar file
+can be searched.
+.It Fl z Ar string
+True if the length of
+.Ar string
+is zero.
+.It Fl L Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+This operator is retained for compatibility with previous versions of
+this program.
+Do not rely on its existence; use
+.Fl h
+instead.
+.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
+True if
+.Ar string
+is not the null
+string.
+.It Ar \&s\&1 Cm \&= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are identical.
+.It Ar \&s\&1 Cm \&!= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are not identical.
+.It Ar \&s\&1 Cm \&\*[Lt] 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 \&\*[Gt] Ar \&s\&2
+True if string
+.Ar \&s\&1
+comes after
+.Ar \&s\&2
+based on the ASCII value of their characters.
+.It Ar \&n\&1 Fl \&eq Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are algebraically
+equal.
+.It Ar \&n\&1 Fl \&ne Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are not
+algebraically equal.
+.It Ar \&n\&1 Fl \&gt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&ge Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than or equal to the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&lt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&le Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than or equal to the integer
+.Ar \&n\&2 .
+.El
+.Pp
+These primaries can be combined with the following operators:
+.Bl -tag -width Ar
+.It Cm \&! Ar expression
+True if
+.Ar expression
+is false.
+.It Ar expression1 Fl a Ar expression2
+True if both
+.Ar expression1
+and
+.Ar expression2
+are true.
+.It Ar expression1 Fl o Ar expression2
+True if either
+.Ar expression1
+or
+.Ar expression2
+are true.
+.It Cm \&( Ar expression Cm \&)
+True if
+.Ar expression
+is true.
+.El
+.Pp
+The
+.Fl a
+operator has higher precedence than the
+.Fl o
+operator.
+.Pp
+Note that all file tests with the exception of
+.Fl h
+and
+.Fl L
+follow symbolic links and thus evaluate the test for the file pointed at.
+.Sh GRAMMAR AMBIGUITY
+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.
+.Sh EXIT STATUS
+The
+.Nm test
+utility exits with one of the following values:
+.Bl -tag -width Ds
+.It 0
+.Ar expression
+evaluated to true.
+.It 1
+.Ar expression
+evaluated to false or was missing.
+.It \*[Gt]1
+An error occurred.
+.El
+.Sh STANDARDS
+The
+.Nm test
+utility implements a superset of the
+.St -p1003.2
+specification.
diff --git a/bin/test/test.c b/bin/test/test.c
new file mode 100644 (file)
index 0000000..1497e4d
--- /dev/null
@@ -0,0 +1,719 @@
+/* $NetBSD: test.c,v 1.39 2012/03/15 02:02:21 joerg Exp $ */
+
+/*
+ * test(1); version 7-like  --  author Erik Baalbergen
+ * 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 program is in the Public Domain.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: test.c,v 1.39 2012/03/15 02:02:21 joerg Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.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";
+
+       binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+                       "-nt"|"-ot"|"-ef";
+       operand ::= <any legal UNIX file name>
+*/
+
+enum token {
+       EOI,
+       FILRD,
+       FILWR,
+       FILEX,
+       FILEXIST,
+       FILREG,
+       FILDIR,
+       FILCDEV,
+       FILBDEV,
+       FILFIFO,
+       FILSOCK,
+       FILSYM,
+       FILGZ,
+       FILTT,
+       FILSUID,
+       FILSGID,
+       FILSTCK,
+       FILNT,
+       FILOT,
+       FILEQ,
+       FILUID,
+       FILGID,
+       STREZ,
+       STRNZ,
+       STREQ,
+       STRNE,
+       STRLT,
+       STRGT,
+       INTEQ,
+       INTNE,
+       INTGE,
+       INTGT,
+       INTLE,
+       INTLT,
+       UNOT,
+       BAND,
+       BOR,
+       LPAREN,
+       RPAREN,
+       OPERAND
+};
+
+enum token_types {
+       UNOP,
+       BINOP,
+       BUNOP,
+       BBINOP,
+       PAREN
+};
+
+struct t_op {
+       const char *op_text;
+       short op_num, op_type;
+};
+
+static const struct t_op cop[] = {
+       {"!",   UNOT,   BUNOP},
+       {"(",   LPAREN, PAREN},
+       {")",   RPAREN, PAREN},
+       {"<",   STRLT,  BINOP},
+       {"=",   STREQ,  BINOP},
+       {">",   STRGT,  BINOP},
+};
+
+static const struct t_op cop2[] = {
+       {"!=",  STRNE,  BINOP},
+};
+
+static const struct t_op mop3[] = {
+       {"ef",  FILEQ,  BINOP},
+       {"eq",  INTEQ,  BINOP},
+       {"ge",  INTGE,  BINOP},
+       {"gt",  INTGT,  BINOP},
+       {"le",  INTLE,  BINOP},
+       {"lt",  INTLT,  BINOP},
+       {"ne",  INTNE,  BINOP},
+       {"nt",  FILNT,  BINOP},
+       {"ot",  FILOT,  BINOP},
+};
+
+static const struct t_op mop2[] = {
+       {"G",   FILGID, UNOP},
+       {"L",   FILSYM, UNOP},
+       {"O",   FILUID, UNOP},
+       {"S",   FILSOCK,UNOP},
+       {"a",   BAND,   BBINOP},
+       {"b",   FILBDEV,UNOP},
+       {"c",   FILCDEV,UNOP},
+       {"d",   FILDIR, UNOP},
+       {"e",   FILEXIST,UNOP},
+       {"f",   FILREG, UNOP},
+       {"g",   FILSGID,UNOP},
+       {"h",   FILSYM, UNOP},          /* for backwards compat */
+       {"k",   FILSTCK,UNOP},
+       {"n",   STRNZ,  UNOP},
+       {"o",   BOR,    BBINOP},
+       {"p",   FILFIFO,UNOP},
+       {"r",   FILRD,  UNOP},
+       {"s",   FILGZ,  UNOP},
+       {"t",   FILTT,  UNOP},
+       {"u",   FILSUID,UNOP},
+       {"w",   FILWR,  UNOP},
+       {"x",   FILEX,  UNOP},
+       {"z",   STREZ,  UNOP},
+};
+
+static char **t_wp;
+static struct t_op const *t_wp_op;
+
+__dead static void syntax(const char *, const char *);
+static int oexpr(enum token);
+static int aexpr(enum token);
+static int nexpr(enum token);
+static int primary(enum token);
+static int binop(void);
+static int test_access(struct stat *, mode_t);
+static int filstat(char *, enum token);
+static enum token t_lex(char *);
+static int isoperand(void);
+static long long getn(const char *);
+static int newerf(const char *, const char *);
+static int olderf(const char *, const char *);
+static int equalf(const char *, const char *);
+
+#if defined(SHELL)
+extern void error(const char *, ...) __dead __printflike(1, 2);
+extern void *ckmalloc(size_t);
+#else
+static void error(const char *, ...) __dead __printflike(1, 2);
+
+static void
+error(const char *msg, ...)
+{
+       va_list ap;
+
+       va_start(ap, msg);
+       verrx(2, msg, ap);
+       /*NOTREACHED*/
+       va_end(ap);
+}
+
+static void *ckmalloc(size_t);
+static void *
+ckmalloc(size_t nbytes)
+{
+       void *p = malloc(nbytes);
+
+       if (!p)
+               error("Not enough memory!");
+       return p;
+}
+#endif
+
+#ifdef SHELL
+int testcmd(int, char **);
+
+int
+testcmd(int argc, char **argv)
+#else
+int main(int, char *[]);
+
+int
+main(int argc, char *argv[])
+#endif
+{
+       int res;
+       const char *argv0;
+
+#ifdef SHELL
+       argv0 = argv[0];
+#else
+       setprogname(argv[0]);
+       (void)setlocale(LC_ALL, "");
+       argv0 = getprogname();
+#endif
+       if (strcmp(argv0, "[") == 0) {
+               if (strcmp(argv[--argc], "]"))
+                       error("missing ]");
+               argv[argc] = NULL;
+       }
+
+       if (argc < 2)
+               return 1;
+
+       t_wp = &argv[1];
+       res = !oexpr(t_lex(*t_wp));
+
+       if (*t_wp != NULL && *++t_wp != NULL)
+               syntax(*t_wp, "unexpected operator");
+
+       return res;
+}
+
+static void
+syntax(const char *op, const char *msg)
+{
+
+       if (op && *op)
+               error("%s: %s", op, msg);
+       else
+               error("%s", msg);
+}
+
+static int
+oexpr(enum token n)
+{
+       int res;
+
+       res = aexpr(n);
+       if (*t_wp == NULL)
+               return res;
+       if (t_lex(*++t_wp) == BOR)
+               return oexpr(t_lex(*++t_wp)) || res;
+       t_wp--;
+       return res;
+}
+
+static int
+aexpr(enum token n)
+{
+       int res;
+
+       res = nexpr(n);
+       if (*t_wp == NULL)
+               return res;
+       if (t_lex(*++t_wp) == BAND)
+               return aexpr(t_lex(*++t_wp)) && res;
+       t_wp--;
+       return res;
+}
+
+static int
+nexpr(enum token n)
+{
+
+       if (n == UNOT)
+               return !nexpr(t_lex(*++t_wp));
+       return primary(n);
+}
+
+static int
+primary(enum token n)
+{
+       enum token nn;
+       int res;
+
+       if (n == EOI)
+               return 0;               /* missing expression */
+       if (n == LPAREN) {
+               if ((nn = t_lex(*++t_wp)) == RPAREN)
+                       return 0;       /* missing expression */
+               res = oexpr(nn);
+               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((int)getn(*t_wp));
+               default:
+                       return filstat(*t_wp, n);
+               }
+       }
+
+       if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
+               return binop();
+       }         
+
+       return strlen(*t_wp) > 0;
+}
+
+static int
+binop(void)
+{
+       const char *opnd1, *opnd2;
+       struct t_op const *op;
+
+       opnd1 = *t_wp;
+       (void) t_lex(*++t_wp);
+       op = t_wp_op;
+
+       if ((opnd2 = *++t_wp) == NULL)
+               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);
+       default:
+               abort();
+               /* NOTREACHED */
+       }
+}
+
+/*
+ * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits,
+ * not use access():
+ *
+ *     True shall indicate only that the write flag is on.  The file is not
+ *     writable on a read-only file system even if this test indicates true.
+ *
+ * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only:
+ *
+ *     True shall indicate that permission to read from file will be granted,
+ *     as defined in "File Read, Write, and Creation".
+ *
+ * and that section says:
+ *
+ *     When a file is to be read or written, the file shall be opened with an
+ *     access mode corresponding to the operation to be performed.  If file
+ *     access permissions deny access, the requested operation shall fail.
+ *
+ * and of course access permissions are described as one might expect:
+ *
+ *     * If a process has the appropriate privilege:
+ *
+ *        * If read, write, or directory search permission is requested,
+ *          access shall be granted.
+ *
+ *        * If execute permission is requested, access shall be granted if
+ *          execute permission is granted to at least one user by the file
+ *          permission bits or by an alternate access control mechanism;
+ *          otherwise, access shall be denied.
+ *
+ *   * Otherwise:
+ *
+ *        * The file permission bits of a file contain read, write, and
+ *          execute/search permissions for the file owner class, file group
+ *          class, and file other class.
+ *
+ *        * Access shall be granted if an alternate access control mechanism
+ *          is not enabled and the requested access permission bit is set for
+ *          the class (file owner class, file group class, or file other class)
+ *          to which the process belongs, or if an alternate access control
+ *          mechanism is enabled and it allows the requested access; otherwise,
+ *          access shall be denied.
+ *
+ * and when I first read this I thought:  surely we can't go about using
+ * open(O_WRONLY) to try this test!  However the POSIX 1003.1-2001 Rationale
+ * section for test does in fact say:
+ *
+ *     On historical BSD systems, test -w directory always returned false
+ *     because test tried to open the directory for writing, which always
+ *     fails.
+ *
+ * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX
+ * System III, and thus presumably also for BSD up to and including 4.3.
+ *
+ * Secondly I remembered why using open() and/or access() are bogus.  They
+ * don't work right for detecting read and write permissions bits when called
+ * by root.
+ *
+ * Interestingly the 'test' in 4.4BSD was closer to correct (as per
+ * 1003.2-1992) and it was implemented efficiently with stat() instead of
+ * open().
+ *
+ * This was apparently broken in NetBSD around about 1994/06/30 when the old
+ * 4.4BSD implementation was replaced with a (arguably much better coded)
+ * implementation derived from pdksh.
+ *
+ * Note that modern pdksh is yet different again, but still not correct, at
+ * least not w.r.t. 1003.2-1992.
+ *
+ * As I think more about it and read more of the related IEEE docs I don't like
+ * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all.  I very
+ * much prefer the original wording in 1003.2-1992.  It is much more useful,
+ * and so that's what I've implemented.
+ *
+ * (Note that a strictly conforming implementation of 1003.1-2001 is in fact
+ * totally useless for the case in question since its 'test -w' and 'test -r'
+ * can never fail for root for any existing files, i.e. files for which 'test
+ * -e' succeeds.)
+ * 
+ * The rationale for 1003.1-2001 suggests that the wording was "clarified" in
+ * 1003.1-2001 to align with the 1003.2b draft.  1003.2b Draft 12 (July 1999),
+ * which is the latest copy I have, does carry the same suggested wording as is
+ * in 1003.1-2001, with its rationale saying:
+ * 
+ *     This change is a clarification and is the result of interpretation
+ *     request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992.
+ * 
+ * That interpretation can be found here:
+ * 
+ *   http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html
+ * 
+ * Not terribly helpful, unfortunately.  I wonder who that fence sitter was.
+ * 
+ * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the
+ * PASC interpretation and appear to be gone against at least one widely used
+ * implementation (namely 4.4BSD).  The problem is that for file access by root
+ * this means that if test '-r' and '-w' are to behave as if open() were called
+ * then there's no way for a shell script running as root to check if a file
+ * has certain access bits set other than by the grotty means of interpreting
+ * the output of 'ls -l'.  This was widely considered to be a bug in V7's
+ * "test" and is, I believe, one of the reasons why direct use of access() was
+ * avoided in some more recent implementations!
+ * 
+ * I have always interpreted '-r' to match '-w' and '-x' as per the original
+ * wording in 1003.2-1992, not the other way around.  I think 1003.2b goes much
+ * too far the wrong way without any valid rationale and that it's best if we
+ * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of
+ * open() since we already know very well how it will work -- existance of the
+ * file is all that matters to open() for root.
+ * 
+ * Unfortunately the SVID is no help at all (which is, I guess, partly why
+ * we're in this mess in the first place :-).
+ * 
+ * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use
+ * access(name, 2) even though it also goes to much greater lengths for '-x'
+ * matching the 1003.2-1992 definition (which is no doubt where that definition
+ * came from).
+ *
+ * The ksh93 implementation uses access() for '-r' and '-w' if
+ * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root.
+ * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b).
+ */
+static int
+test_access(struct stat *sp, mode_t stmode)
+{
+       gid_t *groups; 
+       register int n;
+       uid_t euid;
+       int maxgroups;
+
+       /*
+        * I suppose we could use access() if not running as root and if we are
+        * running with ((euid == uid) && (egid == gid)), but we've already
+        * done the stat() so we might as well just test the permissions
+        * directly instead of asking the kernel to do it....
+        */
+       euid = geteuid();
+       if (euid == 0)                          /* any bit is good enough */
+               stmode = (stmode << 6) | (stmode << 3) | stmode;
+       else if (sp->st_uid == euid)
+               stmode <<= 6;
+       else if (sp->st_gid == getegid())
+               stmode <<= 3;
+       else {
+               /* XXX stolen almost verbatim from ksh93.... */
+               /* on some systems you can be in several groups */
+               if ((maxgroups = getgroups(0, NULL)) <= 0)
+                       maxgroups = NGROUPS_MAX;        /* pre-POSIX system? */
+               groups = ckmalloc((maxgroups + 1) * sizeof(gid_t));
+               n = getgroups(maxgroups, groups);
+               while (--n >= 0) {
+                       if (groups[n] == sp->st_gid) {
+                               stmode <<= 3;
+                               break;
+                       }
+               }
+               free(groups);
+       }
+
+       return sp->st_mode & stmode;
+}
+
+static int
+filstat(char *nm, enum token mode)
+{
+       struct stat s;
+
+       if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
+               return 0;
+
+       switch (mode) {
+       case FILRD:
+               return test_access(&s, S_IROTH);
+       case FILWR:
+               return test_access(&s, S_IWOTH);
+       case FILEX:
+               return test_access(&s, S_IXOTH);
+       case FILEXIST:
+               return 1; /* the successful lstat()/stat() is good enough */
+       case FILREG:
+               return S_ISREG(s.st_mode);
+       case FILDIR:
+               return S_ISDIR(s.st_mode);
+       case FILCDEV:
+               return S_ISCHR(s.st_mode);
+       case FILBDEV:
+               return S_ISBLK(s.st_mode);
+       case FILFIFO:
+               return S_ISFIFO(s.st_mode);
+       case FILSOCK:
+               return S_ISSOCK(s.st_mode);
+       case FILSYM:
+               return S_ISLNK(s.st_mode);
+       case FILSUID:
+               return (s.st_mode & S_ISUID) != 0;
+       case FILSGID:
+               return (s.st_mode & S_ISGID) != 0;
+       case FILSTCK:
+               return (s.st_mode & S_ISVTX) != 0;
+       case FILGZ:
+               return s.st_size > (off_t)0;
+       case FILUID:
+               return s.st_uid == geteuid();
+       case FILGID:
+               return s.st_gid == getegid();
+       default:
+               return 1;
+       }
+}
+
+#define VTOC(x)        (const unsigned char *)((const struct t_op *)x)->op_text
+
+static int
+compare1(const void *va, const void *vb)
+{
+       const unsigned char *a = va;
+       const unsigned char *b = VTOC(vb);
+
+       return a[0] - b[0];
+}
+
+static int
+compare2(const void *va, const void *vb)
+{
+       const unsigned char *a = va;
+       const unsigned char *b = VTOC(vb);
+       int z = a[0] - b[0];
+
+       return z ? z : (a[1] - b[1]);
+}
+
+static struct t_op const *
+findop(const char *s)
+{
+       if (s[0] == '-') {
+               if (s[1] == '\0')
+                       return NULL;
+               if (s[2] == '\0')
+                       return bsearch(s + 1, mop2, __arraycount(mop2),
+                           sizeof(*mop2), compare1);
+               else if (s[3] != '\0')
+                       return NULL;
+               else
+                       return bsearch(s + 1, mop3, __arraycount(mop3),
+                           sizeof(*mop3), compare2);
+       } else {
+               if (s[1] == '\0')
+                       return bsearch(s, cop, __arraycount(cop), sizeof(*cop),
+                           compare1);
+               else if (strcmp(s, cop2[0].op_text) == 0)
+                       return cop2;
+               else
+                       return NULL;
+       }
+}
+
+static enum token
+t_lex(char *s)
+{
+       struct t_op const *op;
+
+       if (s == NULL) {
+               t_wp_op = NULL;
+               return EOI;
+       }
+
+       if ((op = findop(s)) != NULL) {
+               if (!((op->op_type == UNOP && isoperand()) ||
+                   (op->op_num == LPAREN && *(t_wp+1) == 0))) {
+                       t_wp_op = op;
+                       return op->op_num;
+               }
+       }
+       t_wp_op = NULL;
+       return OPERAND;
+}
+
+static int
+isoperand(void)
+{
+       struct t_op const *op;
+       char *s, *t;
+
+       if ((s  = *(t_wp+1)) == 0)
+               return 1;
+       if ((t = *(t_wp+2)) == 0)
+               return 0;
+       if ((op = findop(s)) != NULL)
+               return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0'); 
+       return 0;
+}
+
+/* atoi with error detection */
+static long long
+getn(const char *s)
+{
+       char *p;
+       long long r;
+
+       errno = 0;
+       r = strtoll(s, &p, 10);
+
+       if (errno != 0)
+       if (errno == ERANGE && (r == LLONG_MAX || r == LLONG_MIN))
+             error("%s: out of range", s);
+
+       while (isspace((unsigned char)*p))
+             p++;
+       
+       if (*p || p == s)
+             error("%s: bad number", s);
+
+       return r;
+}
+
+static int
+newerf(const char *f1, const char *f2)
+{
+       struct stat b1, b2;
+
+       return (stat(f1, &b1) == 0 &&
+               stat(f2, &b2) == 0 &&
+               b1.st_mtime > b2.st_mtime);
+}
+
+static int
+olderf(const char *f1, const char *f2)
+{
+       struct stat b1, b2;
+
+       return (stat(f1, &b1) == 0 &&
+               stat(f2, &b2) == 0 &&
+               b1.st_mtime < b2.st_mtime);
+}
+
+static int
+equalf(const char *f1, const char *f2)
+{
+       struct stat b1, b2;
+
+       return (stat(f1, &b1) == 0 &&
+               stat(f2, &b2) == 0 &&
+               b1.st_dev == b2.st_dev &&
+               b1.st_ino == b2.st_ino);
+}
index 7d7613946cd2b97b2373eacc30e8e0c50c5a20f9..2b78850f397ad0cf754f04378744f51303bdfddc 100644 (file)
@@ -15,6 +15,7 @@
 2012/10/17 12:00:00,bin/pax
 2011/08/29 14:48:46,bin/rm
 2011/08/29 14:49:38,bin/rmdir
+2012/10/17 12:00:00,bin/test
 2012/10/17 12:00:00,build.sh
 2012/10/17 12:00:00,common/dist/zlib
 2012/10/17 12:00:00,common/include/prop