summaryrefslogtreecommitdiff
path: root/src/test.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/test.c')
-rw-r--r--src/test.c423
1 files changed, 227 insertions, 196 deletions
diff --git a/src/test.c b/src/test.c
index b25436b..36817d2 100644
--- a/src/test.c
+++ b/src/test.c
@@ -2,27 +2,30 @@
/* Modified to run with the GNU shell by bfox. */
-/* Copyright (C) 1987-2005 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2016 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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. */
+/* Without this pragma, gcc 4.6.2 20111027 mistakenly suggests that
+ the advance function might be candidate for attribute 'pure'. */
+#if (__GNUC__ == 4 && 6 <= __GNUC_MINOR__) || 4 < __GNUC__
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
+#endif
+
#include <config.h>
#include <stdio.h>
#include <sys/types.h>
@@ -33,7 +36,7 @@
# define LBRACKET 0
#endif
-/* The official name of this program (e.g., no `g' prefix). */
+/* The official name of this program (e.g., no 'g' prefix). */
#if LBRACKET
# define PROGRAM_NAME "["
#else
@@ -41,28 +44,28 @@
#endif
#include "system.h"
-#include "error.h"
-#include "euidaccess.h"
-#include "inttostr.h"
#include "quote.h"
#include "stat-time.h"
#include "strnumcmp.h"
+#include <stdarg.h>
+#include "verror.h"
+
#if HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
-char *program_name;
-
/* Exit status for syntax errors, etc. */
enum { TEST_TRUE, TEST_FALSE, TEST_FAILURE };
#if defined TEST_STANDALONE
# define test_exit(val) exit (val)
+# define test_main_return(val) return val
#else
static jmp_buf test_exit_buf;
static int test_error_return = 0;
# define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1)
+# define test_main_return(val) test_exit (val)
#endif /* !TEST_STANDALONE */
static int pos; /* The offset of the current argument in ARGV. */
@@ -81,25 +84,24 @@ static bool term (void);
static bool and (void);
static bool or (void);
-static void test_syntax_error (char const *format, char const *arg)
+static void test_syntax_error (char const *format, ...)
ATTRIBUTE_NORETURN;
static void beyond (void) ATTRIBUTE_NORETURN;
static void
-test_syntax_error (char const *format, char const *arg)
+test_syntax_error (char const *format, ...)
{
- fprintf (stderr, "%s: ", argv[0]);
- fprintf (stderr, format, arg);
- fputc ('\n', stderr);
- fflush (stderr);
+ va_list ap;
+ va_start (ap, format);
+ verror (0, 0, format, ap);
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
+ past the end of the argument list. This check is suppressed if the
argument is false. */
-static inline void
+static void
advance (bool f)
{
++pos;
@@ -108,7 +110,7 @@ advance (bool f)
beyond ();
}
-static inline void
+static void
unary_advance (void)
{
advance (true);
@@ -151,11 +153,11 @@ find_int (char const *string)
if (ISDIGIT (*p++))
{
while (ISDIGIT (*p))
- p++;
+ p++;
while (isblank (to_uchar (*p)))
- p++;
+ p++;
if (!*p)
- return number_start;
+ return number_start;
}
test_syntax_error (_("invalid integer %s"), quote (string));
@@ -181,10 +183,11 @@ get_mtime (char const *filename, struct timespec *mtime)
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")));
+ return ((STREQ (s, "=")) || (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")));
}
/*
@@ -211,7 +214,7 @@ term (void)
bool value;
bool negated = false;
- /* Deal with leading `not's. */
+ /* Deal with leading 'not's. */
while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0')
{
advance (true);
@@ -229,20 +232,21 @@ term (void)
advance (true);
for (nargs = 1;
- pos + nargs < argc && ! STREQ (argv[pos + nargs], ")");
- nargs++)
- if (nargs == 4)
- {
- nargs = argc - pos;
- break;
- }
+ 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);
+ test_syntax_error (_("%s expected"), quote (")"));
else
if (argv[pos][0] != ')' || argv[pos][1])
- test_syntax_error (_("')' expected, found %s"), argv[pos]);
+ test_syntax_error (_("%s expected, found %s"),
+ quote_n (0, ")"), quote_n (1, argv[pos]));
advance (false);
}
@@ -256,9 +260,9 @@ term (void)
else if (argv[pos][0] == '-' && argv[pos][1] && argv[pos][2] == '\0')
{
if (test_unop (argv[pos]))
- value = unary_operator ();
+ value = unary_operator ();
else
- test_syntax_error (_("%s: unary operator expected"), argv[pos]);
+ test_syntax_error (_("%s: unary operator expected"), quote (argv[pos]));
}
else
{
@@ -293,82 +297,83 @@ binary_operator (bool l_is_l)
{
/* 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);
- }
+ && (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 'n':
- if (argv[op][2] == 't' && !argv[op][3])
- {
- /* nt - newer than */
- struct timespec lt, rt;
- bool le, re;
- pos += 3;
- if (l_is_l | r_is_l)
- test_syntax_error (_("-nt does not accept -l"), NULL);
- le = get_mtime (argv[op - 1], &lt);
- re = get_mtime (argv[op + 1], &rt);
- return le && (!re || timespec_cmp (lt, rt) > 0);
- }
- break;
-
- case 'e':
- 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"), 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;
-
- case 'o':
- if ('t' == argv[op][2] && '\000' == argv[op][3])
- {
- /* ot - older than */
- struct timespec lt, rt;
- bool le, re;
- pos += 3;
- if (l_is_l | r_is_l)
- test_syntax_error (_("-ot does not accept -l"), NULL);
- le = get_mtime (argv[op - 1], &lt);
- re = get_mtime (argv[op + 1], &rt);
- return re && (!le || timespec_cmp (lt, rt) < 0);
- }
- break;
- }
+ {
+ default:
+ break;
+
+ case 'n':
+ if (argv[op][2] == 't' && !argv[op][3])
+ {
+ /* nt - newer than */
+ struct timespec lt, rt;
+ bool le, re;
+ pos += 3;
+ if (l_is_l || r_is_l)
+ test_syntax_error (_("-nt does not accept -l"), NULL);
+ le = get_mtime (argv[op - 1], &lt);
+ re = get_mtime (argv[op + 1], &rt);
+ return le && (!re || timespec_cmp (lt, rt) > 0);
+ }
+ break;
+
+ case 'e':
+ 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"), 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;
+
+ case 'o':
+ if ('t' == argv[op][2] && '\000' == argv[op][3])
+ {
+ /* ot - older than */
+ struct timespec lt, rt;
+ bool le, re;
+ pos += 3;
+ if (l_is_l || r_is_l)
+ test_syntax_error (_("-ot does not accept -l"), NULL);
+ le = get_mtime (argv[op - 1], &lt);
+ 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]);
+ test_syntax_error (_("%s: unknown binary operator"), quote (argv[op]));
}
- if (argv[op][0] == '=' && !argv[op][1])
+ if (argv[op][0] == '='
+ && (!argv[op][1] || ((argv[op][1] == '=') && !argv[op][2])))
{
bool value = STREQ (argv[pos], argv[pos + 2]);
pos += 3;
@@ -397,9 +402,9 @@ unary_operator (void)
return false;
/* All of the following unary operators use unary_advance (), which
- checks to make sure that there is an argument, and then advances
- pos right past it. This means that pos - 1 is the location of the
- argument. */
+ checks to make sure that there is an argument, and then advances
+ pos right past it. This means that pos - 1 is the location of the
+ argument. */
case 'a': /* file exists in the file system? */
case 'e':
@@ -419,51 +424,63 @@ unary_operator (void)
return euidaccess (argv[pos - 1], X_OK) == 0;
case 'O': /* File is owned by you? */
- unary_advance ();
- return (stat (argv[pos - 1], &stat_buf) == 0
- && (geteuid () == stat_buf.st_uid));
+ {
+ unary_advance ();
+ if (stat (argv[pos - 1], &stat_buf) != 0)
+ return false;
+ errno = 0;
+ uid_t euid = geteuid ();
+ uid_t NO_UID = -1;
+ return ! (euid == NO_UID && errno) && euid == stat_buf.st_uid;
+ }
case 'G': /* File is owned by your group? */
- unary_advance ();
- return (stat (argv[pos - 1], &stat_buf) == 0
- && (getegid () == stat_buf.st_gid));
+ {
+ unary_advance ();
+ if (stat (argv[pos - 1], &stat_buf) != 0)
+ return false;
+ errno = 0;
+ gid_t egid = getegid ();
+ gid_t NO_GID = -1;
+ return ! (egid == NO_GID && errno) && egid == stat_buf.st_gid;
+ }
case 'f': /* File is a file? */
unary_advance ();
/* Under POSIX, -f is true if the given file exists
- and is a regular file. */
+ and is a regular file. */
return (stat (argv[pos - 1], &stat_buf) == 0
- && S_ISREG (stat_buf.st_mode));
+ && S_ISREG (stat_buf.st_mode));
case 'd': /* File is a directory? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && S_ISDIR (stat_buf.st_mode));
+ && S_ISDIR (stat_buf.st_mode));
case 's': /* File has something in it? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && 0 < stat_buf.st_size);
+ && 0 < stat_buf.st_size);
case 'S': /* File is a socket? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && S_ISSOCK (stat_buf.st_mode));
+ && S_ISSOCK (stat_buf.st_mode));
case 'c': /* File is character special? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && S_ISCHR (stat_buf.st_mode));
+ && S_ISCHR (stat_buf.st_mode));
case 'b': /* File is block special? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && S_ISBLK (stat_buf.st_mode));
+ && S_ISBLK (stat_buf.st_mode));
case 'p': /* File is a named pipe? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && S_ISFIFO (stat_buf.st_mode));
+ && S_ISFIFO (stat_buf.st_mode));
case 'L': /* Same as -h */
/*FALLTHROUGH*/
@@ -471,32 +488,32 @@ unary_operator (void)
case 'h': /* File is a symbolic link? */
unary_advance ();
return (lstat (argv[pos - 1], &stat_buf) == 0
- && S_ISLNK (stat_buf.st_mode));
+ && S_ISLNK (stat_buf.st_mode));
case 'u': /* File is setuid? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && (stat_buf.st_mode & S_ISUID));
+ && (stat_buf.st_mode & S_ISUID));
case 'g': /* File is setgid? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && (stat_buf.st_mode & S_ISGID));
+ && (stat_buf.st_mode & S_ISGID));
case 'k': /* File has sticky bit set? */
unary_advance ();
return (stat (argv[pos - 1], &stat_buf) == 0
- && (stat_buf.st_mode & S_ISVTX));
+ && (stat_buf.st_mode & S_ISVTX));
case 't': /* File (fd) is a terminal? */
{
- 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));
+ 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. */
@@ -519,11 +536,11 @@ and (void)
{
bool value = true;
- for (;;)
+ while (true)
{
value &= term ();
if (! (pos < argc && STREQ (argv[pos], "-a")))
- return value;
+ return value;
advance (false);
}
}
@@ -538,11 +555,11 @@ or (void)
{
bool value = false;
- for (;;)
+ while (true)
{
value |= and ();
if (! (pos < argc && STREQ (argv[pos], "-o")))
- return value;
+ return value;
advance (false);
}
}
@@ -575,9 +592,9 @@ test_unop (char const *op)
case 'u': case 'w': case 'x': case 'z':
case 'G': case 'L': case 'O': case 'S': case 'N':
return true;
+ default:
+ return false;
}
-
- return false;
}
static bool
@@ -597,13 +614,13 @@ two_arguments (void)
value = ! one_argument ();
}
else if (argv[pos][0] == '-'
- && argv[pos][1] != '\0'
- && argv[pos][2] == '\0')
+ && argv[pos][1] != '\0'
+ && argv[pos][2] == '\0')
{
if (test_unop (argv[pos]))
- value = unary_operator ();
+ value = unary_operator ();
else
- test_syntax_error (_("%s: unary operator expected"), argv[pos]);
+ test_syntax_error (_("%s: unary operator expected"), quote (argv[pos]));
}
else
beyond ();
@@ -631,7 +648,7 @@ three_arguments (void)
else if (STREQ (argv[pos + 1], "-a") || STREQ (argv[pos + 1], "-o"))
value = expr ();
else
- test_syntax_error (_("%s: binary operator expected"), argv[pos+1]);
+ test_syntax_error (_("%s: binary operator expected"), quote (argv[pos+1]));
return (value);
}
@@ -644,51 +661,49 @@ posixtest (int nargs)
switch (nargs)
{
case 1:
- value = one_argument ();
- break;
+ value = one_argument ();
+ break;
case 2:
- value = two_arguments ();
- break;
+ value = two_arguments ();
+ break;
case 3:
- value = three_arguments ();
- break;
+ value = three_arguments ();
+ break;
case 4:
- if (STREQ (argv[pos], "!"))
- {
- advance (true);
- value = !three_arguments ();
- break;
- }
- if (STREQ (argv[pos], "(") && STREQ (argv[pos + 3], ")"))
- {
- advance (false);
- value = two_arguments ();
- advance (false);
- break;
- }
- /* FALLTHROUGH */
+ if (STREQ (argv[pos], "!"))
+ {
+ 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 ();
+ if (nargs <= 0)
+ abort ();
+ value = expr ();
}
return (value);
}
#if defined TEST_STANDALONE
-# include "long-options.h"
void
usage (int status)
{
if (status != EXIT_SUCCESS)
- fprintf (stderr, _("Try `%s --help' for more information.\n"),
- program_name);
+ emit_try_help ();
else
{
fputs (_("\
@@ -773,8 +788,13 @@ 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\
"), 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 ["));
- printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ emit_ancillary_info (PROGRAM_NAME);
}
exit (status);
}
@@ -784,7 +804,9 @@ INTEGER may also be -l STRING, which evaluates to the length of STRING.\n\
# define main test_command
#endif
-#define AUTHORS "Kevin Braunsdorf", "Matthew Bradburn"
+#define AUTHORS \
+ proper_name ("Kevin Braunsdorf"), \
+ proper_name ("Matthew Bradburn")
/*
* [:
@@ -806,7 +828,7 @@ main (int margc, char **margv)
return (test_error_return);
#else /* TEST_STANDALONE */
initialize_main (&margc, &margv);
- program_name = margv[0];
+ set_program_name (margv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
@@ -820,16 +842,25 @@ main (int margc, char **margv)
if (LBRACKET)
{
/* 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. */
+ "[" form, when the last argument is not "]". Use direct
+ parsing, rather than parse_long_options, to avoid accepting
+ abbreviations. 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)
+ {
+ if (STREQ (margv[1], "--help"))
+ usage (EXIT_SUCCESS);
+
+ if (STREQ (margv[1], "--version"))
+ {
+ version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
+ (char *) NULL);
+ test_main_return (EXIT_SUCCESS);
+ }
+ }
if (margc < 2 || !STREQ (margv[margc - 1], "]"))
- {
- parse_long_options (margc, margv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
- usage, AUTHORS, (char const *) NULL);
- test_syntax_error (_("missing `]'"), NULL);
- }
+ test_syntax_error (_("missing %s"), quote ("]"));
--margc;
}
@@ -838,12 +869,12 @@ main (int margc, char **margv)
pos = 1;
if (pos >= argc)
- test_exit (TEST_FALSE);
+ test_main_return (TEST_FALSE);
value = posixtest (argc - 1);
if (pos != argc)
test_syntax_error (_("extra argument %s"), quote (argv[pos]));
- test_exit (value ? TEST_TRUE : TEST_FALSE);
+ test_main_return (value ? TEST_TRUE : TEST_FALSE);
}