/* Modified to run with the GNU shell by bfox. */
-/* Copyright (C) 1987-2001 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2005, 2007-2008 Free Software Foundation, Inc.
- This file is part of GNU Bash, the Bourne Again SHell.
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- Bash is free software; you can redistribute it and/or modify it under
- the terms of the GNU General Public License as published by the Free
- Software Foundation; either version 2, or (at your option) any later
- version.
-
- Bash is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- for more details.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* Define TEST_STANDALONE to get the /bin/test version. Otherwise, you get
the shell builtin version. */
-/* #define TEST_STANDALONE */
#include <config.h>
#include <stdio.h>
#include <sys/types.h>
-/* The official name of this program (e.g., no `g' prefix). */
-#define PROGRAM_NAME "test"
-
#define TEST_STANDALONE 1
-#if !defined (TEST_STANDALONE)
-# include "shell.h"
-# include "posixstat.h"
-# include "filecntl.h"
-#else /* TEST_STANDALONE */
-# include "system.h"
-# include "group-member.h"
-# include "error.h"
-# if !defined (S_IXUGO)
-# define S_IXUGO 0111
-# endif /* S_IXUGO */
-# if defined (_POSIX_VERSION)
-# include <limits.h>
-# else /* !_POSIX_VERSION */
-# include <sys/param.h>
-# endif /* _POSIX_VERSION */
-# define whitespace(c) (((c) == ' ') || ((c) == '\t'))
-# define digit(c) ((c) >= '0' && (c) <= '9')
-# define digit_value(c) ((c) - '0')
-char *program_name;
-#endif /* TEST_STANDALONE */
+#ifndef LBRACKET
+# define LBRACKET 0
+#endif
+
+/* The official name of this program (e.g., no `g' prefix). */
+#if LBRACKET
+# define PROGRAM_NAME "["
+#else
+# define PROGRAM_NAME "test"
+#endif
-#if !defined (_POSIX_VERSION)
-# include <sys/file.h>
-#endif /* !_POSIX_VERSION */
+#include "system.h"
+#include "euidaccess.h"
+#include "quote.h"
+#include "stat-time.h"
+#include "strnumcmp.h"
-#include <errno.h>
-#ifndef errno
-extern int errno;
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
#endif
-#undef STREQ
-#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp (a, b) == 0)
-
-#if !defined (member)
-# define member(c, s) ((c) ? (strchr ((s), (c)) ? 1 : 0) : 0)
-#endif /* !member */
-
-extern gid_t getegid ();
-extern uid_t geteuid ();
-
-#if !defined (R_OK)
-# define R_OK 4
-# define W_OK 2
-# define X_OK 1
-# define F_OK 0
-#endif /* R_OK */
-
-/* This name is used solely when printing --version information. */
-#define PROGRAM_NAME "test"
-
-/* The following few defines control the truth and false output of each stage.
- TRUE and FALSE are what we use to compute the final output value.
- SHELL_BOOLEAN is the form which returns truth or falseness in shell terms.
- TRUTH_OR is how to do logical or with TRUE and FALSE.
- TRUTH_AND is how to do logical and with TRUE and FALSE..
- Default is TRUE = 1, FALSE = 0, TRUTH_OR = a | b, TRUTH_AND = a & b,
- SHELL_BOOLEAN = (!value). */
-#define TRUE 1
-#define FALSE 0
-#define SHELL_BOOLEAN(value) (!(value))
-#define TRUTH_OR(a, b) ((a) | (b))
-#define TRUTH_AND(a, b) ((a) & (b))
-
-#if defined (TEST_STANDALONE)
+/* Exit status for syntax errors, etc. */
+enum { TEST_TRUE, TEST_FALSE, TEST_FAILURE };
+
+#if defined TEST_STANDALONE
# define test_exit(val) exit (val)
#else
static jmp_buf test_exit_buf;
static int argc; /* The number of arguments present in ARGV. */
static char **argv; /* The argument list. */
-static int unop PARAMS ((int op));
-static int binop PARAMS ((char *s));
-static int unary_operator PARAMS ((void));
-static int binary_operator PARAMS ((void));
-static int two_arguments PARAMS ((void));
-static int three_arguments PARAMS ((void));
-static int posixtest PARAMS ((void));
+static bool test_unop (char const *s);
+static bool unary_operator (void);
+static bool binary_operator (bool);
+static bool two_arguments (void);
+static bool three_arguments (void);
+static bool posixtest (int);
-static int expr PARAMS ((void));
-static int term PARAMS ((void));
-static int and PARAMS ((void));
-static int or PARAMS ((void));
+static bool expr (void);
+static bool term (void);
+static bool and (void);
+static bool or (void);
-static void test_syntax_error PARAMS ((char *format, char *arg))
+static void test_syntax_error (char const *format, char const *arg)
ATTRIBUTE_NORETURN;
-static void beyond PARAMS ((void)) ATTRIBUTE_NORETURN;
+static void beyond (void) ATTRIBUTE_NORETURN;
static void
-test_syntax_error (char *format, char *arg)
+test_syntax_error (char const *format, char const *arg)
{
fprintf (stderr, "%s: ", argv[0]);
fprintf (stderr, format, arg);
+ fputc ('\n', stderr);
fflush (stderr);
- test_exit (SHELL_BOOLEAN (FALSE));
-}
-
-/* A wrapper for stat () which disallows pathnames that are empty strings. */
-static int
-test_stat (char *path, struct stat *finfo)
-{
- if (*path == '\0')
- {
- errno = ENOENT;
- return (-1);
- }
- return (stat (path, finfo));
-}
-
-/* Do the same thing access(2) does, but use the effective uid and gid,
- and don't make the mistake of telling root that any file is executable.
- But this loses when the containing filesystem is mounted e.g. read-only. */
-static int
-eaccess (char *path, int mode)
-{
- struct stat st;
- static int euid = -1;
-
- if (test_stat (path, &st) < 0)
- return (-1);
-
- if (euid == -1)
- euid = geteuid ();
-
- if (euid == 0)
- {
- /* Root can read or write any file. */
- if (mode != X_OK)
- return (0);
-
- /* Root can execute any file that has any one of the execute
- bits set. */
- if (st.st_mode & S_IXUGO)
- return (0);
- }
-
- if (st.st_uid == euid) /* owner */
- mode <<= 6;
- else if (group_member (st.st_gid))
- mode <<= 3;
-
- if (st.st_mode & mode)
- return (0);
-
- return (-1);
+ test_exit (TEST_FAILURE);
}
/* Increment our position in the argument list. Check that we're not
past the end of the argument list. This check is supressed if the
- argument is FALSE. Made a macro for efficiency. */
-#define advance(f) \
- do \
- { \
- ++pos; \
- if ((f) && pos >= argc) \
- beyond (); \
- } \
- while (0)
-
-#if !defined (advance)
-static int
-advance (int f)
+ argument is false. */
+
+static inline void
+advance (bool f)
{
++pos;
if (f && pos >= argc)
beyond ();
}
-#endif /* advance */
-#define unary_advance() \
- do \
- { \
- advance (1); \
- ++pos; \
- } \
- while (0)
+static inline void
+unary_advance (void)
+{
+ advance (true);
+ ++pos;
+}
/*
* beyond - call when we're beyond the end of the argument list (an
static void
beyond (void)
{
- test_syntax_error (_("argument expected\n"), NULL);
-}
-
-/* Syntax error for when an integer argument was expected, but
- something else was found. */
-static void
-integer_expected_error (char *pch)
-{
- test_syntax_error (_("integer expression expected %s\n"), pch);
+ test_syntax_error (_("missing argument after %s"), quote (argv[argc - 1]));
}
-/* Return nonzero if the characters pointed to by STRING constitute a
- valid number. Stuff the converted number into RESULT if RESULT is
- not null. */
-static int
-isint (register char *string, intmax_t *result)
+/* If the characters pointed to by STRING constitute a valid number,
+ return a pointer to the start of the number, skipping any blanks or
+ leading '+'. Otherwise, report an error and exit. */
+static char const *
+find_int (char const *string)
{
- int sign;
- intmax_t value;
+ char const *p;
+ char const *number_start;
- sign = 1;
- value = 0;
+ for (p = string; isblank (to_uchar (*p)); p++)
+ continue;
- if (result)
- *result = 0;
-
- /* Skip leading whitespace characters. */
- while (whitespace (*string))
- string++;
-
- if (!*string)
- return (0);
-
- /* We allow leading `-' or `+'. */
- if (*string == '-' || *string == '+')
+ if (*p == '+')
{
- if (!digit (string[1]))
- return (0);
-
- if (*string == '-')
- sign = -1;
-
- string++;
+ p++;
+ number_start = p;
}
-
- while (digit (*string))
+ else
{
- if (result)
- value = (value * 10) + digit_value (*string);
- string++;
+ number_start = p;
+ p += (*p == '-');
}
- /* Skip trailing whitespace, if any. */
- while (whitespace (*string))
- string++;
-
- /* Error if not at end of string. */
- if (*string)
- return (0);
-
- if (result)
+ if (ISDIGIT (*p++))
{
- value *= sign;
- *result = value;
+ while (ISDIGIT (*p))
+ p++;
+ while (isblank (to_uchar (*p)))
+ p++;
+ if (!*p)
+ return number_start;
}
- return (1);
+ test_syntax_error (_("invalid integer %s"), quote (string));
}
-/* Find the modification time of FILE, and stuff it into *AGE.
- Return nonzero if successful, else zero. */
-static int
-age_of (char *filename, time_t *age)
+/* Find the modification time of FILE, and stuff it into *MTIME.
+ Return true if successful. */
+static bool
+get_mtime (char const *filename, struct timespec *mtime)
{
struct stat finfo;
+ bool ok = (stat (filename, &finfo) == 0);
+#ifdef lint
+ static struct timespec const zero;
+ *mtime = zero;
+#endif
+ if (ok)
+ *mtime = get_stat_mtime (&finfo);
+ return ok;
+}
- if (test_stat (filename, &finfo) < 0)
- return (0);
-
- if (age)
- *age = finfo.st_mtime;
-
- return (1);
+/* Return true if S is one of the test command's binary operators. */
+static bool
+binop (char const *s)
+{
+ return ((STREQ (s, "=")) || (STREQ (s, "!=")) || (STREQ (s, "-nt")) ||
+ (STREQ (s, "-ot")) || (STREQ (s, "-ef")) || (STREQ (s, "-eq")) ||
+ (STREQ (s, "-ne")) || (STREQ (s, "-lt")) || (STREQ (s, "-le")) ||
+ (STREQ (s, "-gt")) || (STREQ (s, "-ge")));
}
/*
* term ::=
* '-'('h'|'d'|'f'|'r'|'s'|'w'|'c'|'b'|'p'|'u'|'g'|'k') filename
* '-'('L'|'x') filename
- * '-t' [ int ]
+ * '-t' int
* '-'('z'|'n') string
* string
* string ('!='|'=') string
* '-l' string
* positive and negative integers
*/
-static int
+static bool
term (void)
{
- int value;
+ bool value;
+ bool negated = false;
+
+ /* Deal with leading `not's. */
+ while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0')
+ {
+ advance (true);
+ negated = !negated;
+ }
if (pos >= argc)
beyond ();
- /* Deal with leading "not"'s. */
- if ('!' == argv[pos][0] && '\000' == argv[pos][1])
+ /* A paren-bracketed argument. */
+ if (argv[pos][0] == '(' && argv[pos][1] == '\0')
{
- value = FALSE;
- while (pos < argc && '!' == argv[pos][0] && '\000' == argv[pos][1])
- {
- advance (1);
- value ^= (TRUE);
- }
+ int nargs;
- return (value ^ (term ()));
- }
+ advance (true);
- /* A paren-bracketed argument. */
- if (argv[pos][0] == '(' && !argv[pos][1])
- {
- advance (1);
- value = expr ();
- if (!argv[pos])
- test_syntax_error (_("')' expected\n"), NULL);
+ for (nargs = 1;
+ pos + nargs < argc && ! STREQ (argv[pos + nargs], ")");
+ nargs++)
+ if (nargs == 4)
+ {
+ nargs = argc - pos;
+ break;
+ }
+
+ value = posixtest (nargs);
+ if (argv[pos] == 0)
+ test_syntax_error (_("')' expected"), NULL);
else
if (argv[pos][0] != ')' || argv[pos][1])
- test_syntax_error (_("')' expected, found %s\n"), argv[pos]);
- advance (0);
- return (TRUE == (value));
+ test_syntax_error (_("')' expected, found %s"), argv[pos]);
+ advance (false);
}
- /* are there enough arguments left that this could be dyadic? */
- if (((pos + 3 <= argc) && binop (argv[pos + 1])) ||
- ((pos + 4 <= argc && STREQ (argv[pos], "-l") && binop (argv[pos + 2]))))
- value = binary_operator ();
+ /* Are there enough arguments left that this could be dyadic? */
+ else if (4 <= argc - pos && STREQ (argv[pos], "-l") && binop (argv[pos + 2]))
+ value = binary_operator (true);
+ else if (3 <= argc - pos && binop (argv[pos + 1]))
+ value = binary_operator (false);
- /* Might be a switch type argument */
- else if ('-' == argv[pos][0] && argv[pos][1] && 0 == argv[pos][2])
+ /* It might be a switch type argument. */
+ else if (argv[pos][0] == '-' && argv[pos][1] && argv[pos][2] == '\0')
{
- if (unop (argv[pos][1]))
+ if (test_unop (argv[pos]))
value = unary_operator ();
else
- test_syntax_error (_("%s: unary operator expected\n"), argv[pos]);
+ test_syntax_error (_("%s: unary operator expected"), argv[pos]);
}
else
{
value = (argv[pos][0] != '\0');
- advance (0);
+ advance (false);
}
- return (value);
+ return negated ^ value;
}
-static int
-binary_operator (void)
+static bool
+binary_operator (bool l_is_l)
{
- register int op;
+ int op;
struct stat stat_buf, stat_spare;
- intmax_t l, r;
- int value;
- /* Are the left and right integer expressions of the form '-l string'? */
- int l_is_l, r_is_l;
+ /* Is the right integer expression of the form '-l string'? */
+ bool r_is_l;
- if (strcmp (argv[pos], "-l") == 0)
- {
- l_is_l = 1;
- op = pos + 2;
+ if (l_is_l)
+ advance (false);
+ op = pos + 1;
- /* Make sure that OP is still a valid binary operator. */
- if ((op >= argc - 1) || (binop (argv[op]) == 0))
- test_syntax_error (_("%s: binary operator expected\n"), argv[op]);
-
- advance (0);
- }
- else
+ if ((op < argc - 2) && STREQ (argv[op + 1], "-l"))
{
- l_is_l = 0;
- op = pos + 1;
- }
-
- if ((op < argc - 2) && (strcmp (argv[op + 1], "-l") == 0))
- {
- r_is_l = 1;
- advance (0);
+ r_is_l = true;
+ advance (false);
}
else
- r_is_l = 0;
+ r_is_l = false;
if (argv[op][0] == '-')
{
/* check for eq, nt, and stuff */
+ if ((((argv[op][1] == 'l' || argv[op][1] == 'g')
+ && (argv[op][2] == 'e' || argv[op][2] == 't'))
+ || (argv[op][1] == 'e' && argv[op][2] == 'q')
+ || (argv[op][1] == 'n' && argv[op][2] == 'e'))
+ && !argv[op][3])
+ {
+ char lbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+ char rbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+ char const *l = (l_is_l
+ ? umaxtostr (strlen (argv[op - 1]), lbuf)
+ : find_int (argv[op - 1]));
+ char const *r = (r_is_l
+ ? umaxtostr (strlen (argv[op + 2]), rbuf)
+ : find_int (argv[op + 1]));
+ int cmp = strintcmp (l, r);
+ bool xe_operator = (argv[op][2] == 'e');
+ pos += 3;
+ return (argv[op][1] == 'l' ? cmp < xe_operator
+ : argv[op][1] == 'g' ? cmp > - xe_operator
+ : (cmp != 0) == xe_operator);
+ }
+
switch (argv[op][1])
{
default:
break;
- case 'l':
- if (argv[op][2] == 't' && !argv[op][3])
- {
- /* lt */
- if (l_is_l)
- l = strlen (argv[op - 1]);
- else
- {
- if (!isint (argv[op - 1], &l))
- integer_expected_error (_("before -lt"));
- }
-
- if (r_is_l)
- r = strlen (argv[op + 2]);
- else
- {
- if (!isint (argv[op + 1], &r))
- integer_expected_error (_("after -lt"));
- }
- pos += 3;
- return (TRUE == (l < r));
- }
-
- if (argv[op][2] == 'e' && !argv[op][3])
- {
- /* le */
- if (l_is_l)
- l = strlen (argv[op - 1]);
- else
- {
- if (!isint (argv[op - 1], &l))
- integer_expected_error (_("before -le"));
- }
- if (r_is_l)
- r = strlen (argv[op + 2]);
- else
- {
- if (!isint (argv[op + 1], &r))
- integer_expected_error (_("after -le"));
- }
- pos += 3;
- return (TRUE == (l <= r));
- }
- break;
-
- case 'g':
- if (argv[op][2] == 't' && !argv[op][3])
- {
- /* gt integer greater than */
- if (l_is_l)
- l = strlen (argv[op - 1]);
- else
- {
- if (!isint (argv[op - 1], &l))
- integer_expected_error (_("before -gt"));
- }
- if (r_is_l)
- r = strlen (argv[op + 2]);
- else
- {
- if (!isint (argv[op + 1], &r))
- integer_expected_error (_("after -gt"));
- }
- pos += 3;
- return (TRUE == (l > r));
- }
-
- if (argv[op][2] == 'e' && !argv[op][3])
- {
- /* ge - integer greater than or equal to */
- if (l_is_l)
- l = strlen (argv[op - 1]);
- else
- {
- if (!isint (argv[op - 1], &l))
- integer_expected_error (_("before -ge"));
- }
- if (r_is_l)
- r = strlen (argv[op + 2]);
- else
- {
- if (!isint (argv[op + 1], &r))
- integer_expected_error (_("after -ge"));
- }
- pos += 3;
- return (TRUE == (l >= r));
- }
- break;
-
case 'n':
if (argv[op][2] == 't' && !argv[op][3])
{
/* nt - newer than */
- time_t lt, rt;
- pos += 3;
- if (l_is_l || r_is_l)
- test_syntax_error (_("-nt does not accept -l\n"), NULL);
- if (age_of (argv[op - 1], <) && age_of (argv[op + 1], &rt))
- return (TRUE == (lt > rt));
- else
- return (FALSE);
- }
-
- if (argv[op][2] == 'e' && !argv[op][3])
- {
- /* ne - integer not equal */
- if (l_is_l)
- l = strlen (argv[op - 1]);
- else
- {
- if (!isint (argv[op - 1], &l))
- integer_expected_error (_("before -ne"));
- }
- if (r_is_l)
- r = strlen (argv[op + 2]);
- else
- {
- if (!isint (argv[op + 1], &r))
- integer_expected_error (_("after -ne"));
- }
+ struct timespec lt, rt;
+ bool le, re;
pos += 3;
- return (TRUE == (l != r));
+ if (l_is_l | r_is_l)
+ test_syntax_error (_("-nt does not accept -l"), NULL);
+ le = get_mtime (argv[op - 1], <);
+ re = get_mtime (argv[op + 1], &rt);
+ return le && (!re || timespec_cmp (lt, rt) > 0);
}
break;
case 'e':
- if (argv[op][2] == 'q' && !argv[op][3])
- {
- /* eq - integer equal */
- if (l_is_l)
- l = strlen (argv[op - 1]);
- else
- {
- if (!isint (argv[op - 1], &l))
- integer_expected_error (_("before -eq"));
- }
- if (r_is_l)
- r = strlen (argv[op + 2]);
- else
- {
- if (!isint (argv[op + 1], &r))
- integer_expected_error (_("after -eq"));
- }
- pos += 3;
- return (TRUE == (l == r));
- }
-
if (argv[op][2] == 'f' && !argv[op][3])
{
/* ef - hard link? */
pos += 3;
- if (l_is_l || r_is_l)
- test_syntax_error (_("-ef does not accept -l\n"), NULL);
- if (stat (argv[op - 1], &stat_buf) < 0)
- return (FALSE);
- if (stat (argv[op + 1], &stat_spare) < 0)
- return (FALSE);
- return (TRUE ==
- (stat_buf.st_dev == stat_spare.st_dev &&
- stat_buf.st_ino == stat_spare.st_ino));
+ if (l_is_l | r_is_l)
+ test_syntax_error (_("-ef does not accept -l"), NULL);
+ return (stat (argv[op - 1], &stat_buf) == 0
+ && stat (argv[op + 1], &stat_spare) == 0
+ && stat_buf.st_dev == stat_spare.st_dev
+ && stat_buf.st_ino == stat_spare.st_ino);
}
break;
if ('t' == argv[op][2] && '\000' == argv[op][3])
{
/* ot - older than */
- time_t lt, rt;
+ struct timespec lt, rt;
+ bool le, re;
pos += 3;
- if (l_is_l || r_is_l)
- test_syntax_error (_("-ot does not accept -l\n"), NULL);
- if (age_of (argv[op - 1], <) && age_of (argv[op + 1], &rt))
- return (TRUE == (lt < rt));
- return (FALSE);
+ if (l_is_l | r_is_l)
+ test_syntax_error (_("-ot does not accept -l"), NULL);
+ le = get_mtime (argv[op - 1], <);
+ re = get_mtime (argv[op + 1], &rt);
+ return re && (!le || timespec_cmp (lt, rt) < 0);
}
break;
}
+
+ /* FIXME: is this dead code? */
test_syntax_error (_("unknown binary operator"), argv[op]);
}
if (argv[op][0] == '=' && !argv[op][1])
{
- value = (strcmp (argv[pos], argv[pos + 2]) == 0);
+ bool value = STREQ (argv[pos], argv[pos + 2]);
pos += 3;
- return (TRUE == value);
+ return value;
}
- if (strcmp (argv[op], "!=") == 0)
+ if (STREQ (argv[op], "!="))
{
- value = (strcmp (argv[pos], argv[pos + 2]) != 0);
+ bool value = !STREQ (argv[pos], argv[pos + 2]);
pos += 3;
- return (TRUE == value);
+ return value;
}
/* Not reached. */
abort ();
}
-static int
+static bool
unary_operator (void)
{
- int value;
struct stat stat_buf;
switch (argv[pos][1])
{
default:
- return (FALSE);
+ return false;
/* All of the following unary operators use unary_advance (), which
checks to make sure that there is an argument, and then advances
case 'a': /* file exists in the file system? */
case 'e':
unary_advance ();
- value = -1 != test_stat (argv[pos - 1], &stat_buf);
- return (TRUE == value);
+ return stat (argv[pos - 1], &stat_buf) == 0;
case 'r': /* file is readable? */
unary_advance ();
- value = -1 != eaccess (argv[pos - 1], R_OK);
- return (TRUE == value);
+ return euidaccess (argv[pos - 1], R_OK) == 0;
case 'w': /* File is writable? */
unary_advance ();
- value = -1 != eaccess (argv[pos - 1], W_OK);
- return (TRUE == value);
+ return euidaccess (argv[pos - 1], W_OK) == 0;
case 'x': /* File is executable? */
unary_advance ();
- value = -1 != eaccess (argv[pos - 1], X_OK);
- return (TRUE == value);
+ return euidaccess (argv[pos - 1], X_OK) == 0;
case 'O': /* File is owned by you? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (geteuid () == stat_buf.st_uid));
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && (geteuid () == stat_buf.st_uid));
case 'G': /* File is owned by your group? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (getegid () == stat_buf.st_gid));
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && (getegid () == stat_buf.st_gid));
case 'f': /* File is a file? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
/* Under POSIX, -f is true if the given file exists
and is a regular file. */
- return (TRUE == ((S_ISREG (stat_buf.st_mode)) ||
- (0 == (stat_buf.st_mode & S_IFMT))));
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && S_ISREG (stat_buf.st_mode));
case 'd': /* File is a directory? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (S_ISDIR (stat_buf.st_mode)));
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && S_ISDIR (stat_buf.st_mode));
case 's': /* File has something in it? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (stat_buf.st_size > (off_t) 0));
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && 0 < stat_buf.st_size);
case 'S': /* File is a socket? */
-#if !defined (S_ISSOCK)
- return (FALSE);
-#else
unary_advance ();
-
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (S_ISSOCK (stat_buf.st_mode)));
-#endif /* S_ISSOCK */
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && S_ISSOCK (stat_buf.st_mode));
case 'c': /* File is character special? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (S_ISCHR (stat_buf.st_mode)));
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && S_ISCHR (stat_buf.st_mode));
case 'b': /* File is block special? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (S_ISBLK (stat_buf.st_mode)));
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && S_ISBLK (stat_buf.st_mode));
case 'p': /* File is a named pipe? */
unary_advance ();
-#ifndef S_ISFIFO
- return (FALSE);
-#else
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
- return (TRUE == (S_ISFIFO (stat_buf.st_mode)));
-#endif /* S_ISFIFO */
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && S_ISFIFO (stat_buf.st_mode));
case 'L': /* Same as -h */
/*FALLTHROUGH*/
case 'h': /* File is a symbolic link? */
unary_advance ();
-#ifndef S_ISLNK
- return (FALSE);
-#else
- /* An empty filename is not a valid pathname. */
- if ((argv[pos - 1][0] == '\0') ||
- (lstat (argv[pos - 1], &stat_buf) < 0))
- return (FALSE);
-
- return (TRUE == (S_ISLNK (stat_buf.st_mode)));
-#endif /* S_IFLNK */
+ return (lstat (argv[pos - 1], &stat_buf) == 0
+ && S_ISLNK (stat_buf.st_mode));
case 'u': /* File is setuid? */
unary_advance ();
-#ifndef S_ISUID
- return (FALSE);
-#else
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (0 != (stat_buf.st_mode & S_ISUID)));
-#endif
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && (stat_buf.st_mode & S_ISUID));
case 'g': /* File is setgid? */
unary_advance ();
-#ifndef S_ISGID
- return (FALSE);
-#else
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-
- return (TRUE == (0 != (stat_buf.st_mode & S_ISGID)));
-#endif
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && (stat_buf.st_mode & S_ISGID));
case 'k': /* File has sticky bit set? */
unary_advance ();
- if (test_stat (argv[pos - 1], &stat_buf) < 0)
- return (FALSE);
-#ifndef S_ISVTX
- /* This is not Posix, and is not defined on some Posix systems. */
- return (FALSE);
-#else
- return (TRUE == (0 != (stat_buf.st_mode & S_ISVTX)));
-#endif
+ return (stat (argv[pos - 1], &stat_buf) == 0
+ && (stat_buf.st_mode & S_ISVTX));
case 't': /* File (fd) is a terminal? */
{
- intmax_t fd;
- advance (0);
- if (pos < argc)
- {
- if (!isint (argv[pos], &fd))
- integer_expected_error (_("after -t"));
- advance (0);
- }
- else
- {
- fd = 1;
- }
- return (TRUE == (fd == (int) fd && isatty (fd)));
+ long int fd;
+ char const *arg;
+ unary_advance ();
+ arg = find_int (argv[pos - 1]);
+ errno = 0;
+ fd = strtol (arg, NULL, 10);
+ return (errno != ERANGE && 0 <= fd && fd <= INT_MAX && isatty (fd));
}
case 'n': /* True if arg has some length. */
unary_advance ();
- return (TRUE == (argv[pos - 1][0] != 0));
+ return argv[pos - 1][0] != 0;
case 'z': /* True if arg has no length. */
unary_advance ();
- return (TRUE == (argv[pos - 1][0] == '\0'));
+ return argv[pos - 1][0] == '\0';
}
}
* term
* term '-a' and
*/
-static int
+static bool
and (void)
{
- int value;
+ bool value = true;
- value = term ();
- while ((pos < argc) && strcmp (argv[pos], "-a") == 0)
+ for (;;)
{
- advance (0);
- value = TRUTH_AND (value, and ());
+ value &= term ();
+ if (! (pos < argc && STREQ (argv[pos], "-a")))
+ return value;
+ advance (false);
}
- return (TRUE == value);
}
/*
* and
* and '-o' or
*/
-static int
+static bool
or (void)
{
- int value;
-
- value = and ();
+ bool value = false;
- while ((pos < argc) && strcmp (argv[pos], "-o") == 0)
+ for (;;)
{
- advance (0);
- value = TRUTH_OR (value, or ());
+ value |= and ();
+ if (! (pos < argc && STREQ (argv[pos], "-o")))
+ return value;
+ advance (false);
}
-
- return (TRUE == value);
}
/*
* expr:
* or
*/
-static int
+static bool
expr (void)
{
if (pos >= argc)
beyond ();
- return (FALSE ^ (or ())); /* Same with this. */
+ return or (); /* Same with this. */
}
-/* Return TRUE if S is one of the test command's binary operators. */
-static int
-binop (char *s)
+/* Return true if OP is one of the test command's unary operators. */
+static bool
+test_unop (char const *op)
{
- return ((STREQ (s, "=")) || (STREQ (s, "!=")) || (STREQ (s, "-nt")) ||
- (STREQ (s, "-ot")) || (STREQ (s, "-ef")) || (STREQ (s, "-eq")) ||
- (STREQ (s, "-ne")) || (STREQ (s, "-lt")) || (STREQ (s, "-le")) ||
- (STREQ (s, "-gt")) || (STREQ (s, "-ge")));
-}
+ if (op[0] != '-')
+ return false;
-/* Return nonzero if OP is one of the test command's unary operators. */
-static int
-unop (int op)
-{
- return (member (op, "abcdefgkLhprsStuwxOGnz"));
+ switch (op[1])
+ {
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f': case 'g': case 'h': case 'k': case 'n':
+ case 'o': case 'p': case 'r': case 's': case 't':
+ case 'u': case 'w': case 'x': case 'z':
+ case 'G': case 'L': case 'O': case 'S': case 'N':
+ return true;
+ }
+
+ return false;
}
-static int
-one_argument (const char *s)
+static bool
+one_argument (void)
{
- if (STREQ (s, "-t"))
- return (TRUE == (isatty (1)));
-
- return strlen (s) != 0;
+ return argv[pos++][0] != '\0';
}
-static int
+static bool
two_arguments (void)
{
- int value;
+ bool value;
if (STREQ (argv[pos], "!"))
- value = ! one_argument (argv[pos+1]);
+ {
+ advance (false);
+ value = ! one_argument ();
+ }
else if (argv[pos][0] == '-'
&& argv[pos][1] != '\0'
&& argv[pos][2] == '\0')
{
- if (unop (argv[pos][1]))
+ if (test_unop (argv[pos]))
value = unary_operator ();
else
- test_syntax_error (_("%s: unary operator expected\n"), argv[pos]);
+ test_syntax_error (_("%s: unary operator expected"), argv[pos]);
}
else
beyond ();
return (value);
}
-static int
+static bool
three_arguments (void)
{
- int value;
+ bool value;
- if (STREQ (argv[pos], "!"))
+ if (binop (argv[pos + 1]))
+ value = binary_operator (false);
+ else if (STREQ (argv[pos], "!"))
{
- advance (1);
+ advance (true);
value = !two_arguments ();
}
- else if (binop (argv[pos+1]))
+ else if (STREQ (argv[pos], "(") && STREQ (argv[pos + 2], ")"))
{
- value = binary_operator ();
- pos = argc;
+ advance (false);
+ value = one_argument ();
+ advance (false);
}
- else if ((STREQ (argv[pos+1], "-a")) || (STREQ (argv[pos+1], "-o")) ||
- (argv[pos][0] == '('))
+ else if (STREQ (argv[pos + 1], "-a") || STREQ (argv[pos + 1], "-o"))
value = expr ();
else
- test_syntax_error (_("%s: binary operator expected\n"), argv[pos+1]);
+ test_syntax_error (_("%s: binary operator expected"), argv[pos+1]);
return (value);
}
/* This is an implementation of a Posix.2 proposal by David Korn. */
-static int
-posixtest (void)
+static bool
+posixtest (int nargs)
{
- int value;
+ bool value;
- switch (argc - 1) /* one extra passed in */
+ switch (nargs)
{
- case 0:
- value = FALSE;
- pos = argc;
- break;
-
case 1:
- value = one_argument (argv[1]);
- pos = argc;
+ value = one_argument ();
break;
case 2:
value = two_arguments ();
- pos = argc;
break;
case 3:
case 4:
if (STREQ (argv[pos], "!"))
{
- advance (1);
+ advance (true);
value = !three_arguments ();
break;
}
+ if (STREQ (argv[pos], "(") && STREQ (argv[pos + 3], ")"))
+ {
+ advance (false);
+ value = two_arguments ();
+ advance (false);
+ break;
+ }
/* FALLTHROUGH */
case 5:
default:
+ if (nargs <= 0)
+ abort ();
value = expr ();
}
return (value);
}
-#if defined (TEST_STANDALONE)
+#if defined TEST_STANDALONE
# include "long-options.h"
-# include "closeout.h"
void
usage (int status)
{
- if (status != 0)
+ if (status != EXIT_SUCCESS)
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
else
{
- printf (_("\
-Usage: %s EXPRESSION\n\
+ fputs (_("\
+Usage: test EXPRESSION\n\
+ or: test\n\
or: [ EXPRESSION ]\n\
- or: %s OPTION\n\
-"),
- program_name, program_name);
- printf (_("\
+ or: [ ]\n\
+ or: [ OPTION\n\
+"), stdout);
+ fputs (_("\
Exit with the status determined by EXPRESSION.\n\
\n\
- --help display this help and exit\n\
- --version output version information and exit\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ fputs (_("\
\n\
+An omitted EXPRESSION defaults to false. Otherwise,\n\
EXPRESSION is true or false and sets exit status. It is one of:\n\
-"));
- printf (_("\
+"), stdout);
+ fputs (_("\
\n\
( EXPRESSION ) EXPRESSION is true\n\
! EXPRESSION EXPRESSION is false\n\
EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true\n\
EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true\n\
+"), stdout);
+ fputs (_("\
\n\
- [-n] STRING the length of STRING is nonzero\n\
+ -n STRING the length of STRING is nonzero\n\
+ STRING equivalent to -n STRING\n\
-z STRING the length of STRING is zero\n\
STRING1 = STRING2 the strings are equal\n\
STRING1 != STRING2 the strings are not equal\n\
+"), stdout);
+ fputs (_("\
\n\
INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2\n\
INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2\n\
INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2\n\
INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2\n\
INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2\n\
-"));
- printf (_("\
+"), stdout);
+ fputs (_("\
\n\
FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers\n\
FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2\n\
FILE1 -ot FILE2 FILE1 is older than FILE2\n\
+"), stdout);
+ fputs (_("\
\n\
-b FILE FILE exists and is block special\n\
-c FILE FILE exists and is character special\n\
-d FILE FILE exists and is a directory\n\
-e FILE FILE exists\n\
+"), stdout);
+ fputs (_("\
-f FILE FILE exists and is a regular file\n\
-g FILE FILE exists and is set-group-ID\n\
- -h FILE FILE exists and is a symbolic link (same as -L)\n\
-G FILE FILE exists and is owned by the effective group ID\n\
+ -h FILE FILE exists and is a symbolic link (same as -L)\n\
-k FILE FILE exists and has its sticky bit set\n\
+"), stdout);
+ fputs (_("\
-L FILE FILE exists and is a symbolic link (same as -h)\n\
-O FILE FILE exists and is owned by the effective user ID\n\
-p FILE FILE exists and is a named pipe\n\
- -r FILE FILE exists and is readable\n\
+ -r FILE FILE exists and read permission is granted\n\
-s FILE FILE exists and has a size greater than zero\n\
+"), stdout);
+ fputs (_("\
-S FILE FILE exists and is a socket\n\
- -t [FD] file descriptor FD (stdout by default) is opened on a terminal\n\
+ -t FD file descriptor FD is opened on a terminal\n\
-u FILE FILE exists and its set-user-ID bit is set\n\
- -w FILE FILE exists and is writable\n\
- -x FILE FILE exists and is executable\n\
-"));
- printf (_("\
+ -w FILE FILE exists and write permission is granted\n\
+ -x FILE FILE exists and execute (or search) permission is granted\n\
+"), stdout);
+ fputs (_("\
\n\
+Except for -h and -L, all FILE-related tests dereference symbolic links.\n\
Beware that parentheses need to be escaped (e.g., by backslashes) for shells.\n\
INTEGER may also be -l STRING, which evaluates to the length of STRING.\n\
-"));
- puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
+"), stdout);
+ fputs (_("\
+\n\
+NOTE: [ honors the --help and --version options, but test does not.\n\
+test treats each of those as it treats any other nonempty STRING.\n\
+"), stdout);
+ printf (USAGE_BUILTIN_WARNING, _("test and/or ["));
+ emit_bug_reporting_address ();
}
exit (status);
}
#endif /* TEST_STANDALONE */
-#if !defined (TEST_STANDALONE)
+#if !defined TEST_STANDALONE
# define main test_command
#endif
-#define AUTHORS N_ ("FIXME: ksb and mjb")
+#define AUTHORS \
+ proper_name ("Kevin Braunsdorf"), \
+ proper_name ("Matthew Bradburn")
/*
* [:
int
main (int margc, char **margv)
{
- int value;
+ bool value;
-#if !defined (TEST_STANDALONE)
+#if !defined TEST_STANDALONE
int code;
code = setjmp (test_exit_buf);
if (code)
return (test_error_return);
#else /* TEST_STANDALONE */
- program_name = margv[0];
+ initialize_main (&margc, &margv);
+ set_program_name (margv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
+ initialize_exit_failure (TEST_FAILURE);
atexit (close_stdout);
#endif /* TEST_STANDALONE */
argv = margv;
- if (margv[0] && strcmp (margv[0], "[") == 0)
+ if (LBRACKET)
{
- /* Don't recognize --help or --version if POSIXLY_CORRECT is set. */
- if (getenv ("POSIXLY_CORRECT") == NULL)
- parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
- AUTHORS, usage);
+ /* Recognize --help or --version, but only when invoked in the
+ "[" form, and when the last argument is not "]". POSIX
+ allows "[ --help" and "[ --version" to have the usual GNU
+ behavior, but it requires "test --help" and "test --version"
+ to exit silently with status 0. */
+ if (margc < 2 || !STREQ (margv[margc - 1], "]"))
+ {
+ parse_long_options (margc, margv, PROGRAM_NAME, PACKAGE_NAME, VERSION,
+ usage, AUTHORS, (char const *) NULL);
+ test_syntax_error (_("missing `]'"), NULL);
+ }
--margc;
-
- if (margc < 2)
- test_exit (SHELL_BOOLEAN (FALSE));
-
- if (margv[margc] && strcmp (margv[margc], "]") != 0)
- test_syntax_error (_("missing `]'\n"), NULL);
}
argc = margc;
pos = 1;
if (pos >= argc)
- test_exit (SHELL_BOOLEAN (FALSE));
+ test_exit (TEST_FALSE);
- parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
- AUTHORS, usage);
- value = posixtest ();
+ value = posixtest (argc - 1);
if (pos != argc)
- test_syntax_error (_("too many arguments\n"), NULL);
+ test_syntax_error (_("extra argument %s"), quote (argv[pos]));
- test_exit (SHELL_BOOLEAN (value));
+ test_exit (value ? TEST_TRUE : TEST_FALSE);
}