summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>1997-01-11 04:21:29 +0000
committerJim Meyering <jim@meyering.net>1997-01-11 04:21:29 +0000
commit0503b2807605cc1cef5b5b8bace7b4c03178a7e4 (patch)
tree157148b6bc99954efa44e19f189ff9692b2f3544 /src
parentf45eaa5782dd8843ce4426e84e14c2467bd4fea1 (diff)
downloadcoreutils-0503b2807605cc1cef5b5b8bace7b4c03178a7e4.tar.xz
.
Diffstat (limited to 'src')
-rw-r--r--src/copy.c770
1 files changed, 770 insertions, 0 deletions
diff --git a/src/copy.c b/src/copy.c
new file mode 100644
index 000000000..735e9bd22
--- /dev/null
+++ b/src/copy.c
@@ -0,0 +1,770 @@
+#ifdef _AIX
+ #pragma alloca
+#endif
+
+#include <config.h>
+#include <stdio.h>
+
+#include "cp.h"
+#include "backupfile.h"
+
+/* 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), (x->myeuid == 0 ? (New_uid) : x->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 || x->myeuid == 0))
+
+/* 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
+};
+
+struct flag
+{
+ /* If nonzero, copy all files except (directories and, if not dereferencing
+ them, symbolic links,) as if they were regular files. */
+ int copy_as_regular;
+
+ /* If nonzero, dereference symbolic links (copy the files they point to). */
+ int dereference;
+
+ /* If nonzero, remove existing destination nondirectories. */
+ int force;
+
+ /* If nonzero, create hard links instead of copying files.
+ Create destination directories as usual. */
+ int hard_link;
+
+ /* If nonzero, query before overwriting existing destinations
+ with regular files. */
+ int interactive;
+
+ /* If nonzero, when copying recursively, skip any subdirectories that are
+ on different filesystems from the one we started on. */
+ int one_file_system;
+
+ /* If nonzero, give the copies the original files' permissions,
+ ownership, and timestamps. */
+ int preserve;
+
+ /* If nonzero, copy directories recursively and copy special files
+ as themselves rather than copying their contents. */
+ int recursive;
+
+ /* Control creation of sparse files. */
+ enum Sparse_type sparse_mode;
+
+ /* If nonzero, create symbolic links instead of copying files.
+ Create destination directories as usual. */
+ int symbolic_link;
+
+ /* The bits to preserve in created files' modes. */
+ int umask_kill;
+
+ /* If nonzero, do not copy a nondirectory that has an existing destination
+ with the same or newer modification time. */
+ int update;
+
+ /* If nonzero, display the names of the files before copying them. */
+ int verbose;
+
+ /* This process's effective user ID. */
+ uid_t myeuid;
+
+ /* A pointer to either lstat or stat, depending on
+ whether dereferencing of symlinks is done. */
+ int (*xstat) ();
+};
+
+static int copy __P ((const char *src_path, const char *dst_path,
+ int new_dst, dev_t device,
+ struct dir_list *ancestors, struct flag *x));
+
+int full_write ();
+int euidaccess ();
+
+/* The invocation name of this program. */
+extern char *program_name;
+
+/* 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,
+ struct flag *x)
+{
+ 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, x);
+
+ /* 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;
+}
+
+/* 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.
+ FIXME: describe sparse_mode. */
+
+static int
+copy_reg (const char *src_path, const char *dst_path,
+ enum Sparse_type sparse_mode)
+{
+ 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 = (sparse_mode == 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 (sparse_mode == 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;
+}
+
+/* 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, struct flag *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 ((*(x->xstat)) (src_path, &src_sb))
+ {
+ error (0, errno, "%s", src_path);
+ return 1;
+ }
+
+ /* Are we crossing a file system boundary? */
+ if (x->one_file_system && device != 0 && device != src_sb.st_dev)
+ return 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. */
+
+ earlier_file = remember_copied (dst_path, src_sb.st_ino, src_sb.st_dev);
+
+ /* Did we just create this file? */
+
+ if (earlier_file == &new_file)
+ return 0;
+
+ src_mode = src_sb.st_mode;
+ src_type = src_sb.st_mode;
+
+ if (S_ISDIR (src_type) && !x->recursive)
+ {
+ error (0, 0, _("%s: omitting directory"), src_path);
+ return 1;
+ }
+
+ if (!new_dst)
+ {
+ if ((*(x->xstat)) (dst_path, &dst_sb))
+ {
+ if (errno != ENOENT)
+ {
+ error (0, errno, "%s", dst_path);
+ return 1;
+ }
+ else
+ new_dst = 1;
+ }
+ else
+ {
+ /* The file exists already. */
+
+ if (src_sb.st_ino == dst_sb.st_ino && src_sb.st_dev == dst_sb.st_dev)
+ {
+ if (x->hard_link)
+ return 0;
+
+ error (0, 0, _("`%s' and `%s' are the same file"),
+ src_path, dst_path);
+ return 1;
+ }
+
+ 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;
+ }
+
+ if (x->update && src_sb.st_mtime <= dst_sb.st_mtime)
+ return 0;
+ }
+
+ if (!S_ISDIR (src_type) && !x->force && x->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;
+ }
+
+ 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"));
+
+ /* 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;
+
+ }
+ 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 (x->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;
+ }
+ }
+ }
+ }
+
+ /* 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 (x->verbose && !S_ISDIR (src_type))
+ printf ("%s -> %s\n", src_path, dst_path);
+
+ /* Did we copy this inode somewhere else (in this command line argument)
+ and therefore this is a second hard link to the inode? */
+
+ if (!x->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;
+ }
+
+ if (S_ISDIR (src_type))
+ {
+ struct dir_list *dir;
+
+ /* 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. */
+
+ if (is_ancestor (&src_sb, ancestors))
+ {
+ error (0, 0, _("%s: cannot copy cyclic symbolic link"), src_path);
+ goto un_backup;
+ }
+
+ /* Insert the current directory in the list of parents. */
+
+ dir = (struct dir_list *) alloca (sizeof (struct dir_list));
+ dir->parent = ancestors;
+ dir->ino = src_sb.st_ino;
+ dir->dev = src_sb.st_dev;
+
+ if (new_dst || !S_ISDIR (dst_sb.st_mode))
+ {
+ /* Create the new directory writable and searchable, so
+ we can create new entries in it. */
+
+ if (mkdir (dst_path, (src_mode & x->umask_kill) | 0700))
+ {
+ error (0, errno, _("cannot create directory `%s'"), dst_path);
+ goto un_backup;
+ }
+
+ /* Insert the created directory's inode and device
+ numbers into the search structure, so that we can
+ avoid copying it again. */
+
+ if (remember_created (dst_path))
+ goto un_backup;
+
+ if (x->verbose)
+ printf ("%s -> %s\n", src_path, dst_path);
+ }
+
+ /* Copy the contents of the directory. */
+
+ if (copy_dir (src_path, dst_path, new_dst, &src_sb, dir, x))
+ return 1;
+ }
+#ifdef S_ISLNK
+ else if (x->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 (x->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)
+ || (x->copy_as_regular && !S_ISDIR (src_type)
+#ifdef S_ISLNK
+ && !S_ISLNK (src_type)
+#endif
+ ))
+ {
+ if (copy_reg (src_path, dst_path, x->sparse_mode))
+ goto un_backup;
+ }
+ else
+#ifdef S_ISFIFO
+ if (S_ISFIFO (src_type))
+ {
+ if (mkfifo (dst_path, src_mode & x->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 (mknod (dst_path, src_mode & x->umask_kill, src_sb.st_rdev))
+ {
+ error (0, errno, _("cannot create special file `%s'"), dst_path);
+ goto un_backup;
+ }
+ }
+ 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 (x->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 (x->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
+ }
+
+ return 0;
+ }
+ else
+#endif
+ {
+ error (0, 0, _("%s: unknown file type"), src_path);
+ goto un_backup;
+ }
+
+ /* 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 (x->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 (DO_CHOWN (chown, dst_path, src_sb.st_uid, src_sb.st_gid))
+ {
+ error (0, errno, "%s", dst_path);
+ return 1;
+ }
+ }
+
+ if ((x->preserve || new_dst)
+ && (x->copy_as_regular || S_ISREG (src_type) || S_ISDIR (src_type)))
+ {
+ if (chmod (dst_path, src_mode & x->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;
+ }
+ }
+
+ return 0;
+
+un_backup:
+ if (dst_backup)
+ {
+ if (rename (dst_backup, dst_path))
+ error (0, errno, _("cannot un-backup `%s'"), dst_path);
+ }
+ return 1;
+}