Upgrading test

Change-Id: I1cd8e24475030989c95ba60b4e7462dacf945b9f
This commit is contained in:
Lionel Sambuc 2013-01-30 15:53:50 +01:00
parent 2c96f0541b
commit 3a19ae756f
6 changed files with 1193 additions and 1 deletions

View 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>

9
bin/test/Makefile Normal file
View file

@ -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>

138
bin/test/TEST.csh Normal file
View file

@ -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" \)

325
bin/test/test.1 Normal file
View file

@ -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.

719
bin/test/test.c Normal file
View file

@ -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);
}

View 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