dd: clarify meaning of multiplication factors; put xM in order
[platform/upstream/coreutils.git] / src / test.c
index a0cfefa..833b254 100644 (file)
 
 /* 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;
@@ -109,112 +62,51 @@ static int pos;           /* The offset of the current argument in ARGV. */
 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
@@ -223,89 +115,69 @@ advance (int f)
 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")));
 }
 
 /*
@@ -315,7 +187,7 @@ age_of (char *filename, time_t *age)
  * 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
@@ -326,266 +198,146 @@ age_of (char *filename, time_t *age)
  *     '-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], &lt) && 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], &lt);
+             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;
 
@@ -593,47 +345,49 @@ binary_operator (void)
          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], &lt) && 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], &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]);
     }
 
   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
@@ -643,171 +397,108 @@ unary_operator (void)
     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';
     }
 }
 
@@ -816,18 +507,18 @@ unary_operator (void)
  *     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);
 }
 
 /*
@@ -835,126 +526,122 @@ and (void)
  *     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:
@@ -964,56 +651,73 @@ posixtest (void)
       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\
@@ -1021,49 +725,66 @@ EXPRESSION is true or false and sets exit status.  It is one of:\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")
 
 /*
  * [:
@@ -1074,9 +795,9 @@ INTEGER may also be -l STRING, which evaluates to the length of STRING.\n\
 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);
@@ -1084,44 +805,45 @@ main (int margc, char **margv)
   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);
 }