Include stat-time.h, and use its functions instead of the obsolete
[platform/upstream/coreutils.git] / src / cp.c
index 739cc5f..4e08612 100644 (file)
--- a/src/cp.c
+++ b/src/cp.c
@@ -1,5 +1,5 @@
 /* cp.c  -- file copying (main routines)
-   Copyright (C) 89, 90, 91, 1995-2004 Free Software Foundation.
+   Copyright (C) 89, 90, 91, 1995-2005 Free Software Foundation.
 
    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
@@ -13,7 +13,7 @@
 
    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.
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
    Written by Torbjorn Granlund, David MacKenzie, and Jim Meyering. */
 
 #include "cp-hash.h"
 #include "error.h"
 #include "dirname.h"
-#include "path-concat.h"
+#include "filenamecat.h"
 #include "quote.h"
+#include "quotearg.h"
+#include "stat-time.h"
 #include "utimens.h"
 
 #define ASSIGN_BASENAME_STRDUPA(Dest, File_name)       \
 
 #define AUTHORS "Torbjorn Granlund", "David MacKenzie", "Jim Meyering"
 
-#ifndef _POSIX_VERSION
-uid_t geteuid ();
-#endif
-
-/* Used by do_copy, make_path_private, and re_protect
+/* Used by do_copy, make_dir_parents_private, and re_protect
    to keep a list of leading directories whose protections
    need to be fixed after copying. */
 struct dir_attr
 {
-  int is_new_dir;
-  int slash_offset;
+  bool is_new_dir;
+  size_t slash_offset;
   struct dir_attr *next;
 };
 
@@ -74,7 +72,6 @@ enum
   REPLY_OPTION,
   SPARSE_OPTION,
   STRIP_TRAILING_SLASHES_OPTION,
-  TARGET_DIRECTORY_OPTION,
   UNLINK_DEST_BEFORE_OPENING
 };
 
@@ -87,34 +84,34 @@ enum
 /* The invocation name of this program.  */
 char *program_name;
 
-/* If nonzero, the command "cp x/e_file e_dir" uses "e_dir/x/e_file"
+/* If true, the command "cp x/e_file e_dir" uses "e_dir/x/e_file"
    as its destination instead of the usual "e_dir/e_file." */
-static int flag_path = 0;
+static bool parents_option = false;
 
 /* Remove any trailing slashes from each SOURCE argument.  */
-static int remove_trailing_slashes;
+static bool remove_trailing_slashes;
 
 static char const *const sparse_type_string[] =
 {
-  "never", "auto", "always", 0
+  "never", "auto", "always", NULL
 };
-
 static enum Sparse_type const sparse_type[] =
 {
   SPARSE_NEVER, SPARSE_AUTO, SPARSE_ALWAYS
 };
+ARGMATCH_VERIFY (sparse_type_string, sparse_type);
 
 /* Valid arguments to the `--reply' option. */
 static char const* const reply_args[] =
 {
-  "yes", "no", "query", 0
+  "yes", "no", "query", NULL
 };
-
 /* The values that correspond to the above strings. */
 static int const reply_vals[] =
 {
   I_ALWAYS_YES, I_ALWAYS_NO, I_ASK_USER
 };
+ARGMATCH_VERIFY (reply_args, reply_vals);
 
 static struct option const long_opts[] =
 {
@@ -127,21 +124,22 @@ static struct option const long_opts[] =
   {"link", no_argument, NULL, 'l'},
   {"no-dereference", no_argument, NULL, 'P'},
   {"no-preserve", required_argument, NULL, NO_PRESERVE_ATTRIBUTES_OPTION},
+  {"no-target-directory", no_argument, NULL, 'T'},
   {"one-file-system", no_argument, NULL, 'x'},
   {"parents", no_argument, NULL, PARENTS_OPTION},
   {"path", no_argument, NULL, PARENTS_OPTION},   /* Deprecated.  */
   {"preserve", optional_argument, NULL, PRESERVE_ATTRIBUTES_OPTION},
   {"recursive", no_argument, NULL, 'R'},
   {"remove-destination", no_argument, NULL, UNLINK_DEST_BEFORE_OPENING},
-  {"reply", required_argument, NULL, REPLY_OPTION},
+  {"reply", required_argument, NULL, REPLY_OPTION}, /* Deprecated 2005-07-03,
+                                                      remove in 2008. */
   {"sparse", required_argument, NULL, SPARSE_OPTION},
   {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION},
   {"suffix", required_argument, NULL, 'S'},
   {"symbolic-link", no_argument, NULL, 's'},
-  {"target-directory", required_argument, NULL, TARGET_DIRECTORY_OPTION},
+  {"target-directory", required_argument, NULL, 't'},
   {"update", no_argument, NULL, 'u'},
   {"verbose", no_argument, NULL, 'v'},
-  {"version-control", required_argument, NULL, 'V'}, /* Deprecated. FIXME. */
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -156,9 +154,9 @@ usage (int status)
   else
     {
       printf (_("\
-Usage: %s [OPTION]... SOURCE DEST\n\
+Usage: %s [OPTION]... [-T] SOURCE DEST\n\
   or:  %s [OPTION]... SOURCE... DIRECTORY\n\
-  or:  %s [OPTION]... --target-directory=DIRECTORY SOURCE...\n\
+  or:  %s [OPTION]... -DIRECTORY SOURCE...\n\
 "),
              program_name, program_name, program_name);
       fputs (_("\
@@ -176,7 +174,6 @@ Mandatory arguments to long options are mandatory for short options too.\n\
   -d                           same as --no-dereference --preserve=link\n\
 "), stdout);
       fputs (_("\
-      --no-dereference         never follow symbolic links\n\
   -f, --force                  if an existing destination file cannot be\n\
                                  opened, remove it and try again\n\
   -i, --interactive            prompt before overwrite\n\
@@ -185,6 +182,11 @@ Mandatory arguments to long options are mandatory for short options too.\n\
       fputs (_("\
   -l, --link                   link files instead of copying\n\
   -L, --dereference            always follow symbolic links\n\
+"), stdout);
+      fputs (_("\
+  -P, --no-dereference         never follow symbolic links\n\
+"), stdout);
+      fputs (_("\
   -p                           same as --preserve=mode,ownership,timestamps\n\
       --preserve[=ATTR_LIST]   preserve the specified attributes (default:\n\
                                  mode,ownership,timestamps), if possible\n\
@@ -192,8 +194,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\
 "), stdout);
       fputs (_("\
       --no-preserve=ATTR_LIST  don't preserve the specified attributes\n\
-      --parents                append source path to DIRECTORY\n\
-  -P                           same as `--no-dereference'\n\
+      --parents                use full source file name under DIRECTORY\n\
 "), stdout);
       fputs (_("\
   -R, -r, --recursive          copy directories recursively\n\
@@ -201,8 +202,6 @@ Mandatory arguments to long options are mandatory for short options too.\n\
                                  attempting to open it (contrast with --force)\n\
 "), stdout);
       fputs (_("\
-      --reply={yes,no,query}   specify how to handle the prompt about an\n\
-                                 existing destination file\n\
       --sparse=WHEN            control creation of sparse files\n\
       --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\
                                  argument\n\
@@ -210,7 +209,8 @@ Mandatory arguments to long options are mandatory for short options too.\n\
       fputs (_("\
   -s, --symbolic-link          make symbolic links instead of copying\n\
   -S, --suffix=SUFFIX          override the usual backup suffix\n\
-      --target-directory=DIRECTORY  move all SOURCE arguments into DIRECTORY\n\
+  -t, --target-directory=DIRECTORY  copy all SOURCE arguments into DIRECTORY\n\
+  -T, --no-target-directory    treat DEST as a normal file\n\
 "), stdout);
       fputs (_("\
   -u, --update                 copy only when the SOURCE file is newer\n\
@@ -253,49 +253,48 @@ regular file.\n\
   exit (status);
 }
 
-/* Ensure that the parent directories of CONST_DST_PATH have the
+/* Ensure that the parent directories of CONST_DST_NAME have the
    correct protections, for the --parents option.  This is done
    after all copying has been completed, to allow permissions
    that don't include user write/execute.
 
-   SRC_OFFSET is the index in CONST_DST_PATH of the beginning of the
+   SRC_OFFSET is the index in CONST_DST_NAME of the beginning of the
    source directory name.
 
    ATTR_LIST is a null-terminated linked list of structures that
    indicates the end of the filename of each intermediate directory
-   in CONST_DST_PATH that may need to have its attributes changed.
+   in CONST_DST_NAME that may need to have its attributes changed.
    The command `cp --parents --preserve a/b/c d/e_dir' changes the
    attributes of the directories d/e_dir/a and d/e_dir/a/b to match
    the corresponding source directories regardless of whether they
    existed before the `cp' command was given.
 
-   Return 0 if the parent of CONST_DST_PATH and any intermediate
+   Return true if the parent of CONST_DST_NAME and any intermediate
    directories specified by ATTR_LIST have the proper permissions
-   when done, otherwise 1. */
+   when done */
 
-static int
-re_protect (const char *const_dst_path, int src_offset,
+static bool
+re_protect (char const *const_dst_name, size_t src_offset,
            struct dir_attr *attr_list, const struct cp_options *x)
 {
   struct dir_attr *p;
-  char *dst_path;              /* A copy of CONST_DST_PATH we can change. */
-  char *src_path;              /* The source name in `dst_path'. */
-  uid_t myeuid = geteuid ();
+  char *dst_name;              /* A copy of CONST_DST_NAME we can change. */
+  char *src_name;              /* The source name in `dst_name'. */
 
-  ASSIGN_STRDUPA (dst_path, const_dst_path);
-  src_path = dst_path + src_offset;
+  ASSIGN_STRDUPA (dst_name, const_dst_name);
+  src_name = dst_name + src_offset;
 
   for (p = attr_list; p; p = p->next)
     {
       struct stat src_sb;
 
-      dst_path[p->slash_offset] = '\0';
+      dst_name[p->slash_offset] = '\0';
 
-      if (XSTAT (x, src_path, &src_sb))
+      if (XSTAT (x, src_name, &src_sb))
        {
          error (0, errno, _("failed to get attributes of %s"),
-                quote (src_path));
-         return 1;
+                quote (src_name));
+         return false;
        }
 
       /* Adjust the times (and if possible, ownership) for the copy.
@@ -306,53 +305,48 @@ re_protect (const char *const_dst_path, int src_offset,
        {
          struct timespec timespec[2];
 
-         timespec[0].tv_sec = src_sb.st_atime;
-         timespec[0].tv_nsec = TIMESPEC_NS (src_sb.st_atim);
-         timespec[1].tv_sec = src_sb.st_mtime;
-         timespec[1].tv_nsec = TIMESPEC_NS (src_sb.st_mtim);
+         timespec[0] = get_stat_atime (&src_sb);
+         timespec[1] = get_stat_mtime (&src_sb);
 
-         if (utimens (dst_path, timespec))
+         if (utimens (dst_name, timespec))
            {
              error (0, errno, _("failed to preserve times for %s"),
-                    quote (dst_path));
-             return 1;
+                    quote (dst_name));
+             return false;
            }
        }
 
       if (x->preserve_ownership)
        {
-         /* If non-root uses -p, it's ok if we can't preserve ownership.
-            But root probably wants to know, e.g. if NFS disallows it,
-            or if the target system doesn't support file ownership.  */
-         if (chown (dst_path, src_sb.st_uid, src_sb.st_gid)
-             && ((errno != EPERM && errno != EINVAL) || myeuid == 0))
+         if (chown (dst_name, src_sb.st_uid, src_sb.st_gid) != 0
+             && ! chown_failure_ok (x))
            {
              error (0, errno, _("failed to preserve ownership for %s"),
-                    quote (dst_path));
-             return 1;
+                    quote (dst_name));
+             return false;
            }
        }
 
-      if (x->preserve_mode || p->is_new_dir)
+      if (x->preserve_mode | p->is_new_dir)
        {
-         if (chmod (dst_path, src_sb.st_mode & x->umask_kill))
+         if (chmod (dst_name, src_sb.st_mode & x->umask_kill))
            {
              error (0, errno, _("failed to preserve permissions for %s"),
-                    quote (dst_path));
-             return 1;
+                    quote (dst_name));
+             return false;
            }
        }
 
-      dst_path[p->slash_offset] = '/';
+      dst_name[p->slash_offset] = '/';
     }
-  return 0;
+  return true;
 }
 
-/* Ensure that the parent directory of CONST_DIRPATH exists, for
+/* Ensure that the parent directory of CONST_DIR exists, for
    the --parents option.
 
-   SRC_OFFSET is the index in CONST_DIRPATH (which is a destination
-   path) of the beginning of the source directory name.
+   SRC_OFFSET is the index in CONST_DIR (which is a destination
+   directory) of the beginning of the source directory name.
    Create any leading directories that don't already exist,
    giving them permissions MODE.
    If VERBOSE_FMT_STRING is nonzero, use it as a printf format
@@ -361,38 +355,39 @@ re_protect (const char *const_dst_path, int src_offset,
    source and destination directories.
    Creates a linked list of attributes of intermediate directories,
    *ATTR_LIST, for re_protect to use after calling copy.
-   Sets *NEW_DST to 1 if this function creates parent of CONST_DIRPATH.
+   Sets *NEW_DST if this function creates parent of CONST_DIR.
 
-   Return 0 if parent of CONST_DIRPATH exists as a directory with the proper
-   permissions when done, otherwise 1. */
+   Return true if parent of CONST_DIR exists as a directory with the proper
+   permissions when done */
 
-/* FIXME: find a way to synch this function with the one in lib/makepath.c. */
+/* FIXME: Synch this function with the one in ../lib/mkdir-p.c.  */
 
-static int
-make_path_private (const char *const_dirpath, int src_offset, int mode,
-                  const char *verbose_fmt_string, struct dir_attr **attr_list,
-                  int *new_dst, int (*xstat)())
+static bool
+make_dir_parents_private (char const *const_dir, size_t src_offset,
+                         mode_t mode, char const *verbose_fmt_string,
+                         struct dir_attr **attr_list, bool *new_dst,
+                         int (*xstat) ())
 {
   struct stat stats;
-  char *dirpath;               /* A copy of CONST_DIRPATH we can change. */
-  char *src;                   /* Source name in `dirpath'. */
-  char *dst_dirname;           /* Leading path of `dirpath'. */
-  size_t dirlen;               /* Length of leading path of `dirpath'. */
+  char *dir;           /* A copy of CONST_DIR we can change.  */
+  char *src;           /* Source name in DIR.  */
+  char *dst_dir;       /* Leading directory of DIR.  */
+  size_t dirlen;       /* Length of DIR.  */
 
-  ASSIGN_STRDUPA (dirpath, const_dirpath);
+  ASSIGN_STRDUPA (dir, const_dir);
 
-  src = dirpath + src_offset;
+  src = dir + src_offset;
 
-  dirlen = dir_len (dirpath);
-  dst_dirname = alloca (dirlen + 1);
-  memcpy (dst_dirname, dirpath, dirlen);
-  dst_dirname[dirlen] = '\0';
+  dirlen = dir_len (dir);
+  dst_dir = alloca (dirlen + 1);
+  memcpy (dst_dir, dir, dirlen);
+  dst_dir[dirlen] = '\0';
 
   *attr_list = NULL;
 
-  if ((*xstat) (dst_dirname, &stats))
+  if ((*xstat) (dst_dir, &stats))
     {
-      /* Parent of CONST_DIRNAME does not exist.
+      /* A parent of CONST_DIR does not exist.
         Make all missing intermediate directories. */
       char *slash;
 
@@ -404,149 +399,138 @@ make_path_private (const char *const_dirpath, int src_offset, int mode,
          /* Add this directory to the list of directories whose modes need
             fixing later. */
          struct dir_attr *new = xmalloc (sizeof *new);
-         new->slash_offset = slash - dirpath;
+         new->slash_offset = slash - dir;
          new->next = *attr_list;
          *attr_list = new;
 
          *slash = '\0';
-         if ((*xstat) (dirpath, &stats))
+         if ((*xstat) (dir, &stats))
            {
-             /* This element of the path does not exist.  We must set
+             /* This component does not exist.  We must set
                 *new_dst and new->is_new_dir inside this loop because,
                 for example, in the command `cp --parents ../a/../b/c e_dir',
-                make_path_private creates only e_dir/../a if ./b already
-                exists. */
-             *new_dst = 1;
-             new->is_new_dir = 1;
-             if (mkdir (dirpath, mode))
+                make_dir_parents_private creates only e_dir/../a if
+                ./b already exists. */
+             *new_dst = true;
+             new->is_new_dir = true;
+             if (mkdir (dir, mode))
                {
                  error (0, errno, _("cannot make directory %s"),
-                        quote (dirpath));
-                 return 1;
+                        quote (dir));
+                 return false;
                }
              else
                {
                  if (verbose_fmt_string != NULL)
-                   printf (verbose_fmt_string, src, dirpath);
+                   printf (verbose_fmt_string, src, dir);
                }
            }
          else if (!S_ISDIR (stats.st_mode))
            {
              error (0, 0, _("%s exists but is not a directory"),
-                    quote (dirpath));
-             return 1;
+                    quote (dir));
+             return false;
            }
          else
            {
-             new->is_new_dir = 0;
-             *new_dst = 0;
+             new->is_new_dir = false;
+             *new_dst = false;
            }
          *slash++ = '/';
 
          /* Avoid unnecessary calls to `stat' when given
-            pathnames containing multiple adjacent slashes.  */
+            file names containing multiple adjacent slashes.  */
          while (*slash == '/')
            slash++;
        }
     }
 
-  /* We get here if the parent of `dirpath' already exists. */
+  /* We get here if the parent of DIR already exists.  */
 
   else if (!S_ISDIR (stats.st_mode))
     {
-      error (0, 0, _("%s exists but is not a directory"), quote (dst_dirname));
-      return 1;
+      error (0, 0, _("%s exists but is not a directory"), quote (dst_dir));
+      return false;
     }
   else
     {
-      *new_dst = 0;
+      *new_dst = false;
     }
-  return 0;
+  return true;
+}
+
+/* FILE is the last operand of this command.
+   Return true if FILE is a directory.
+   But report an error and exit if there is a problem accessing FILE,
+   or if FILE does not exist but would have to refer to an existing
+   directory if it referred to anything at all.
+
+   If the file exists, store the file's status into *ST.
+   Otherwise, set *NEW_DST.  */
+
+static bool
+target_directory_operand (char const *file, struct stat *st, bool *new_dst)
+{
+  char const *b = base_name (file);
+  size_t blen = strlen (b);
+  bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1]));
+  int err = (stat (file, st) == 0 ? 0 : errno);
+  bool is_a_dir = !err && S_ISDIR (st->st_mode);
+  if (err)
+    {
+      if (err != ENOENT)
+       error (EXIT_FAILURE, err, _("accessing %s"), quote (file));
+      *new_dst = true;
+    }
+  if (is_a_dir < looks_like_a_dir)
+    error (EXIT_FAILURE, err, _("target %s is not a directory"), quote (file));
+  return is_a_dir;
 }
 
 /* Scan the arguments, and copy each by calling copy.
-   Return 0 if successful, 1 if any errors occur. */
+   Return true if successful.  */
 
-static int
+static bool
 do_copy (int n_files, char **file, const char *target_directory,
-        struct cp_options *x)
+        bool no_target_directory, struct cp_options *x)
 {
-  const char *dest;
   struct stat sb;
-  int new_dst = 0;
-  int ret = 0;
-  int dest_is_dir = 0;
+  bool new_dst = false;
+  bool ok = true;
 
-  if (n_files <= 0)
+  if (n_files <= !target_directory)
     {
-      error (0, 0, _("missing file argument"));
-      usage (EXIT_FAILURE);
-    }
-  if (n_files == 1 && !target_directory)
-    {
-      error (0, 0, _("missing destination file"));
+      if (n_files <= 0)
+       error (0, 0, _("missing file operand"));
+      else
+       error (0, 0, _("missing destination file operand after %s"),
+              quote (file[0]));
       usage (EXIT_FAILURE);
     }
 
-  if (target_directory)
-    dest = target_directory;
-  else
-    {
-      dest = file[n_files - 1];
-      --n_files;
-    }
-
-  /* Initialize these hash tables only if we'll need them.
-     The problems they're used to detect can arise only if
-     there are two or more files to copy.  */
-  if (n_files >= 2)
+  if (no_target_directory)
     {
-      dest_info_init (x);
-      src_info_init (x);
-    }
-
-  if (lstat (dest, &sb))
-    {
-      if (errno != ENOENT)
+      if (target_directory)
+       error (EXIT_FAILURE, 0,
+              _("Cannot combine --target-directory (-t) "
+                "and --no-target-directory (-T)"));
+      if (2 < n_files)
        {
-         error (0, errno, _("accessing %s"), quote (dest));
-         return 1;
+         error (0, 0, _("extra operand %s"), quote (file[2]));
+         usage (EXIT_FAILURE);
        }
-
-      new_dst = 1;
-    }
-  else
-    {
-      struct stat sbx;
-
-      /* If `dest' is not a symlink to a nonexistent file, use
-        the results of stat instead of lstat, so we can copy files
-        into symlinks to directories. */
-      if (stat (dest, &sbx) == 0)
-       sb = sbx;
-
-      dest_is_dir = S_ISDIR (sb.st_mode);
     }
-
-  if (!dest_is_dir)
+  else if (!target_directory)
     {
-      if (target_directory || 1 < n_files)
-       {
-         char const *msg;
-         if (new_dst)
-           msg = N_("%s: specified destination directory does not exist");
-         else if (target_directory)
-           msg = N_("%s: specified target is not a directory");
-         else /* n_files > 1 */
-           msg =
-         N_("copying multiple files, but last argument %s is not a directory");
-
-         error (0, 0, _(msg), quote (dest));
-         usage (EXIT_FAILURE);
-       }
+      if (2 <= n_files
+         && target_directory_operand (file[n_files - 1], &sb, &new_dst))
+       target_directory = file[--n_files];
+      else if (2 < n_files)
+       error (EXIT_FAILURE, 0, _("target %s is not a directory"),
+              quote (file[n_files - 1]));
     }
 
-  if (dest_is_dir)
+  if (target_directory)
     {
       /* cp file1...filen edir
         Copy the files `file1' through `filen'
@@ -557,10 +541,19 @@ do_copy (int n_files, char **file, const char *target_directory,
                        ? stat
                        : lstat);
 
+      /* Initialize these hash tables only if we'll need them.
+        The problems they're used to detect can arise only if
+        there are two or more files to copy.  */
+      if (2 <= n_files)
+       {
+         dest_info_init (x);
+         src_info_init (x);
+       }
+
       for (i = 0; i < n_files; i++)
        {
-         char *dst_path;
-         int parent_exists = 1; /* True if dir_name (dst_path) exists. */
+         char *dst_name;
+         bool parent_exists = true;  /* True if dir_name (dst_name) exists. */
          struct dir_attr *attr_list;
          char *arg_in_concat = NULL;
          char *arg = file[i];
@@ -570,7 +563,7 @@ do_copy (int n_files, char **file, const char *target_directory,
          if (remove_trailing_slashes)
            strip_trailing_slashes (arg);
 
-         if (flag_path)
+         if (parents_option)
            {
              char *arg_no_trailing_slash;
 
@@ -583,71 +576,65 @@ do_copy (int n_files, char **file, const char *target_directory,
              strip_trailing_slashes (arg_no_trailing_slash);
 
              /* Append all of `arg' (minus any trailing slash) to `dest'.  */
-             dst_path = path_concat (dest, arg_no_trailing_slash,
-                                     &arg_in_concat);
-             if (dst_path == NULL)
-               xalloc_die ();
+             dst_name = file_name_concat (target_directory,
+                                          arg_no_trailing_slash,
+                                          &arg_in_concat);
 
              /* For --parents, we have to make sure that the directory
-                dir_name (dst_path) exists.  We may have to create a few
+                dir_name (dst_name) exists.  We may have to create a few
                 leading directories. */
-             parent_exists = !make_path_private (dst_path,
-                                                 arg_in_concat - dst_path,
-                                                 S_IRWXU,
-                                                 (x->verbose
-                                                  ? "%s -> %s\n" : NULL),
-                                                 &attr_list, &new_dst,
-                                                 xstat);
+             parent_exists =
+               (make_dir_parents_private
+                (dst_name, arg_in_concat - dst_name, S_IRWXU,
+                 (x->verbose ? "%s -> %s\n" : NULL),
+                 &attr_list, &new_dst, xstat));
            }
          else
            {
              char *arg_base;
-             /* Append the last component of `arg' to `dest'.  */
+             /* Append the last component of `arg' to `target_directory'.  */
 
              ASSIGN_BASENAME_STRDUPA (arg_base, arg);
              /* For `cp -R source/.. dest', don't copy into `dest/..'. */
-             dst_path = (STREQ (arg_base, "..")
-                         ? xstrdup (dest)
-                         : path_concat (dest, arg_base, NULL));
+             dst_name = (STREQ (arg_base, "..")
+                         ? xstrdup (target_directory)
+                         : file_name_concat (target_directory, arg_base,
+                                             NULL));
            }
 
          if (!parent_exists)
            {
-             /* make_path_private failed, so don't even attempt the copy. */
-             ret = 1;
+             /* make_dir_parents_private failed, so don't even
+                attempt the copy.  */
+             ok = false;
            }
          else
            {
-             int copy_into_self;
-             ret |= copy (arg, dst_path, new_dst, x, &copy_into_self, NULL);
+             bool copy_into_self;
+             ok &= copy (arg, dst_name, new_dst, x, &copy_into_self, NULL);
 
-             if (flag_path)
-               {
-                 ret |= re_protect (dst_path, arg_in_concat - dst_path,
-                                    attr_list, x);
-               }
+             if (parents_option)
+               ok &= re_protect (dst_name, arg_in_concat - dst_name,
+                                 attr_list, x);
            }
 
-         free (dst_path);
+         free (dst_name);
        }
-      return ret;
     }
-  else /* if (n_files == 1) */
+  else /* !target_directory */
     {
-      char *new_dest;
-      char *source;
-      int unused;
-      struct stat source_stats;
+      char const *new_dest;
+      char const *source = file[0];
+      char const *dest = file[1];
+      bool unused;
 
-      if (flag_path)
+      if (parents_option)
        {
          error (0, 0,
-              _("when preserving paths, the destination must be a directory"));
+                _("with --parents, the destination must be a directory"));
          usage (EXIT_FAILURE);
        }
 
-      source = file[0];
-
       /* When the force and backup options have been specified and
         the source and destination are the same name for an existing
         regular file, convert the user's command, e.g.,
@@ -655,81 +642,60 @@ do_copy (int n_files, char **file, const char *target_directory,
         where SUFFIX is determined by any version control options used.  */
 
       if (x->unlink_dest_after_failed_open
-         && x->backup_type != none
+         && x->backup_type != no_backups
          && STREQ (source, dest)
          && !new_dst && S_ISREG (sb.st_mode))
        {
          static struct cp_options x_tmp;
 
          new_dest = find_backup_file_name (dest, x->backup_type);
-         /* Set x->backup_type to `none' so that the normal backup
+         /* Set x->backup_type to `no_backups' so that the normal backup
             mechanism is not used when performing the actual copy.
-            backup_type must be set to `none' only *after* the above
+            backup_type must be set to `no_backups' only *after* the above
             call to find_backup_file_name -- that function uses
             backup_type to determine the suffix it applies.  */
          x_tmp = *x;
-         x_tmp.backup_type = none;
+         x_tmp.backup_type = no_backups;
          x = &x_tmp;
-
-         if (new_dest == NULL)
-           xalloc_die ();
-       }
-
-      /* When the destination is specified with a trailing slash and the
-        source exists but is not a directory, convert the user's command
-        `cp source dest/' to `cp source dest/basename(source)'.  Doing
-        this ensures that the command `cp non-directory file/' will now
-        fail rather than performing the copy.  COPY diagnoses the case of
-        `cp directory non-directory'.  */
-
-      else if (dest[strlen (dest) - 1] == '/'
-         && lstat (source, &source_stats) == 0
-         && !S_ISDIR (source_stats.st_mode))
-       {
-         char *source_base;
-
-         ASSIGN_BASENAME_STRDUPA (source_base, source);
-         new_dest = alloca (strlen (dest) + strlen (source_base) + 1);
-         stpcpy (stpcpy (new_dest, dest), source_base);
        }
       else
        {
-         new_dest = (char *) dest;
+         new_dest = dest;
        }
 
-      return copy (source, new_dest, new_dst, x, &unused, NULL);
+      ok = copy (source, new_dest, 0, x, &unused, NULL);
     }
 
-  /* unreachable */
+  return ok;
 }
 
 static void
 cp_option_init (struct cp_options *x)
 {
-  x->copy_as_regular = 1;
+  x->copy_as_regular = true;
   x->dereference = DEREF_UNDEFINED;
-  x->unlink_dest_before_opening = 0;
-  x->unlink_dest_after_failed_open = 0;
-  x->hard_link = 0;
+  x->unlink_dest_before_opening = false;
+  x->unlink_dest_after_failed_open = false;
+  x->hard_link = false;
   x->interactive = I_UNSPECIFIED;
-  x->myeuid = geteuid ();
-  x->move_mode = 0;
-  x->one_file_system = 0;
+  x->chown_privileges = chown_privileges ();
+  x->move_mode = false;
+  x->one_file_system = false;
 
-  x->preserve_ownership = 0;
-  x->preserve_links = 0;
-  x->preserve_mode = 0;
-  x->preserve_timestamps = 0;
+  x->preserve_ownership = false;
+  x->preserve_links = false;
+  x->preserve_mode = false;
+  x->preserve_timestamps = false;
 
-  x->require_preserve = 0;
-  x->recursive = 0;
+  x->require_preserve = false;
+  x->recursive = false;
   x->sparse_mode = SPARSE_AUTO;
-  x->symbolic_link = 0;
-  x->set_mode = 0;
+  x->symbolic_link = false;
+  x->set_mode = false;
   x->mode = 0;
 
   /* Not used.  */
-  x->stdin_tty = 0;
+  x->stdin_tty = false;
 
   /* Find out the current file creation mask, to knock the right bits
      when using chmod.  The creation mask is set to be liberal, so
@@ -737,8 +703,8 @@ cp_option_init (struct cp_options *x)
      have been allowed with the mask this process was started with.  */
   x->umask_kill = ~ umask (0);
 
-  x->update = 0;
-  x->verbose = 0;
+  x->update = false;
+  x->verbose = false;
   x->dest_info = NULL;
   x->src_info = NULL;
 }
@@ -746,7 +712,7 @@ cp_option_init (struct cp_options *x)
 /* Given a string, ARG, containing a comma-separated list of arguments
    to the --preserve option, set the appropriate fields of X to ON_OFF.  */
 static void
-decode_preserve_arg (char const *arg, struct cp_options *x, int on_off)
+decode_preserve_arg (char const *arg, struct cp_options *x, bool on_off)
 {
   enum File_attribute
     {
@@ -761,13 +727,13 @@ decode_preserve_arg (char const *arg, struct cp_options *x, int on_off)
       PRESERVE_MODE, PRESERVE_TIMESTAMPS,
       PRESERVE_OWNERSHIP, PRESERVE_LINK, PRESERVE_ALL
     };
-
   /* Valid arguments to the `--preserve' option. */
   static char const* const preserve_args[] =
     {
       "mode", "timestamps",
-      "ownership", "links", "all", 0
+      "ownership", "links", "all", NULL
     };
+  ARGMATCH_VERIFY (preserve_args, preserve_vals);
 
   char *arg_writable = xstrdup (arg);
   char *s = arg_writable;
@@ -822,13 +788,14 @@ int
 main (int argc, char **argv)
 {
   int c;
-  int exit_status;
-  int make_backups = 0;
+  bool ok;
+  bool make_backups = false;
   char *backup_suffix_string;
   char *version_control_string = NULL;
   struct cp_options x;
-  int copy_contents = 0;
+  bool copy_contents = false;
   char *target_directory = NULL;
+  bool no_target_directory = false;
 
   initialize_main (&argc, &argv);
   program_name = argv[0];
@@ -844,14 +811,12 @@ main (int argc, char **argv)
      we'll actually use backup_suffix_string.  */
   backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
 
-  while ((c = getopt_long (argc, argv, "abdfHilLprsuvxPRS:V:", long_opts, NULL))
+  while ((c = getopt_long (argc, argv, "abdfHilLprst:uvxPRS:T",
+                          long_opts, NULL))
         != -1)
     {
       switch (c)
        {
-       case 0:
-         break;
-
        case SPARSE_OPTION:
          x.sparse_mode = XARGMATCH ("--sparse", optarg,
                                     sparse_type_string, sparse_type);
@@ -859,38 +824,31 @@ main (int argc, char **argv)
 
        case 'a':               /* Like -dpPR. */
          x.dereference = DEREF_NEVER;
-         x.preserve_links = 1;
-         x.preserve_ownership = 1;
-         x.preserve_mode = 1;
-         x.preserve_timestamps = 1;
-         x.require_preserve = 1;
-         x.recursive = 1;
+         x.preserve_links = true;
+         x.preserve_ownership = true;
+         x.preserve_mode = true;
+         x.preserve_timestamps = true;
+         x.require_preserve = true;
+         x.recursive = true;
          break;
 
-       case 'V':  /* FIXME: this is deprecated.  Remove it in 2001.  */
-         error (0, 0,
-                _("warning: --version-control (-V) is obsolete;  support for\
- it\nwill be removed in some future release.  Use --backup=%s instead."
-                  ), optarg);
-         /* Fall through.  */
-
        case 'b':
-         make_backups = 1;
+         make_backups = true;
          if (optarg)
            version_control_string = optarg;
          break;
 
        case COPY_CONTENTS_OPTION:
-         copy_contents = 1;
+         copy_contents = true;
          break;
 
        case 'd':
-         x.preserve_links = 1;
+         x.preserve_links = true;
          x.dereference = DEREF_NEVER;
          break;
 
        case 'f':
-         x.unlink_dest_after_failed_open = 1;
+         x.unlink_dest_after_failed_open = true;
          break;
 
        case 'H':
@@ -902,7 +860,7 @@ main (int argc, char **argv)
          break;
 
        case 'l':
-         x.hard_link = 1;
+         x.hard_link = true;
          break;
 
        case 'L':
@@ -914,7 +872,7 @@ main (int argc, char **argv)
          break;
 
        case NO_PRESERVE_ATTRIBUTES_OPTION:
-         decode_preserve_arg (optarg, &x, 0);
+         decode_preserve_arg (optarg, &x, false);
          break;
 
        case PRESERVE_ATTRIBUTES_OPTION:
@@ -924,67 +882,85 @@ main (int argc, char **argv)
            }
          else
            {
-             decode_preserve_arg (optarg, &x, 1);
-             x.require_preserve = 1;
+             decode_preserve_arg (optarg, &x, true);
+             x.require_preserve = true;
              break;
            }
 
        case 'p':
-         x.preserve_ownership = 1;
-         x.preserve_mode = 1;
-         x.preserve_timestamps = 1;
-         x.require_preserve = 1;
+         x.preserve_ownership = true;
+         x.preserve_mode = true;
+         x.preserve_timestamps = true;
+         x.require_preserve = true;
          break;
 
        case PARENTS_OPTION:
-         flag_path = 1;
+         parents_option = true;
          break;
 
        case 'r':
        case 'R':
-         x.recursive = 1;
+         x.recursive = true;
          break;
 
-       case REPLY_OPTION:
+       case REPLY_OPTION: /* Deprecated */
          x.interactive = XARGMATCH ("--reply", optarg,
                                     reply_args, reply_vals);
+         error (0, 0,
+                _("the --reply option is deprecated; use -i or -f instead"));
          break;
 
        case UNLINK_DEST_BEFORE_OPENING:
-         x.unlink_dest_before_opening = 1;
+         x.unlink_dest_before_opening = true;
          break;
 
        case STRIP_TRAILING_SLASHES_OPTION:
-         remove_trailing_slashes = 1;
+         remove_trailing_slashes = true;
          break;
 
        case 's':
 #ifdef S_ISLNK
-         x.symbolic_link = 1;
+         x.symbolic_link = true;
 #else
          error (EXIT_FAILURE, 0,
                 _("symbolic links are not supported on this system"));
 #endif
          break;
 
-       case TARGET_DIRECTORY_OPTION:
+       case 't':
+         if (target_directory)
+           error (EXIT_FAILURE, 0,
+                  _("multiple target directories specified"));
+         else
+           {
+             struct stat st;
+             if (stat (optarg, &st) != 0)
+               error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg));
+             if (! S_ISDIR (st.st_mode))
+               error (EXIT_FAILURE, 0, _("target %s is not a directory"),
+                      quote (optarg));
+           }
          target_directory = optarg;
          break;
 
+       case 'T':
+         no_target_directory = true;
+         break;
+
        case 'u':
-         x.update = 1;
+         x.update = true;
          break;
 
        case 'v':
-         x.verbose = 1;
+         x.verbose = true;
          break;
 
        case 'x':
-         x.one_file_system = 1;
+         x.one_file_system = true;
          break;
 
        case 'S':
-         make_backups = 1;
+         make_backups = true;
          backup_suffix_string = optarg;
          break;
 
@@ -997,7 +973,7 @@ main (int argc, char **argv)
        }
     }
 
-  if (x.hard_link && x.symbolic_link)
+  if (x.hard_link & x.symbolic_link)
     {
       error (0, 0, _("cannot make both hard and symbolic links"));
       usage (EXIT_FAILURE);
@@ -1009,9 +985,9 @@ main (int argc, char **argv)
   x.backup_type = (make_backups
                   ? xget_version (_("backup type"),
                                   version_control_string)
-                  : none);
+                  : no_backups);
 
-  if (x.preserve_mode == 1)
+  if (x.preserve_mode)
     x.umask_kill = ~ (mode_t) 0;
 
   if (x.dereference == DEREF_UNDEFINED)
@@ -1031,16 +1007,17 @@ main (int argc, char **argv)
 
   /* If --force (-f) was specified and we're in link-creation mode,
      first remove any existing destination file.  */
-  if (x.unlink_dest_after_failed_open && (x.hard_link || x.symbolic_link))
-    x.unlink_dest_before_opening = 1;
+  if (x.unlink_dest_after_failed_open & (x.hard_link | x.symbolic_link))
+    x.unlink_dest_before_opening = true;
 
   /* Allocate space for remembering copied and created files.  */
 
   hash_init ();
 
-  exit_status = do_copy (argc - optind, argv + optind, target_directory, &x);
+  ok = do_copy (argc - optind, argv + optind,
+               target_directory, no_target_directory, &x);
 
   forget_all ();
 
-  exit (exit_status);
+  exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }