]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
Gutted code -- pieces are in copy.[ch].
authorJim Meyering <jim@meyering.net>
Sun, 2 Feb 1997 18:33:19 +0000 (18:33 +0000)
committerJim Meyering <jim@meyering.net>
Sun, 2 Feb 1997 18:33:19 +0000 (18:33 +0000)
src/cp.c

index 3ec93b7d1ae834c5bc59466b1ffc66b2475aac1a..a8668f7709a87d332ee0433db40764b94e5dc405 100644 (file)
--- a/src/cp.c
+++ b/src/cp.c
 
 #include <config.h>
 #include <stdio.h>
+#include <sys/types.h>
 
 #define NDEBUG
 #include <assert.h>
 
 #include <getopt.h>
-#include "cp.h"
+#include "system.h"
 #include "backupfile.h"
 #include "argmatch.h"
 #include "path-concat.h"
+#include "cp-hash.h"
+#include "copy.h"
+#include "error.h"
+
+#define CP_OPTIONS NULL
 
 #ifndef _POSIX_VERSION
 uid_t geteuid ();
 #endif
 
-/* On Linux (from slackware-1.2.13 to 2.0.2?) there is no lchown function.
-   To change ownership of symlinks, you must run chown with an effective
-   UID of 0.  */
-#ifdef __linux__
-# define ROOT_CHOWN_AFFECTS_SYMLINKS
-#endif
-
-#define DO_CHOWN(Chown, File, New_uid, New_gid)                                \
-  (Chown ((File), (myeuid == 0 ? (New_uid) : myeuid), (New_gid))       \
-   /* 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.  */   \
-   && (errno != EPERM || myeuid == 0))
-
 /* Used by do_copy, make_path_private, and re_protect
    to keep a list of leading directories whose protections
    need to be fixed after copying. */
@@ -60,46 +53,15 @@ struct dir_attr
   struct dir_attr *next;
 };
 
-/* Control creation of sparse files (files with holes).  */
-enum Sparse_type
-{
-  /* Never create holes in DEST.  */
-  SPARSE_NEVER,
-
-  /* This is the default.  Use a crude (and sometimes inaccurate)
-     heuristic to determine if SOURCE has holes.  If so, try to create
-     holes in DEST.  */
-  SPARSE_AUTO,
-
-  /* For every sufficiently long sequence of bytes in SOURCE, try to
-     create a corresponding hole in DEST.  There is a performance penalty
-     here because CP has to search for holes in SRC.  But if the holes are
-     big enough, that penalty can be offset by the decrease in the amount
-     of data written to disk.   */
-  SPARSE_ALWAYS
-};
-
 int stat ();
 int lstat ();
 
+char *basename ();
 char *dirname ();
-char *xstrdup ();
 enum backup_type get_version ();
-int euidaccess ();
-int full_write ();
-
-static int do_copy __P ((int argc, char **argv));
-static int copy __P ((const char *src_path, const char *dst_path, int new_dst,
-                     dev_t device, struct dir_list *ancestors));
-static int copy_dir __P ((const char *src_path_in, const char *dst_path_in,
-                         int new_dst, const struct stat *src_sb,
-                         struct dir_list *ancestors));
-static int make_path_private __P ((const char *const_dirpath, int src_offset,
-                                  int mode, const char *verbose_fmt_string,
-                                  struct dir_attr **attr_list, int *new_dst));
-static int copy_reg __P ((const char *src_path, const char *dst_path));
-static int re_protect __P ((const char *const_dst_path, int src_offset,
-                           struct dir_attr *attr_list));
+void strip_trailing_slashes ();
+char *xmalloc ();
+char *xstrdup ();
 
 /* Initial number of entries in each hash table entry's table of inodes.  */
 #define INITIAL_HASH_MODULE 100
@@ -110,55 +72,10 @@ static int re_protect __P ((const char *const_dst_path, int src_offset,
 /* The invocation name of this program.  */
 char *program_name;
 
-/* A pointer to either lstat or stat, depending on
-   whether dereferencing of symlinks is done.  */
-static int (*xstat) ();
-
-/* If nonzero, copy all files except (directories and, if not dereferencing
-   them, symbolic links,) as if they were regular files. */
-static int flag_copy_as_regular = 1;
-
-/* If nonzero, dereference symbolic links (copy the files they point to). */
-static int flag_dereference = 1;
-
-/* If nonzero, remove existing destination nondirectories. */
-static int flag_force = 0;
-
-/* If nonzero, create hard links instead of copying files.
-   Create destination directories as usual. */
-static int flag_hard_link = 0;
-
-/* If nonzero, query before overwriting existing destinations
-   with regular files. */
-static int flag_interactive = 0;
-
 /* If nonzero, 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;
 
-/* If nonzero, give the copies the original files' permissions,
-   ownership, and timestamps. */
-static int flag_preserve = 0;
-
-/* If nonzero, copy directories recursively and copy special files
-   as themselves rather than copying their contents. */
-static int flag_recursive = 0;
-
-/* If nonzero, create symbolic links instead of copying files.
-   Create destination directories as usual. */
-static int flag_symbolic_link = 0;
-
-/* If nonzero, when copying recursively, skip any subdirectories that are
-   on different filesystems from the one we started on. */
-static int flag_one_file_system = 0;
-
-/* If nonzero, do not copy a nondirectory that has an existing destination
-   with the same or newer modification time. */
-static int flag_update = 0;
-
-/* If nonzero, display the names of the files before copying them. */
-static int flag_verbose = 0;
-
 static char const *const sparse_type_string[] =
 {
   "never", "auto", "always", 0
@@ -169,18 +86,9 @@ static enum Sparse_type const sparse_type[] =
   SPARSE_NEVER, SPARSE_AUTO, SPARSE_ALWAYS
 };
 
-/* Control creation of sparse files.  */
-static int flag_sparse = SPARSE_AUTO;
-
 /* The error code to return to the system. */
 static int exit_status = 0;
 
-/* The bits to preserve in created files' modes. */
-static int umask_kill;
-
-/* This process's effective user ID.  */
-static uid_t myeuid;
-
 /* If nonzero, display usage information and exit.  */
 static int show_help;
 
@@ -195,206 +103,291 @@ static struct option const long_opts[] =
   {"sparse", required_argument, NULL, 2},
   {"interactive", no_argument, NULL, 'i'},
   {"link", no_argument, NULL, 'l'},
-  {"no-dereference", no_argument, &flag_dereference, 0},
-  {"one-file-system", no_argument, &flag_one_file_system, 1},
+  {"no-dereference", no_argument, NULL, 'd'},
+  {"one-file-system", no_argument, NULL, 'x'},
   {"parents", no_argument, &flag_path, 1},
   {"path", no_argument, &flag_path, 1},
-  {"preserve", no_argument, &flag_preserve, 1},
+  {"preserve", no_argument, NULL, 'p'},
   {"recursive", no_argument, NULL, 'R'},
   {"suffix", required_argument, NULL, 'S'},
   {"symbolic-link", no_argument, NULL, 's'},
-  {"update", no_argument, &flag_update, 1},
-  {"verbose", no_argument, &flag_verbose, 1},
+  {"update", no_argument, NULL, 'u'},
+  {"verbose", no_argument, NULL, 'v'},
   {"version-control", required_argument, NULL, 'V'},
   {"help", no_argument, &show_help, 1},
   {"version", no_argument, &show_version, 1},
   {NULL, 0, NULL, 0}
 };
 
-static int
-is_ancestor (const struct stat *sb, const struct dir_list *ancestors)
+static void
+usage (int status)
 {
-  while (ancestors != 0)
+  if (status != 0)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+            program_name);
+  else
     {
-      if (ancestors->ino == sb->st_ino && ancestors->dev == sb->st_dev)
-       return 1;
-      ancestors = ancestors->parent;
-    }
-  return 0;
+      printf (_("\
+Usage: %s [OPTION]... SOURCE DEST\n\
+  or:  %s [OPTION]... SOURCE... DIRECTORY\n\
+"),
+             program_name, program_name);
+      printf (_("\
+Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n\
+\n\
+  -a, --archive                same as -dpR\n\
+  -b, --backup                 make backup before removal\n\
+  -d, --no-dereference         preserve links\n\
+  -f, --force                  remove existing destinations, never prompt\n\
+  -i, --interactive            prompt before overwrite\n\
+  -l, --link                   link files instead of copying\n\
+  -p, --preserve               preserve file attributes if possible\n\
+  -P, --parents                append source path to DIRECTORY\n\
+  -r                           copy recursively, non-directories as files\n\
+      --sparse=WHEN            control creation of sparse files\n\
+  -R, --recursive              copy directories recursively\n\
+  -s, --symbolic-link          make symbolic links instead of copying\n\
+  -S, --suffix=SUFFIX          override the usual backup suffix\n\
+  -u, --update                 copy only when the SOURCE file is newer\n\
+                                 than the destination file or when the\n\
+                                 destination file is missing\n\
+  -v, --verbose                explain what is being done\n\
+  -V, --version-control=WORD   override the usual version control\n\
+  -x, --one-file-system        stay on this file system\n\
+      --help                   display this help and exit\n\
+      --version                output version information and exit\n\
+\n\
+By default, sparse SOURCE files are detected by a crude heuristic and the\n\
+corresponding DEST file is made sparse as well.  That is the behavior\n\
+selected by --sparse=auto.  Specify --sparse=always to create a sparse DEST\n\
+file whenever the SOURCE file contains a long enough sequence of zero bytes.\n\
+Use --sparse=never to inhibit creation of sparse files.\n\
+\n\
+"));
+      printf (_("\
+The backup suffix is ~, unless set with SIMPLE_BACKUP_SUFFIX.  The\n\
+version control may be set with VERSION_CONTROL, values are:\n\
+\n\
+  t, numbered     make numbered backups\n\
+  nil, existing   numbered if numbered backups exist, simple otherwise\n\
+  never, simple   always make simple backups\n\
+"));
+      printf (_("\
+\n\
+As a special case, cp makes a backup of SOURCE when the force and backup\n\
+options are given and SOURCE and DEST are the same name for an existing,\n\
+regular file.\n\
+"));
+      puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
+    }
+  exit (status);
 }
 
-int
-main (int argc, char **argv)
-{
-  int c;
-  int make_backups = 0;
-  char *version;
+/* Ensure that the parent directories of CONST_DST_PATH 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.
 
-  program_name = argv[0];
-  setlocale (LC_ALL, "");
-  bindtextdomain (PACKAGE, LOCALEDIR);
-  textdomain (PACKAGE);
+   SRC_OFFSET is the index in CONST_DST_PATH of the beginning of the
+   source directory name.
 
-  myeuid = geteuid ();
+   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.
+   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.
 
-  version = getenv ("SIMPLE_BACKUP_SUFFIX");
-  if (version)
-    simple_backup_suffix = version;
-  version = getenv ("VERSION_CONTROL");
+   Return 0 if the parent of CONST_DST_PATH and any intermediate
+   directories specified by ATTR_LIST have the proper permissions
+   when done, otherwise 1. */
 
-  /* Find out the current file creation mask, to knock the right bits
-     when using chmod.  The creation mask is set to to be liberal, so
-     that created directories can be written, even if it would not
-     have been allowed with the mask this process was started with.  */
+static int
+re_protect (const char *const_dst_path, int 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 ();
 
-  umask_kill = 0777777 ^ umask (0);
+  dst_path = (char *) alloca (strlen (const_dst_path) + 1);
+  strcpy (dst_path, const_dst_path);
+  src_path = dst_path + src_offset;
 
-  while ((c = getopt_long (argc, argv, "abdfilprsuvxPRS:V:", long_opts,
-                          NULL)) != -1)
+  for (p = attr_list; p; p = p->next)
     {
-      switch (c)
+      struct stat src_sb;
+
+      dst_path[p->slash_offset] = '\0';
+
+      if ((*(x->xstat)) (src_path, &src_sb))
        {
-       case 0:
-         break;
+         error (0, errno, "%s", src_path);
+         return 1;
+       }
 
-       case 2:
-         {
-           int i;
+      /* Adjust the times (and if possible, ownership) for the copy.
+        chown turns off set[ug]id bits for non-root,
+        so do the chmod last.  */
 
-           /* --sparse={never,auto,always}  */
-           i = argmatch (optarg, sparse_type_string);
-           if (i < 0)
-             {
-               invalid_arg (_("sparse type"), optarg, i);
-               usage (1);
-             }
-           flag_sparse = sparse_type[i];
-         }
-         break;
+      if (x->preserve)
+       {
+         struct utimbuf utb;
 
-       case 'a':               /* Like -dpR. */
-         flag_dereference = 0;
-         flag_preserve = 1;
-         flag_recursive = 1;
-         flag_copy_as_regular = 0;
-         break;
+         utb.actime = src_sb.st_atime;
+         utb.modtime = src_sb.st_mtime;
 
-       case 'b':
-         make_backups = 1;
-         break;
+         if (utime (dst_path, &utb))
+           {
+             error (0, errno, "%s", dst_path);
+             return 1;
+           }
 
-       case 'd':
-         flag_dereference = 0;
-         break;
+         /* 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.  */
+         if (chown (dst_path, src_sb.st_uid, src_sb.st_gid)
+             && (errno != EPERM || myeuid == 0))
+           {
+             error (0, errno, "%s", dst_path);
+             return 1;
+           }
+       }
 
-       case 'f':
-         flag_force = 1;
-         flag_interactive = 0;
-         break;
+      if (x->preserve || p->is_new_dir)
+       {
+         if (chmod (dst_path, src_sb.st_mode & x->umask_kill))
+           {
+             error (0, errno, "%s", dst_path);
+             return 1;
+           }
+       }
 
-       case 'i':
-         flag_force = 0;
-         flag_interactive = 1;
-         break;
+      dst_path[p->slash_offset] = '/';
+    }
+  return 0;
+}
 
-       case 'l':
-         flag_hard_link = 1;
-         break;
+/* Ensure that the parent directory of CONST_DIRPATH exists, for
+   the --parents option.
 
-       case 'p':
-         flag_preserve = 1;
-         break;
+   SRC_OFFSET is the index in CONST_DIRPATH (which is a destination
+   path) 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
+   string for printing a message after successfully making a directory.
+   The format should take two string arguments: the names of the
+   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.
 
-       case 'P':
-         flag_path = 1;
-         break;
+   Return 0 if parent of CONST_DIRPATH exists as a directory with the proper
+   permissions when done, otherwise 1. */
 
-       case 'r':
-         flag_recursive = 1;
-         flag_copy_as_regular = 1;
-         break;
+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)())
+{
+  struct stat stats;
+  char *dirpath;               /* A copy of CONST_DIRPATH we can change. */
+  char *src;                   /* Source name in `dirpath'. */
+  char *tmp_dst_dirname;       /* Leading path of `dirpath', malloc. */
+  char *dst_dirname;           /* Leading path of `dirpath', alloca. */
 
-       case 'R':
-         flag_recursive = 1;
-         flag_copy_as_regular = 0;
-         break;
+  dirpath = (char *) alloca (strlen (const_dirpath) + 1);
+  strcpy (dirpath, const_dirpath);
 
-       case 's':
-#ifdef S_ISLNK
-         flag_symbolic_link = 1;
-#else
-         error (1, 0, _("symbolic links are not supported on this system"));
-#endif
-         break;
+  src = dirpath + src_offset;
 
-       case 'u':
-         flag_update = 1;
-         break;
+  tmp_dst_dirname = dirname (dirpath);
+  dst_dirname = (char *) alloca (strlen (tmp_dst_dirname) + 1);
+  strcpy (dst_dirname, tmp_dst_dirname);
+  free (tmp_dst_dirname);
 
-       case 'v':
-         flag_verbose = 1;
-         break;
+  *attr_list = NULL;
 
-       case 'x':
-         flag_one_file_system = 1;
-         break;
+  if ((*xstat) (dst_dirname, &stats))
+    {
+      /* Parent of CONST_DIRNAME does not exist.
+        Make all missing intermediate directories. */
+      char *slash;
 
-       case 'S':
-         simple_backup_suffix = optarg;
-         break;
+      slash = src;
+      while (*slash == '/')
+       slash++;
+      while ((slash = strchr (slash, '/')))
+       {
+         /* Add this directory to the list of directories whose modes need
+            fixing later. */
+         struct dir_attr *new =
+           (struct dir_attr *) xmalloc (sizeof (struct dir_attr));
+         new->slash_offset = slash - dirpath;
+         new->next = *attr_list;
+         *attr_list = new;
 
-       case 'V':
-         version = optarg;
-         break;
+         *slash = '\0';
+         if ((*xstat) (dirpath, &stats))
+           {
+             /* This element of the path 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))
+               {
+                 error (0, errno, _("cannot make directory `%s'"), dirpath);
+                 return 1;
+               }
+             else
+               {
+                 if (verbose_fmt_string != NULL)
+                   printf (verbose_fmt_string, src, dirpath);
+               }
+           }
+         else if (!S_ISDIR (stats.st_mode))
+           {
+             error (0, 0, _("`%s' exists but is not a directory"), dirpath);
+             return 1;
+           }
+         else
+           {
+             new->is_new_dir = 0;
+             *new_dst = 0;
+           }
+         *slash++ = '/';
 
-       default:
-         usage (1);
+         /* Avoid unnecessary calls to `stat' when given
+            pathnames containing multiple adjacent slashes.  */
+         while (*slash == '/')
+           slash++;
        }
     }
 
-  if (show_version)
+  /* We get here if the parent of `dirpath' already exists. */
+
+  else if (!S_ISDIR (stats.st_mode))
     {
-      printf ("cp (%s) %s\n", GNU_PACKAGE, VERSION);
-      exit (0);
+      error (0, 0, _("`%s' exists but is not a directory"), dst_dirname);
+      return 1;
     }
-
-  if (show_help)
-    usage (0);
-
-  if (flag_hard_link && flag_symbolic_link)
+  else
     {
-      error (0, 0, _("cannot make both hard and symbolic links"));
-      usage (1);
+      *new_dst = 0;
     }
-
-  if (make_backups)
-    backup_type = get_version (version);
-
-  if (flag_preserve == 1)
-    umask_kill = 0777777;
-
-  /* The key difference between -d (--no-dereference) and not is the version
-     of `stat' to call.  */
-
-  if (flag_dereference)
-    xstat = stat;
-  else
-    xstat = lstat;
-
-  /* Allocate space for remembering copied and created files.  */
-
-  hash_init (INITIAL_HASH_MODULE, INITIAL_ENTRY_TAB_SIZE);
-
-  exit_status |= do_copy (argc, argv);
-
-  exit (exit_status);
+  return 0;
 }
 
 /* Scan the arguments, and copy each by calling copy.
    Return 0 if successful, 1 if any errors occur. */
 
 static int
-do_copy (int argc, char **argv)
+do_copy (int argc, char **argv, const struct cp_options *x)
 {
   char *dest;
   struct stat sb;
@@ -467,9 +460,10 @@ do_copy (int argc, char **argv)
              parent_exists = !make_path_private (dst_path,
                                                  arg_in_concat - dst_path,
                                                  0700,
-                                                 (flag_verbose
+                                                 (x->verbose
                                                   ? "%s -> %s\n" : NULL),
-                                                 &attr_list, &new_dst);
+                                                 &attr_list, &new_dst,
+                                                 x->xstat);
            }
          else
            {
@@ -489,13 +483,13 @@ do_copy (int argc, char **argv)
            }
          else
            {
-             ret |= copy (arg, dst_path, new_dst, 0, (struct dir_list *) 0);
+             ret |= copy (arg, dst_path, new_dst, CP_OPTIONS);
              forget_all ();
 
              if (flag_path)
                {
                  ret |= re_protect (dst_path, arg_in_concat - dst_path,
-                                    attr_list);
+                                    attr_list, x);
                }
            }
 
@@ -527,7 +521,7 @@ do_copy (int argc, char **argv)
         `cp --force --backup foo foo' to `cp --force foo fooSUFFIX'
         where SUFFIX is determined by any version control options used.  */
 
-      if (flag_force
+      if (x->force
          && backup_type != none
          && STREQ (source, dest)
          && !new_dst && S_ISREG (sb.st_mode))
@@ -571,7 +565,7 @@ do_copy (int argc, char **argv)
          new_dest = dest;
        }
 
-      return copy (source, new_dest, new_dst, 0, (struct dir_list *) 0);
+      return copy (source, new_dest, new_dst, CP_OPTIONS);
     }
   else
     {
@@ -583,860 +577,188 @@ is not a directory"),
     }
 }
 
-/* Copy the file SRC_PATH to the file DST_PATH.  The files may be of
-   any type.  NEW_DST should be nonzero if the file DST_PATH cannot
-   exist because its parent directory was just created; NEW_DST should
-   be zero if DST_PATH might already exist.  DEVICE is the device
-   number of the parent directory, or 0 if the parent of this file is
-   not known.  ANCESTORS points to a linked, null terminated list of
-   devices and inodes of parent directories of SRC_PATH.
-   Return 0 if successful, 1 if an error occurs. */
-
-static int
-copy (const char *src_path, const char *dst_path, int new_dst, dev_t device,
-      struct dir_list *ancestors)
+static void
+cp_option_init (struct cp_options *x)
 {
-  struct stat src_sb;
-  struct stat dst_sb;
-  int src_mode;
-  int src_type;
-  char *earlier_file;
-  char *dst_backup = NULL;
-  int fix_mode = 0;
-
-  if ((*xstat) (src_path, &src_sb))
-    {
-      error (0, errno, "%s", src_path);
-      return 1;
-    }
-
-  /* Are we crossing a file system boundary?  */
-  if (flag_one_file_system && device != 0 && device != src_sb.st_dev)
-    return 0;
+  x->copy_as_regular = 1;
+  x->dereference = 1;
+  x->force = 0;
+  x->hard_link = 0;
+  x->interactive = 0;
+  x->myeuid = geteuid ();
+  x->one_file_system = 0;
+  x->preserve = 0;
+  x->recursive = 0;
+  x->sparse_mode = SPARSE_AUTO;
+  x->symbolic_link = 0;
 
-  /* We wouldn't insert a node unless nlink > 1, except that we need to
-     find created files so as to not copy infinitely if a directory is
-     copied into itself.  */
+  /* Find out the current file creation mask, to knock the right bits
+     when using chmod.  The creation mask is set to be liberal, so
+     that created directories can be written, even if it would not
+     have been allowed with the mask this process was started with.  */
+  x->umask_kill = 0777777 ^ umask (0);
 
-  earlier_file = remember_copied (dst_path, src_sb.st_ino, src_sb.st_dev);
+  x->update = 0;
+  x->verbose = 0;
+}
 
-  /* Did we just create this file?  */
+int
+main (int argc, char **argv)
+{
+  int c;
+  int make_backups = 0;
+  char *version;
+  struct cp_options x;
 
-  if (earlier_file == &new_file)
-    return 0;
+  program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
 
-  src_mode = src_sb.st_mode;
-  src_type = src_sb.st_mode;
+  cp_option_init (&x);
 
-  if (S_ISDIR (src_type) && !flag_recursive)
-    {
-      error (0, 0, _("%s: omitting directory"), src_path);
-      return 1;
-    }
+  version = getenv ("SIMPLE_BACKUP_SUFFIX");
+  if (version)
+    simple_backup_suffix = version;
+  version = getenv ("VERSION_CONTROL");
 
-  if (!new_dst)
+  while ((c = getopt_long (argc, argv, "abdfilprsuvxPRS:V:", long_opts, NULL))
+        != -1)
     {
-      if ((*xstat) (dst_path, &dst_sb))
-       {
-         if (errno != ENOENT)
-           {
-             error (0, errno, "%s", dst_path);
-             return 1;
-           }
-         else
-           new_dst = 1;
-       }
-      else
+      switch (c)
        {
-         /* The file exists already.  */
-
-         if (src_sb.st_ino == dst_sb.st_ino && src_sb.st_dev == dst_sb.st_dev)
-           {
-             if (flag_hard_link)
-               return 0;
-
-             error (0, 0, _("`%s' and `%s' are the same file"),
-                    src_path, dst_path);
-             return 1;
-           }
+       case 0:
+         break;
 
-         if (!S_ISDIR (src_type))
-           {
-             if (S_ISDIR (dst_sb.st_mode))
-               {
-                 error (0, 0,
-                      _("%s: cannot overwrite directory with non-directory"),
-                        dst_path);
-                 return 1;
-               }
+       case 2:
+         {
+           int i;
 
-             if (flag_update && src_sb.st_mtime <= dst_sb.st_mtime)
-               return 0;
-           }
+           /* --sparse={never,auto,always}  */
+           i = argmatch (optarg, sparse_type_string);
+           if (i < 0)
+             {
+               invalid_arg (_("sparse type"), optarg, i);
+               usage (1);
+             }
+           x.sparse_mode = sparse_type[i];
+         }
+         break;
 
-         if (!S_ISDIR (src_type) && !flag_force && flag_interactive)
-           {
-             if (euidaccess (dst_path, W_OK) != 0)
-               {
-                 fprintf (stderr,
-                          _("%s: overwrite `%s', overriding mode %04o? "),
-                          program_name, dst_path,
-                          (unsigned int) (dst_sb.st_mode & 07777));
-               }
-             else
-               {
-                 fprintf (stderr, _("%s: overwrite `%s'? "),
-                          program_name, dst_path);
-               }
-             if (!yesno ())
-               return 0;
-           }
+       case 'a':               /* Like -dpR. */
+         x.dereference = 0;
+         x.preserve = 1;
+         x.recursive = 1;
+         x.copy_as_regular = 0;
+         break;
 
-         if (backup_type != none && !S_ISDIR (dst_sb.st_mode))
-           {
-             char *tmp_backup = find_backup_file_name (dst_path);
-             if (tmp_backup == NULL)
-               error (1, 0, _("virtual memory exhausted"));
+       case 'b':
+         make_backups = 1;
+         break;
 
-             /* Detect (and fail) when creating the backup file would
-                destroy the source file.  Before, running the commands
-                cd /tmp; rm -f a a~; : > a; echo A > a~; cp -b -V simple a~ a
-                would leave two zero-length files: a and a~.  */
-             if (STREQ (tmp_backup, src_path))
-               {
-                 error (0, 0,
-                  _("backing up `%s' would destroy source;  `%s' not copied"),
-                        dst_path, src_path);
-                 return 1;
+       case 'd':
+         x.dereference = 0;
+         break;
 
-               }
-             dst_backup = (char *) alloca (strlen (tmp_backup) + 1);
-             strcpy (dst_backup, tmp_backup);
-             free (tmp_backup);
-             if (rename (dst_path, dst_backup))
-               {
-                 if (errno != ENOENT)
-                   {
-                     error (0, errno, _("cannot backup `%s'"), dst_path);
-                     return 1;
-                   }
-                 else
-                   dst_backup = NULL;
-               }
-             new_dst = 1;
-           }
-         else if (flag_force)
-           {
-             if (S_ISDIR (dst_sb.st_mode))
-               {
-                 /* Temporarily change mode to allow overwriting. */
-                 if (euidaccess (dst_path, W_OK | X_OK) != 0)
-                   {
-                     if (chmod (dst_path, 0700))
-                       {
-                         error (0, errno, "%s", dst_path);
-                         return 1;
-                       }
-                     else
-                       fix_mode = 1;
-                   }
-               }
-             else
-               {
-                 if (unlink (dst_path) && errno != ENOENT)
-                   {
-                     error (0, errno, _("cannot remove old link to `%s'"),
-                            dst_path);
-                     return 1;
-                   }
-                 new_dst = 1;
-               }
-           }
-       }
-    }
+       case 'f':
+         x.force = 1;
+         x.interactive = 0;
+         break;
 
-  /* If the source is a directory, we don't always create the destination
-     directory.  So --verbose should not announce anything until we're
-     sure we'll create a directory. */
-  if (flag_verbose && !S_ISDIR (src_type))
-    printf ("%s -> %s\n", src_path, dst_path);
+       case 'i':
+         x.force = 0;
+         x.interactive = 1;
+         break;
 
-  /* Did we copy this inode somewhere else (in this command line argument)
-     and therefore this is a second hard link to the inode?  */
+       case 'l':
+         x.hard_link = 1;
+         break;
 
-  if (!flag_dereference && src_sb.st_nlink > 1 && earlier_file)
-    {
-      if (link (earlier_file, dst_path))
-       {
-         error (0, errno, "%s", dst_path);
-         goto un_backup;
-       }
-      return 0;
-    }
+       case 'p':
+         x.preserve = 1;
+         break;
 
-  if (S_ISDIR (src_type))
-    {
-      struct dir_list *dir;
+       case 'P':
+         flag_path = 1;
+         break;
 
-      /* If this directory has been copied before during the
-         recursion, there is a symbolic link to an ancestor
-         directory of the symbolic link.  It is impossible to
-         continue to copy this, unless we've got an infinite disk.  */
+       case 'r':
+         x.recursive = 1;
+         x.copy_as_regular = 1;
+         break;
 
-      if (is_ancestor (&src_sb, ancestors))
-       {
-         error (0, 0, _("%s: cannot copy cyclic symbolic link"), src_path);
-         goto un_backup;
-       }
+       case 'R':
+         x.recursive = 1;
+         x.copy_as_regular = 0;
+         break;
 
-      /* Insert the current directory in the list of parents.  */
+       case 's':
+#ifdef S_ISLNK
+         x.symbolic_link = 1;
+#else
+         error (1, 0, _("symbolic links are not supported on this system"));
+#endif
+         break;
 
-      dir = (struct dir_list *) alloca (sizeof (struct dir_list));
-      dir->parent = ancestors;
-      dir->ino = src_sb.st_ino;
-      dir->dev = src_sb.st_dev;
+       case 'u':
+         x.update = 1;
+         break;
 
-      if (new_dst || !S_ISDIR (dst_sb.st_mode))
-       {
-         /* Create the new directory writable and searchable, so
-             we can create new entries in it.  */
+       case 'v':
+         x.verbose = 1;
+         break;
 
-         if (mkdir (dst_path, (src_mode & umask_kill) | 0700))
-           {
-             error (0, errno, _("cannot create directory `%s'"), dst_path);
-             goto un_backup;
-           }
+       case 'x':
+         x.one_file_system = 1;
+         break;
 
-         /* Insert the created directory's inode and device
-             numbers into the search structure, so that we can
-             avoid copying it again.  */
+       case 'S':
+         simple_backup_suffix = optarg;
+         break;
 
-         if (remember_created (dst_path))
-           goto un_backup;
+       case 'V':
+         version = optarg;
+         break;
 
-         if (flag_verbose)
-           printf ("%s -> %s\n", src_path, dst_path);
+       default:
+         usage (1);
        }
-
-      /* Copy the contents of the directory.  */
-
-      if (copy_dir (src_path, dst_path, new_dst, &src_sb, dir))
-       return 1;
     }
-#ifdef S_ISLNK
-  else if (flag_symbolic_link)
-    {
-      if (*src_path == '/'
-         || (!strncmp (dst_path, "./", 2) && strchr (dst_path + 2, '/') == 0)
-         || strchr (dst_path, '/') == 0)
-       {
-         if (symlink (src_path, dst_path))
-           {
-             error (0, errno, "%s", dst_path);
-             goto un_backup;
-           }
 
-         return 0;
-       }
-      else
-       {
-         error (0, 0,
-          _("%s: can make relative symbolic links only in current directory"),
-                dst_path);
-         goto un_backup;
-       }
-    }
-#endif
-  else if (flag_hard_link)
-    {
-      if (link (src_path, dst_path))
-       {
-         error (0, errno, _("cannot create link `%s'"), dst_path);
-         goto un_backup;
-       }
-      return 0;
-    }
-  else if (S_ISREG (src_type)
-          || (flag_copy_as_regular && !S_ISDIR (src_type)
-#ifdef S_ISLNK
-              && !S_ISLNK (src_type)
-#endif
-              ))
-    {
-      if (copy_reg (src_path, dst_path))
-       goto un_backup;
-    }
-  else
-#ifdef S_ISFIFO
-  if (S_ISFIFO (src_type))
-    {
-      if (mkfifo (dst_path, src_mode & umask_kill))
-       {
-         error (0, errno, _("cannot create fifo `%s'"), dst_path);
-         goto un_backup;
-       }
-    }
-  else
-#endif
-    if (S_ISBLK (src_type) || S_ISCHR (src_type)
-#ifdef S_ISSOCK
-       || S_ISSOCK (src_type)
-#endif
-       )
+  if (show_version)
     {
-      if (mknod (dst_path, src_mode & umask_kill, src_sb.st_rdev))
-       {
-         error (0, errno, _("cannot create special file `%s'"), dst_path);
-         goto un_backup;
-       }
+      printf ("cp (%s) %s\n", GNU_PACKAGE, VERSION);
+      exit (0);
     }
-  else
-#ifdef S_ISLNK
-  if (S_ISLNK (src_type))
-    {
-      char *link_val;
-      int link_size;
-
-      link_val = (char *) alloca (PATH_MAX + 2);
-      link_size = readlink (src_path, link_val, PATH_MAX + 1);
-      if (link_size < 0)
-       {
-         error (0, errno, _("cannot read symbolic link `%s'"), src_path);
-         goto un_backup;
-       }
-      link_val[link_size] = '\0';
-
-      if (symlink (link_val, dst_path))
-       {
-         error (0, errno, _("cannot create symbolic link `%s'"), dst_path);
-         goto un_backup;
-       }
 
-      if (flag_preserve)
-       {
-         /* Preserve the owner and group of the just-`copied'
-            symbolic link, if possible.  */
-#ifdef HAVE_LCHOWN
-         if (DO_CHOWN (lchown, dst_path, src_sb.st_uid, src_sb.st_gid))
-           {
-             error (0, errno, "%s", dst_path);
-             goto un_backup;
-           }
-#else
-# ifdef ROOT_CHOWN_AFFECTS_SYMLINKS
-         if (myeuid == 0)
-           {
-             if (DO_CHOWN (chown, dst_path, src_sb.st_uid, src_sb.st_gid))
-               {
-                 error (0, errno, "%s", dst_path);
-                 goto un_backup;
-               }
-           }
-         else
-           {
-             /* FIXME: maybe give a diagnostic: you must be root
-                to preserve ownership and group of symlinks.  */
-           }
-# else
-         /* Can't preserve ownership of symlinks.
-            FIXME: maybe give a warning or even error for symlinks
-            in directories with the sticky bit set -- there, not
-            preserving owner/group is a potential security problem.  */
-# endif
-#endif
-       }
+  if (show_help)
+    usage (0);
 
-      return 0;
-    }
-  else
-#endif
+  if (x.hard_link && x.symbolic_link)
     {
-      error (0, 0, _("%s: unknown file type"), src_path);
-      goto un_backup;
+      error (0, 0, _("cannot make both hard and symbolic links"));
+      usage (1);
     }
 
-  /* Adjust the times (and if possible, ownership) for the copy.
-     chown turns off set[ug]id bits for non-root,
-     so do the chmod last.  */
+  if (make_backups)
+    backup_type = get_version (version);
 
-  if (flag_preserve)
-    {
-      struct utimbuf utb;
+  if (x.preserve == 1)
+    x.umask_kill = 0777777;
 
-      utb.actime = src_sb.st_atime;
-      utb.modtime = src_sb.st_mtime;
+  /* The key difference between -d (--no-dereference) and not is the version
+     of `stat' to call.  */
 
-      if (utime (dst_path, &utb))
-       {
-         error (0, errno, "%s", dst_path);
-         return 1;
-       }
+  if (x.dereference)
+    x.xstat = stat;
+  else
+    x.xstat = lstat;
 
-      if (DO_CHOWN (chown, dst_path, src_sb.st_uid, src_sb.st_gid))
-       {
-         error (0, errno, "%s", dst_path);
-         return 1;
-       }
-    }
+  /* Allocate space for remembering copied and created files.  */
 
-  if ((flag_preserve || new_dst)
-      && (flag_copy_as_regular || S_ISREG (src_type) || S_ISDIR (src_type)))
-    {
-      if (chmod (dst_path, src_mode & umask_kill))
-       {
-         error (0, errno, "%s", dst_path);
-         return 1;
-       }
-    }
-  else if (fix_mode)
-    {
-      /* Reset the temporarily changed mode.  */
-      if (chmod (dst_path, dst_sb.st_mode))
-       {
-         error (0, errno, "%s", dst_path);
-         return 1;
-       }
-    }
+  hash_init (INITIAL_HASH_MODULE, INITIAL_ENTRY_TAB_SIZE);
 
-  return 0;
+  exit_status |= do_copy (argc, argv, &x);
 
-un_backup:
-  if (dst_backup)
-    {
-      if (rename (dst_backup, dst_path))
-       error (0, errno, _("cannot un-backup `%s'"), dst_path);
-    }
-  return 1;
-}
-\f
-/* Ensure that the parent directory of CONST_DIRPATH 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.
-   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
-   string for printing a message after successfully making a directory.
-   The format should take two string arguments: the names of the
-   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.
-
-   Return 0 if parent of CONST_DIRPATH exists as a directory with the proper
-   permissions when done, otherwise 1. */
-
-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)
-{
-  struct stat stats;
-  char *dirpath;               /* A copy of CONST_DIRPATH we can change. */
-  char *src;                   /* Source name in `dirpath'. */
-  char *tmp_dst_dirname;       /* Leading path of `dirpath', malloc. */
-  char *dst_dirname;           /* Leading path of `dirpath', alloca. */
-
-  dirpath = (char *) alloca (strlen (const_dirpath) + 1);
-  strcpy (dirpath, const_dirpath);
-
-  src = dirpath + src_offset;
-
-  tmp_dst_dirname = dirname (dirpath);
-  dst_dirname = (char *) alloca (strlen (tmp_dst_dirname) + 1);
-  strcpy (dst_dirname, tmp_dst_dirname);
-  free (tmp_dst_dirname);
-
-  *attr_list = NULL;
-
-  if ((*xstat) (dst_dirname, &stats))
-    {
-      /* Parent of CONST_DIRNAME does not exist.
-        Make all missing intermediate directories. */
-      char *slash;
-
-      slash = src;
-      while (*slash == '/')
-       slash++;
-      while ((slash = strchr (slash, '/')))
-       {
-         /* Add this directory to the list of directories whose modes need
-            fixing later. */
-         struct dir_attr *new =
-           (struct dir_attr *) xmalloc (sizeof (struct dir_attr));
-         new->slash_offset = slash - dirpath;
-         new->next = *attr_list;
-         *attr_list = new;
-
-         *slash = '\0';
-         if ((*xstat) (dirpath, &stats))
-           {
-             /* This element of the path 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))
-               {
-                 error (0, errno, _("cannot make directory `%s'"), dirpath);
-                 return 1;
-               }
-             else
-               {
-                 if (verbose_fmt_string != NULL)
-                   printf (verbose_fmt_string, src, dirpath);
-               }
-           }
-         else if (!S_ISDIR (stats.st_mode))
-           {
-             error (0, 0, _("`%s' exists but is not a directory"), dirpath);
-             return 1;
-           }
-         else
-           {
-             new->is_new_dir = 0;
-             *new_dst = 0;
-           }
-         *slash++ = '/';
-
-         /* Avoid unnecessary calls to `stat' when given
-            pathnames containing multiple adjacent slashes.  */
-         while (*slash == '/')
-           slash++;
-       }
-    }
-
-  /* We get here if the parent of `dirpath' already exists. */
-
-  else if (!S_ISDIR (stats.st_mode))
-    {
-      error (0, 0, _("`%s' exists but is not a directory"), dst_dirname);
-      return 1;
-    }
-  else
-    {
-      *new_dst = 0;
-    }
-  return 0;
-}
-\f
-/* Ensure that the parent directories of CONST_DST_PATH 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
-   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.
-   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
-   directories specified by ATTR_LIST have the proper permissions
-   when done, otherwise 1. */
-
-static int
-re_protect (const char *const_dst_path, int src_offset,
-           struct dir_attr *attr_list)
-{
-  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'. */
-
-  dst_path = (char *) alloca (strlen (const_dst_path) + 1);
-  strcpy (dst_path, const_dst_path);
-  src_path = dst_path + src_offset;
-
-  for (p = attr_list; p; p = p->next)
-    {
-      struct stat src_sb;
-
-      dst_path[p->slash_offset] = '\0';
-
-      if ((*xstat) (src_path, &src_sb))
-       {
-         error (0, errno, "%s", src_path);
-         return 1;
-       }
-
-      /* Adjust the times (and if possible, ownership) for the copy.
-        chown turns off set[ug]id bits for non-root,
-        so do the chmod last.  */
-
-      if (flag_preserve)
-       {
-         struct utimbuf utb;
-
-         utb.actime = src_sb.st_atime;
-         utb.modtime = src_sb.st_mtime;
-
-         if (utime (dst_path, &utb))
-           {
-             error (0, errno, "%s", dst_path);
-             return 1;
-           }
-
-         /* 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.  */
-         if (chown (dst_path, src_sb.st_uid, src_sb.st_gid)
-             && (errno != EPERM || myeuid == 0))
-           {
-             error (0, errno, "%s", dst_path);
-             return 1;
-           }
-       }
-
-      if (flag_preserve || p->is_new_dir)
-       {
-         if (chmod (dst_path, src_sb.st_mode & umask_kill))
-           {
-             error (0, errno, "%s", dst_path);
-             return 1;
-           }
-       }
-
-      dst_path[p->slash_offset] = '/';
-    }
-  return 0;
-}
-\f
-/* Read the contents of the directory SRC_PATH_IN, and recursively
-   copy the contents to DST_PATH_IN.  NEW_DST is nonzero if
-   DST_PATH_IN is a directory that was created previously in the
-   recursion.   SRC_SB and ANCESTORS describe SRC_PATH_IN.
-   Return 0 if successful, -1 if an error occurs. */
-
-static int
-copy_dir (const char *src_path_in, const char *dst_path_in, int new_dst,
-         const struct stat *src_sb, struct dir_list *ancestors)
-{
-  char *name_space;
-  char *namep;
-  char *src_path;
-  char *dst_path;
-  int ret = 0;
-
-  errno = 0;
-  name_space = savedir (src_path_in, src_sb->st_size);
-  if (name_space == 0)
-    {
-      if (errno)
-       {
-         error (0, errno, "%s", src_path_in);
-         return -1;
-       }
-      else
-       error (1, 0, _("virtual memory exhausted"));
-    }
-
-  namep = name_space;
-  while (*namep != '\0')
-    {
-      int fn_length = strlen (namep) + 1;
-
-      dst_path = xmalloc (strlen (dst_path_in) + fn_length + 1);
-      src_path = xmalloc (strlen (src_path_in) + fn_length + 1);
-
-      stpcpy (stpcpy (stpcpy (src_path, src_path_in), "/"), namep);
-      stpcpy (stpcpy (stpcpy (dst_path, dst_path_in), "/"), namep);
-
-      ret |= copy (src_path, dst_path, new_dst, src_sb->st_dev, ancestors);
-
-      /* Free the memory for `src_path'.  The memory for `dst_path'
-        cannot be deallocated, since it is used to create multiple
-        hard links.  */
-
-      free (src_path);
-
-      namep += fn_length;
-    }
-  free (name_space);
-  return -ret;
-}
-\f
-/* Copy a regular file from SRC_PATH to DST_PATH.
-   If the source file contains holes, copies holes and blocks of zeros
-   in the source file as holes in the destination file.
-   (Holes are read as zeroes by the `read' system call.)
-   Return 0 if successful, -1 if an error occurred. */
-
-static int
-copy_reg (const char *src_path, const char *dst_path)
-{
-  char *buf;
-  int buf_size;
-  int dest_desc;
-  int source_desc;
-  int n_read;
-  struct stat sb;
-  char *cp;
-  int *ip;
-  int return_val = 0;
-  long n_read_total = 0;
-  int last_write_made_hole = 0;
-  int make_holes = (flag_sparse == SPARSE_ALWAYS);
-
-  source_desc = open (src_path, O_RDONLY);
-  if (source_desc < 0)
-    {
-      error (0, errno, "%s", src_path);
-      return -1;
-    }
-
-  /* Create the new regular file with small permissions initially,
-     to not create a security hole.  */
-
-  dest_desc = open (dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
-  if (dest_desc < 0)
-    {
-      error (0, errno, _("cannot create regular file `%s'"), dst_path);
-      return_val = -1;
-      goto ret2;
-    }
-
-  /* Find out the optimal buffer size.  */
-
-  if (fstat (dest_desc, &sb))
-    {
-      error (0, errno, "%s", dst_path);
-      return_val = -1;
-      goto ret;
-    }
-
-  buf_size = ST_BLKSIZE (sb);
-
-#ifdef HAVE_ST_BLOCKS
-  if (flag_sparse == SPARSE_AUTO && S_ISREG (sb.st_mode))
-    {
-      /* Use a heuristic to determine whether SRC_PATH contains any
-        sparse blocks. */
-
-      if (fstat (source_desc, &sb))
-       {
-         error (0, errno, "%s", src_path);
-         return_val = -1;
-         goto ret;
-       }
-
-      /* If the file has fewer blocks than would normally
-        be needed for a file of its size, then
-        at least one of the blocks in the file is a hole. */
-      if (S_ISREG (sb.st_mode)
-         && (size_t) (sb.st_size / 512) > (size_t) ST_NBLOCKS (sb))
-       make_holes = 1;
-    }
-#endif
-
-  /* Make a buffer with space for a sentinel at the end.  */
-
-  buf = (char *) alloca (buf_size + sizeof (int));
-
-  for (;;)
-    {
-      n_read = read (source_desc, buf, buf_size);
-      if (n_read < 0)
-       {
-#ifdef EINTR
-         if (errno == EINTR)
-           continue;
-#endif
-         error (0, errno, "%s", src_path);
-         return_val = -1;
-         goto ret;
-       }
-      if (n_read == 0)
-       break;
-
-      n_read_total += n_read;
-
-      ip = 0;
-      if (make_holes)
-       {
-         buf[n_read] = 1;      /* Sentinel to stop loop.  */
-
-         /* Find first nonzero *word*, or the word with the sentinel.  */
-
-         ip = (int *) buf;
-         while (*ip++ == 0)
-           ;
-
-         /* Find the first nonzero *byte*, or the sentinel.  */
-
-         cp = (char *) (ip - 1);
-         while (*cp++ == 0)
-           ;
-
-         /* If we found the sentinel, the whole input block was zero,
-            and we can make a hole.  */
-
-         if (cp > buf + n_read)
-           {
-             /* Make a hole.  */
-             if (lseek (dest_desc, (off_t) n_read, SEEK_CUR) < 0L)
-               {
-                 error (0, errno, "%s", dst_path);
-                 return_val = -1;
-                 goto ret;
-               }
-             last_write_made_hole = 1;
-           }
-         else
-           /* Clear to indicate that a normal write is needed. */
-           ip = 0;
-       }
-      if (ip == 0)
-       {
-         if (full_write (dest_desc, buf, n_read) < 0)
-           {
-             error (0, errno, "%s", dst_path);
-             return_val = -1;
-             goto ret;
-           }
-         last_write_made_hole = 0;
-       }
-    }
-
-  /* If the file ends with a `hole', something needs to be written at
-     the end.  Otherwise the kernel would truncate the file at the end
-     of the last write operation.  */
-
-  if (last_write_made_hole)
-    {
-#ifdef HAVE_FTRUNCATE
-      /* Write a null character and truncate it again.  */
-      if (full_write (dest_desc, "", 1) < 0
-         || ftruncate (dest_desc, n_read_total) < 0)
-#else
-      /* Seek backwards one character and write a null.  */
-      if (lseek (dest_desc, (off_t) -1, SEEK_CUR) < 0L
-         || full_write (dest_desc, "", 1) < 0)
-#endif
-       {
-         error (0, errno, "%s", dst_path);
-         return_val = -1;
-       }
-    }
-
-ret:
-  if (close (dest_desc) < 0)
-    {
-      error (0, errno, "%s", dst_path);
-      return_val = -1;
-    }
-ret2:
-  if (close (source_desc) < 0)
-    {
-      error (0, errno, "%s", src_path);
-      return_val = -1;
-    }
-
-  return return_val;
+  exit (exit_status);
 }