diff options
Diffstat (limited to 'src/test.c')
-rw-r--r-- | src/test.c | 423 |
1 files changed, 227 insertions, 196 deletions
@@ -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], <); - 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], <); - 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], <); + 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], <); + 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); } |