diff options
Diffstat (limited to 'bin/test')
-rw-r--r-- | bin/test/TEST.csh | 138 | ||||
-rw-r--r-- | bin/test/test.1 | 383 | ||||
-rw-r--r-- | bin/test/test.c | 904 |
3 files changed, 1425 insertions, 0 deletions
diff --git a/bin/test/TEST.csh b/bin/test/TEST.csh new file mode 100644 index 0000000..672e1a0 --- /dev/null +++ b/bin/test/TEST.csh @@ -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 index 0000000..9d62515 --- /dev/null +++ b/bin/test/test.1 @@ -0,0 +1,383 @@ +.\" $NetBSD: test.1,v 1.33 2017/10/18 18:11:54 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 October 17, 2017 +.Dt TEST 1 +.Os +.Sh NAME +.Nm test , +.Nm \&[ +.Nd condition evaluation utility +.Sh SYNOPSIS +.Nm +.Ar expression +.Nm \&[ +.Ar expression Cm \&] +.Sh DESCRIPTION +The +.Nm +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 +also +returns 1 (false). +.Pp +All operators and flags are separate arguments to the +.Nm +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 (FIFO). +.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 \&< 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 \&> 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 \> 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 \< 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 +is 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 EXIT STATUS +The +.Nm +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 >1 +An error occurred. +.El +.Sh STANDARDS +The +.Nm +utility implements a superset of the +.St -p1003.2 +specification. +.Sh HISTORY +A +.Nm +utility appeared in +.At v7 . +.Sh CAVEATS +The +.Nm +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. +.Pp +This means that +.Nm +should not be used with more than 4 operands +(where the terminating +.Cm \&] +in the case of the +.Nm \&[ +command does not count as an operand,) +and that the obsolete +.Fl a +and +.Fl o +options should not be used. +Instead invoke +.Nm +multiple times connected by the +.Dq && +and +.Dq || +operators from +.Xr sh 1 . +When those operators are not used, there is no need +for the parentheses as grouping symbols, so those should also be +avoided. +Using +.Xr sh 1 Ns 's +.Cm \&! +command instead of the equivalent operator from +.Nm +can also protect the script from future test enhancements. +.Pp +Most expressions with 3 or less operands will evaluate as expected, +though be aware that with 3 operands, +if the second is a known binary operator, +that is always evaluated, +regardless of what the other operands might suggest had been intended. +If, and only if, the middle operand is not a defined binary operator +is the first operand examined to see if it is +.Cm \&! +in which case the remaining operands are evaluated as a two operand test, +and the result inverted. +The only other defined three operand case is the meaningless +degenerate case where parentheses (1st and 3rd operands) +surround a one operand expression. +.Pp +With 4 operands there are just two defined cases, the first +where the first operand is +.Cm \&! +in which case the result of the three operand test on the +remaining operands is inverted, +and the second is similar to the 3 operand case, +the degenerate case of parentheses surrounding an (in this case) +2 operand test expression. diff --git a/bin/test/test.c b/bin/test/test.c new file mode 100644 index 0000000..8cf9dab --- /dev/null +++ b/bin/test/test.c @@ -0,0 +1,904 @@ +/* $NetBSD: test.c,v 1.43 2018/09/13 22:00:58 kre 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.43 2018/09/13 22:00:58 kre 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 +#ifndef SMALL + , + BUNOP, + BBINOP, + PAREN +#endif +}; + +struct t_op { + const char *op_text; + short op_num, op_type; +}; + +static const struct t_op cop[] = { +#ifndef SMALL + {"!", UNOT, BUNOP}, + {"(", LPAREN, PAREN}, + {")", RPAREN, PAREN}, +#endif + {"<", 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}, +#ifndef SMALL + {"a", BAND, BBINOP}, +#endif + {"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}, +#ifndef SMALL + {"o", BOR, BBINOP}, +#endif + {"p", FILFIFO,UNOP}, + {"r", FILRD, UNOP}, + {"s", FILGZ, UNOP}, + {"t", FILTT, UNOP}, + {"u", FILSUID,UNOP}, + {"w", FILWR, UNOP}, + {"x", FILEX, UNOP}, + {"z", STREZ, UNOP}, +}; + +#ifndef SMALL +static char **t_wp; +static struct t_op const *t_wp_op; +#endif + +#ifndef SMALL +__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 enum token t_lex(char *); +static int isoperand(void); +#endif +static struct t_op const *findop(const char *); +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(const char *, enum token); +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 *); + +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); +#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 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; + } + + /* + * 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) + */ + +#ifdef SMALL + error("SMALL test, no fallback usage"); +#else + + t_wp = &argv[1]; + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) + syntax(*t_wp, "unexpected operator"); + + return res; +#endif +} + +#ifndef SMALL +static void +syntax(const char *op, const char *msg) +{ + + if (op && *op) + error("%s: %s", op, msg); + else + error("%s", msg); +} +#endif + +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); + +#ifndef TINY + /* + * 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; +#endif + + 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; + } + +#ifndef TINY + if (a1[0] == '(' && a3[0] == ')' && a3[1] == '\0') + return one_arg(a2); +#endif + + 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; + } + +#ifndef TINY + if (a1[0] == '(' && a4[0] == ')' && a4[1] == '\0') + return two_arg(a2, a3); +#endif + + return -1; +} + +#ifndef SMALL +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"); + return perform_unop(n, *t_wp); + } + + if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { + return binop(); + } + + return strlen(*t_wp) > 0; +} +#endif /* !SMALL */ + +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); + } +} + +#ifndef SMALL +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"); + + return perform_binop(op->op_num, opnd1, opnd2); +} +#endif + +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: + 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(const 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; + } +} + +#ifndef SMALL +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; +} +#endif + +/* 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); + + if (p != 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 && + timespeccmp(&b1.st_mtim, &b2.st_mtim, >)); +} + +static int +olderf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + timespeccmp(&b1.st_mtim, &b2.st_mtim, <)); +} + +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); +} |