diff options
Diffstat (limited to 'src/copy.c')
-rw-r--r-- | src/copy.c | 2007 |
1 files changed, 2007 insertions, 0 deletions
diff --git a/src/copy.c b/src/copy.c new file mode 100644 index 0000000..4bdb75c --- /dev/null +++ b/src/copy.c @@ -0,0 +1,2007 @@ +/* copy.c -- core functions for copying files and directories + Copyright (C) 89, 90, 91, 1995-2007 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 + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Extracted from cp.c and librarified by Jim Meyering. */ + +#include <config.h> +#include <stdio.h> +#include <assert.h> +#include <sys/types.h> + +#if HAVE_HURD_H +# include <hurd.h> +#endif +#if HAVE_PRIV_H +# include <priv.h> +#endif + +#include "system.h" +#include "acl.h" +#include "backupfile.h" +#include "buffer-lcm.h" +#include "copy.h" +#include "cp-hash.h" +#include "euidaccess.h" +#include "error.h" +#include "fcntl--.h" +#include "filemode.h" +#include "filenamecat.h" +#include "full-write.h" +#include "getpagesize.h" +#include "hash.h" +#include "hash-pjw.h" +#include "lchmod.h" +#include "quote.h" +#include "same.h" +#include "savedir.h" +#include "stat-time.h" +#include "utimecmp.h" +#include "utimens.h" +#include "xreadlink.h" +#include "yesno.h" + +#ifndef HAVE_FCHOWN +# define HAVE_FCHOWN false +# define fchown(fd, uid, gid) (-1) +#endif + +#define SAME_OWNER(A, B) ((A).st_uid == (B).st_uid) +#define SAME_GROUP(A, B) ((A).st_gid == (B).st_gid) +#define SAME_OWNER_AND_GROUP(A, B) (SAME_OWNER (A, B) && SAME_GROUP (A, B)) + +#define UNWRITABLE(File_name, File_mode) \ + ( /* euidaccess is not meaningful for symlinks */ \ + ! S_ISLNK (File_mode) \ + && euidaccess (File_name, W_OK) != 0) + +struct dir_list +{ + struct dir_list *parent; + ino_t ino; + dev_t dev; +}; + +/* Describe a just-created or just-renamed destination file. */ +struct F_triple +{ + char *name; + ino_t st_ino; + dev_t st_dev; +}; + +/* Initial size of the above hash table. */ +#define DEST_INFO_INITIAL_CAPACITY 61 + +static bool copy_internal (char const *src_name, char const *dst_name, + bool new_dst, dev_t device, + struct dir_list *ancestors, + const struct cp_options *x, + bool command_line_arg, + bool *copy_into_self, + bool *rename_succeeded); + +/* Pointers to the file names: they're used in the diagnostic that is issued + when we detect the user is trying to copy a directory into itself. */ +static char const *top_level_src_name; +static char const *top_level_dst_name; + +/* The invocation name of this program. */ +extern char *program_name; + +/* FIXME: describe */ +/* FIXME: rewrite this to use a hash table so we avoid the quadratic + performance hit that's probably noticeable only on trees deeper + than a few hundred levels. See use of active_dir_map in remove.c */ + +static bool +is_ancestor (const struct stat *sb, const struct dir_list *ancestors) +{ + while (ancestors != 0) + { + if (ancestors->ino == sb->st_ino && ancestors->dev == sb->st_dev) + return true; + ancestors = ancestors->parent; + } + return false; +} + +/* Read the contents of the directory SRC_NAME_IN, and recursively + copy the contents to DST_NAME_IN. NEW_DST is true if + DST_NAME_IN is a directory that was created previously in the + recursion. SRC_SB and ANCESTORS describe SRC_NAME_IN. + Set *COPY_INTO_SELF if SRC_NAME_IN is a parent of + (or the same as) DST_NAME_IN; otherwise, clear it. + Return true if successful. */ + +static bool +copy_dir (char const *src_name_in, char const *dst_name_in, bool new_dst, + const struct stat *src_sb, struct dir_list *ancestors, + const struct cp_options *x, bool *copy_into_self) +{ + char *name_space; + char *namep; + struct cp_options non_command_line_options = *x; + bool ok = true; + + name_space = savedir (src_name_in); + if (name_space == NULL) + { + /* This diagnostic is a bit vague because savedir can fail in + several different ways. */ + error (0, errno, _("cannot access %s"), quote (src_name_in)); + return false; + } + + /* For cp's -H option, dereference command line arguments, but do not + dereference symlinks that are found via recursive traversal. */ + if (x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) + non_command_line_options.dereference = DEREF_NEVER; + + namep = name_space; + while (*namep != '\0') + { + bool local_copy_into_self; + char *src_name = file_name_concat (src_name_in, namep, NULL); + char *dst_name = file_name_concat (dst_name_in, namep, NULL); + + ok &= copy_internal (src_name, dst_name, new_dst, src_sb->st_dev, + ancestors, &non_command_line_options, false, + &local_copy_into_self, NULL); + *copy_into_self |= local_copy_into_self; + + free (dst_name); + free (src_name); + + namep += strlen (namep) + 1; + } + free (name_space); + return ok; +} + +/* Set the owner and owning group of DEST_DESC to the st_uid and + st_gid fields of SRC_SB. If DEST_DESC is undefined (-1), set + the owner and owning group of DST_NAME instead. DEST_DESC must + refer to the same file as DEST_NAME if defined. + Return 1 if the syscall succeeds, 0 if it fails but it's OK + not to preserve ownership, -1 otherwise. */ + +static int +set_owner (const struct cp_options *x, char const *dst_name, int dest_desc, + uid_t uid, gid_t gid) +{ + if (HAVE_FCHOWN && dest_desc != -1) + { + if (fchown (dest_desc, uid, gid) == 0) + return 1; + } + else + { + if (chown (dst_name, uid, gid) == 0) + return 1; + } + + if (! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + quote (dst_name)); + if (x->require_preserve) + return -1; + } + + return 0; +} + +/* Set the st_author field of DEST_DESC to the st_author field of + SRC_SB. If DEST_DESC is undefined (-1), set the st_author field + of DST_NAME instead. DEST_DESC must refer to the same file as + DEST_NAME if defined. */ + +static void +set_author (const char *dst_name, int dest_desc, const struct stat *src_sb) +{ +#if HAVE_STRUCT_STAT_ST_AUTHOR + /* Preserve the st_author field. */ + file_t file = (dest_desc < 0 + ? file_name_lookup (dst_name, 0, 0) + : getdport (dest_desc)); + if (file == MACH_PORT_NULL) + error (0, errno, _("failed to lookup file %s"), quote (dst_name)); + else + { + error_t err = file_chauthor (file, src_sb->st_author); + if (err) + error (0, err, _("failed to preserve authorship for %s"), + quote (dst_name)); + mach_port_deallocate (mach_task_self (), file); + } +#endif +} + +/* Change the file mode bits of the file identified by DESC or NAME to MODE. + Use DESC if DESC is valid and fchmod is available, NAME otherwise. */ + +static int +fchmod_or_lchmod (int desc, char const *name, mode_t mode) +{ +#if HAVE_FCHMOD + if (0 <= desc) + return fchmod (desc, mode); +#endif + return lchmod (name, mode); +} + +/* Copy a regular file from SRC_NAME to DST_NAME. + 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.) + When creating the destination, use DST_MODE & ~OMITTED_PERMISSIONS + as the third argument in the call to open, adding + OMITTED_PERMISSIONS after copying as needed. + X provides many option settings. + Return true if successful. + *NEW_DST is as in copy_internal. + SRC_SB is the result of calling XSTAT (aka stat) on SRC_NAME. */ + +static bool +copy_reg (char const *src_name, char const *dst_name, + const struct cp_options *x, + mode_t dst_mode, mode_t omitted_permissions, bool *new_dst, + struct stat const *src_sb) +{ + char *buf; + char *buf_alloc = NULL; + int dest_desc; + int source_desc; + mode_t src_mode = src_sb->st_mode; + struct stat sb; + struct stat src_open_sb; + bool return_val = true; + + source_desc = open (src_name, O_RDONLY | O_BINARY); + if (source_desc < 0) + { + error (0, errno, _("cannot open %s for reading"), quote (src_name)); + return false; + } + + if (fstat (source_desc, &src_open_sb) != 0) + { + error (0, errno, _("cannot fstat %s"), quote (src_name)); + return_val = false; + goto close_src_desc; + } + + /* Compare the source dev/ino from the open file to the incoming, + saved ones obtained via a previous call to stat. */ + if (! SAME_INODE (*src_sb, src_open_sb)) + { + error (0, 0, + _("skipping file %s, as it was replaced while being copied"), + quote (src_name)); + return_val = false; + goto close_src_desc; + } + + /* The semantics of the following open calls are mandated + by the specs for both cp and mv. */ + if (! *new_dst) + { + dest_desc = open (dst_name, O_WRONLY | O_TRUNC | O_BINARY); + + if (dest_desc < 0 && x->unlink_dest_after_failed_open) + { + if (unlink (dst_name) != 0) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + return_val = false; + goto close_src_desc; + } + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + + /* Tell caller that the destination file was unlinked. */ + *new_dst = true; + } + } + + if (*new_dst) + dest_desc = open (dst_name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, + dst_mode & ~omitted_permissions); + else + omitted_permissions = 0; + + if (dest_desc < 0) + { + error (0, errno, _("cannot create regular file %s"), quote (dst_name)); + return_val = false; + goto close_src_desc; + } + + if (fstat (dest_desc, &sb) != 0) + { + error (0, errno, _("cannot fstat %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + + if (! (S_ISREG (src_open_sb.st_mode) && src_open_sb.st_size == 0)) + { + typedef uintptr_t word; + off_t n_read_total = 0; + + /* Choose a suitable buffer size; it may be adjusted later. */ + size_t buf_alignment = lcm (getpagesize (), sizeof (word)); + size_t buf_alignment_slop = sizeof (word) + buf_alignment - 1; + size_t buf_size = ST_BLKSIZE (sb); + + /* Deal with sparse files. */ + bool last_write_made_hole = false; + bool make_holes = false; + + if (S_ISREG (sb.st_mode)) + { + /* Even with --sparse=always, try to create holes only + if the destination is a regular file. */ + if (x->sparse_mode == SPARSE_ALWAYS) + make_holes = true; + +#if HAVE_STRUCT_STAT_ST_BLOCKS + /* Use a heuristic to determine whether SRC_NAME contains any sparse + blocks. 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 (x->sparse_mode == SPARSE_AUTO && S_ISREG (src_open_sb.st_mode) + && ST_NBLOCKS (src_open_sb) < src_open_sb.st_size / ST_NBLOCKSIZE) + make_holes = true; +#endif + } + + /* If not making a sparse file, try to use a more-efficient + buffer size. */ + if (! make_holes) + { + /* These days there's no point ever messing with buffers smaller + than 8 KiB. It would be nice to configure SMALL_BUF_SIZE + dynamically for this host and pair of files, but there doesn't + seem to be a good way to get readahead info portably. */ + enum { SMALL_BUF_SIZE = 8 * 1024 }; + + /* Compute the least common multiple of the input and output + buffer sizes, adjusting for outlandish values. */ + size_t blcm_max = MIN (SIZE_MAX, SSIZE_MAX) - buf_alignment_slop; + size_t blcm = buffer_lcm (ST_BLKSIZE (src_open_sb), buf_size, + blcm_max); + + /* Do not use a block size that is too small. */ + buf_size = MAX (SMALL_BUF_SIZE, blcm); + + /* Do not bother with a buffer larger than the input file, plus one + byte to make sure the file has not grown while reading it. */ + if (S_ISREG (src_open_sb.st_mode) && src_open_sb.st_size < buf_size) + buf_size = src_open_sb.st_size + 1; + + /* However, stick with a block size that is a positive multiple of + blcm, overriding the above adjustments. Watch out for + overflow. */ + buf_size += blcm - 1; + buf_size -= buf_size % blcm; + if (buf_size == 0 || blcm_max < buf_size) + buf_size = blcm; + } + + /* Make a buffer with space for a sentinel at the end. */ + buf_alloc = xmalloc (buf_size + buf_alignment_slop); + buf = ptr_align (buf_alloc, buf_alignment); + + for (;;) + { + word *wp = NULL; + + ssize_t n_read = read (source_desc, buf, buf_size); + if (n_read < 0) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + error (0, errno, _("reading %s"), quote (src_name)); + return_val = false; + goto close_src_and_dst_desc; + } + if (n_read == 0) + break; + + n_read_total += n_read; + + if (make_holes) + { + char *cp; + + /* Sentinel to stop loop. */ + buf[n_read] = '\1'; +#ifdef lint + /* Usually, buf[n_read] is not the byte just before a "word" + (aka uintptr_t) boundary. In that case, the word-oriented + test below (*wp++ == 0) would read some uninitialized bytes + after the sentinel. To avoid false-positive reports about + this condition (e.g., from a tool like valgrind), set the + remaining bytes -- to any value. */ + memset (buf + n_read + 1, 0, sizeof (word) - 1); +#endif + + /* Find first nonzero *word*, or the word with the sentinel. */ + + wp = (word *) buf; + while (*wp++ == 0) + continue; + + /* Find the first nonzero *byte*, or the sentinel. */ + + cp = (char *) (wp - 1); + while (*cp++ == 0) + continue; + + if (cp <= buf + n_read) + /* Clear to indicate that a normal write is needed. */ + wp = NULL; + else + { + /* We found the sentinel, so the whole input block was zero. + Make a hole. */ + if (lseek (dest_desc, n_read, SEEK_CUR) < 0) + { + error (0, errno, _("cannot lseek %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + last_write_made_hole = true; + } + } + + if (!wp) + { + size_t n = n_read; + if (full_write (dest_desc, buf, n) != n) + { + error (0, errno, _("writing %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + last_write_made_hole = false; + + /* A short read on a regular file means EOF. */ + if (n_read != buf_size && S_ISREG (src_open_sb.st_mode)) + break; + } + } + + /* If the file ends with a `hole', we need to do something to record + the length of the file. On modern systems, calling ftruncate does + the job. On systems without native ftruncate support, we have to + write a byte at the ending position. Otherwise the kernel would + truncate the file at the end of the last write operation. */ + + if (last_write_made_hole) + { + if (HAVE_FTRUNCATE + ? /* ftruncate sets the file size, + so there is no need for a write. */ + ftruncate (dest_desc, n_read_total) < 0 + : /* Seek backwards one character and write a null. */ + (lseek (dest_desc, (off_t) -1, SEEK_CUR) < 0L + || full_write (dest_desc, "", 1) != 1)) + { + error (0, errno, _("writing %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + } + } + + if (x->preserve_timestamps) + { + struct timespec timespec[2]; + timespec[0] = get_stat_atime (src_sb); + timespec[1] = get_stat_mtime (src_sb); + + if (futimens (dest_desc, dst_name, timespec) != 0) + { + error (0, errno, _("preserving times for %s"), quote (dst_name)); + if (x->require_preserve) + { + return_val = false; + goto close_src_and_dst_desc; + } + } + } + + if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb)) + { + switch (set_owner (x, dst_name, dest_desc, + src_sb->st_uid, src_sb->st_gid)) + { + case -1: + return_val = false; + goto close_src_and_dst_desc; + + case 0: + src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX); + break; + } + } + + set_author (dst_name, dest_desc, src_sb); + + if (x->preserve_mode || x->move_mode) + { + if (copy_acl (src_name, source_desc, dst_name, dest_desc, src_mode) != 0 + && x->require_preserve) + return_val = false; + } + else if (x->set_mode) + { + if (set_acl (dst_name, dest_desc, x->mode) != 0) + return_val = false; + } + else if (omitted_permissions) + { + omitted_permissions &= ~ cached_umask (); + if (omitted_permissions + && fchmod_or_lchmod (dest_desc, dst_name, dst_mode) != 0) + { + error (0, errno, _("preserving permissions for %s"), + quote (dst_name)); + if (x->require_preserve) + return_val = false; + } + } + +close_src_and_dst_desc: + if (close (dest_desc) < 0) + { + error (0, errno, _("closing %s"), quote (dst_name)); + return_val = false; + } +close_src_desc: + if (close (source_desc) < 0) + { + error (0, errno, _("closing %s"), quote (src_name)); + return_val = false; + } + + free (buf_alloc); + return return_val; +} + +/* Return true if it's ok that the source and destination + files are the `same' by some measure. The goal is to avoid + making the `copy' operation remove both copies of the file + in that case, while still allowing the user to e.g., move or + copy a regular file onto a symlink that points to it. + Try to minimize the cost of this function in the common case. + Set *RETURN_NOW if we've determined that the caller has no more + work to do and should return successfully, right away. + + Set *UNLINK_SRC if we've determined that the caller wants to do + `rename (a, b)' where `a' and `b' are distinct hard links to the same + file. In that case, the caller should try to unlink `a' and then return + successfully. Ideally, we wouldn't have to do that, and we'd be + able to rely on rename to remove the source file. However, POSIX + mistakenly requires that such a rename call do *nothing* and return + successfully. */ + +static bool +same_file_ok (char const *src_name, struct stat const *src_sb, + char const *dst_name, struct stat const *dst_sb, + const struct cp_options *x, bool *return_now, bool *unlink_src) +{ + const struct stat *src_sb_link; + const struct stat *dst_sb_link; + struct stat tmp_dst_sb; + struct stat tmp_src_sb; + + bool same_link; + bool same = SAME_INODE (*src_sb, *dst_sb); + + *return_now = false; + *unlink_src = false; + + /* FIXME: this should (at the very least) be moved into the following + if-block. More likely, it should be removed, because it inhibits + making backups. But removing it will result in a change in behavior + that will probably have to be documented -- and tests will have to + be updated. */ + if (same && x->hard_link) + { + *return_now = true; + return true; + } + + if (x->dereference == DEREF_NEVER) + { + same_link = same; + + /* If both the source and destination files are symlinks (and we'll + know this here IFF preserving symlinks), then it's ok -- as long + as they are distinct. */ + if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode)) + return ! same_name (src_name, dst_name); + + src_sb_link = src_sb; + dst_sb_link = dst_sb; + } + else + { + if (!same) + return true; + + if (lstat (dst_name, &tmp_dst_sb) != 0 + || lstat (src_name, &tmp_src_sb) != 0) + return true; + + src_sb_link = &tmp_src_sb; + dst_sb_link = &tmp_dst_sb; + + same_link = SAME_INODE (*src_sb_link, *dst_sb_link); + + /* If both are symlinks, then it's ok, but only if the destination + will be unlinked before being opened. This is like the test + above, but with the addition of the unlink_dest_before_opening + conjunct because otherwise, with two symlinks to the same target, + we'd end up truncating the source file. */ + if (S_ISLNK (src_sb_link->st_mode) && S_ISLNK (dst_sb_link->st_mode) + && x->unlink_dest_before_opening) + return true; + } + + /* The backup code ensures there's a copy, so it's usually ok to + remove any destination file. One exception is when both + source and destination are the same directory entry. In that + case, moving the destination file aside (in making the backup) + would also rename the source file and result in an error. */ + if (x->backup_type != no_backups) + { + if (!same_link) + { + /* In copy mode when dereferencing symlinks, if the source is a + symlink and the dest is not, then backing up the destination + (moving it aside) would make it a dangling symlink, and the + subsequent attempt to open it in copy_reg would fail with + a misleading diagnostic. Avoid that by returning zero in + that case so the caller can make cp (or mv when it has to + resort to reading the source file) fail now. */ + + /* FIXME-note: even with the following kludge, we can still provoke + the offending diagnostic. It's just a little harder to do :-) + $ rm -f a b c; touch c; ln -s c b; ln -s b a; cp -b a b + cp: cannot open `a' for reading: No such file or directory + That's misleading, since a subsequent `ls' shows that `a' + is still there. + One solution would be to open the source file *before* moving + aside the destination, but that'd involve a big rewrite. */ + if ( ! x->move_mode + && x->dereference != DEREF_NEVER + && S_ISLNK (src_sb_link->st_mode) + && ! S_ISLNK (dst_sb_link->st_mode)) + return false; + + return true; + } + + return ! same_name (src_name, dst_name); + } + +#if 0 + /* FIXME: use or remove */ + + /* If we're making a backup, we'll detect the problem case in + copy_reg because SRC_NAME will no longer exist. Allowing + the test to be deferred lets cp do some useful things. + But when creating hardlinks and SRC_NAME is a symlink + but DST_NAME is not we must test anyway. */ + if (x->hard_link + || !S_ISLNK (src_sb_link->st_mode) + || S_ISLNK (dst_sb_link->st_mode)) + return true; + + if (x->dereference != DEREF_NEVER) + return true; +#endif + + /* They may refer to the same file if we're in move mode and the + target is a symlink. That is ok, since we remove any existing + destination file before opening it -- via `rename' if they're on + the same file system, via `unlink (DST_NAME)' otherwise. + It's also ok if they're distinct hard links to the same file. */ + if (x->move_mode || x->unlink_dest_before_opening) + { + if (S_ISLNK (dst_sb_link->st_mode)) + return true; + + if (same_link + && 1 < dst_sb_link->st_nlink + && ! same_name (src_name, dst_name)) + { + if (x->move_mode) + { + *unlink_src = true; + *return_now = true; + } + return true; + } + } + + /* If neither is a symlink, then it's ok as long as they aren't + hard links to the same file. */ + if (!S_ISLNK (src_sb_link->st_mode) && !S_ISLNK (dst_sb_link->st_mode)) + { + if (!SAME_INODE (*src_sb_link, *dst_sb_link)) + return true; + + /* If they are the same file, it's ok if we're making hard links. */ + if (x->hard_link) + { + *return_now = true; + return true; + } + } + + /* It's ok to remove a destination symlink. But that works only when we + unlink before opening the destination and when the source and destination + files are on the same partition. */ + if (x->unlink_dest_before_opening + && S_ISLNK (dst_sb_link->st_mode)) + return dst_sb_link->st_dev == src_sb_link->st_dev; + + if (x->dereference == DEREF_NEVER) + { + if ( ! S_ISLNK (src_sb_link->st_mode)) + tmp_src_sb = *src_sb_link; + else if (stat (src_name, &tmp_src_sb) != 0) + return true; + + if ( ! S_ISLNK (dst_sb_link->st_mode)) + tmp_dst_sb = *dst_sb_link; + else if (stat (dst_name, &tmp_dst_sb) != 0) + return true; + + if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb)) + return true; + + /* FIXME: shouldn't this be testing whether we're making symlinks? */ + if (x->hard_link) + { + *return_now = true; + return true; + } + } + + return false; +} + +static void +overwrite_prompt (char const *dst_name, struct stat const *dst_sb) +{ + if (euidaccess (dst_name, W_OK) != 0) + { + char perms[12]; /* "-rwxrwxrwx " ls-style modes. */ + strmode (dst_sb->st_mode, perms); + perms[10] = '\0'; + fprintf (stderr, + _("%s: try to overwrite %s, overriding mode %04lo (%s)? "), + program_name, quote (dst_name), + (unsigned long int) (dst_sb->st_mode & CHMOD_MODE_BITS), + &perms[1]); + } + else + { + fprintf (stderr, _("%s: overwrite %s? "), + program_name, quote (dst_name)); + } +} + +/* Hash an F_triple. */ +static size_t +triple_hash (void const *x, size_t table_size) +{ + struct F_triple const *p = x; + + /* Also take the name into account, so that when moving N hard links to the + same file (all listed on the command line) all into the same directory, + we don't experience any N^2 behavior. */ + /* FIXME-maybe: is it worth the overhead of doing this + just to avoid N^2 in such an unusual case? N would have + to be very large to make the N^2 factor noticable, and + one would probably encounter a limit on the length of + a command line before it became a problem. */ + size_t tmp = hash_pjw (p->name, table_size); + + /* Ignoring the device number here should be fine. */ + return (tmp | p->st_ino) % table_size; +} + +/* Hash an F_triple. */ +static size_t +triple_hash_no_name (void const *x, size_t table_size) +{ + struct F_triple const *p = x; + + /* Ignoring the device number here should be fine. */ + return p->st_ino % table_size; +} + +/* Compare two F_triple structs. */ +static bool +triple_compare (void const *x, void const *y) +{ + struct F_triple const *a = x; + struct F_triple const *b = y; + return (SAME_INODE (*a, *b) && same_name (a->name, b->name)) ? true : false; +} + +/* Free an F_triple. */ +static void +triple_free (void *x) +{ + struct F_triple *a = x; + free (a->name); + free (a); +} + +/* Initialize the hash table implementing a set of F_triple entries + corresponding to destination files. */ +extern void +dest_info_init (struct cp_options *x) +{ + x->dest_info + = hash_initialize (DEST_INFO_INITIAL_CAPACITY, + NULL, + triple_hash, + triple_compare, + triple_free); +} + +/* Initialize the hash table implementing a set of F_triple entries + corresponding to source files listed on the command line. */ +extern void +src_info_init (struct cp_options *x) +{ + + /* Note that we use triple_hash_no_name here. + Contrast with the use of triple_hash above. + That is necessary because a source file may be specified + in many different ways. We want to warn about this + cp a a d/ + as well as this: + cp a ./a d/ + */ + x->src_info + = hash_initialize (DEST_INFO_INITIAL_CAPACITY, + NULL, + triple_hash_no_name, + triple_compare, + triple_free); +} + +/* Return true if there is an entry in hash table, HT, + for the file described by FILE and STATS. */ +static bool +seen_file (Hash_table const *ht, char const *file, + struct stat const *stats) +{ + struct F_triple new_ent; + + if (ht == NULL) + return false; + + new_ent.name = (char *) file; + new_ent.st_ino = stats->st_ino; + new_ent.st_dev = stats->st_dev; + + return !!hash_lookup (ht, &new_ent); +} + +/* Record destination file, FILE, and dev/ino from *STATS, + in the hash table, HT. If HT is NULL, return immediately. + If STATS is NULL, call lstat on FILE to get the device + and inode numbers. If that lstat fails, simply return. + If memory allocation fails, exit immediately. */ +static void +record_file (Hash_table *ht, char const *file, + struct stat const *stats) +{ + struct F_triple *ent; + + if (ht == NULL) + return; + + ent = xmalloc (sizeof *ent); + ent->name = xstrdup (file); + if (stats) + { + ent->st_ino = stats->st_ino; + ent->st_dev = stats->st_dev; + } + else + { + struct stat sb; + if (lstat (file, &sb) != 0) + return; + ent->st_ino = sb.st_ino; + ent->st_dev = sb.st_dev; + } + + { + struct F_triple *ent_from_table = hash_insert (ht, ent); + if (ent_from_table == NULL) + { + /* Insertion failed due to lack of memory. */ + xalloc_die (); + } + + if (ent_from_table != ent) + { + /* There was alread a matching entry in the table, so ENT was + not inserted. Free it. */ + triple_free (ent); + } + } +} + +/* When effecting a move (e.g., for mv(1)), and given the name DST_NAME + of the destination and a corresponding stat buffer, DST_SB, return + true if the logical `move' operation should _not_ proceed. + Otherwise, return false. + Depending on options specified in X, this code may issue an + interactive prompt asking whether it's ok to overwrite DST_NAME. */ +static bool +abandon_move (const struct cp_options *x, + char const *dst_name, + struct stat const *dst_sb) +{ + assert (x->move_mode); + return (x->interactive == I_ALWAYS_NO + || ((x->interactive == I_ASK_USER + || (x->interactive == I_UNSPECIFIED + && x->stdin_tty + && UNWRITABLE (dst_name, dst_sb->st_mode))) + && (overwrite_prompt (dst_name, dst_sb), 1) + && ! yesno ())); +} + +/* Print --verbose output on standard output, e.g. `new' -> `old'. + If BACKUP_DST_NAME is non-NULL, then also indicate that it is + the name of a backup file. */ +static void +emit_verbose (char const *src, char const *dst, char const *backup_dst_name) +{ + printf ("%s -> %s", quote_n (0, src), quote_n (1, dst)); + if (backup_dst_name) + printf (_(" (backup: %s)"), quote (backup_dst_name)); + putchar ('\n'); +} + +/* Copy the file SRC_NAME to the file DST_NAME. The files may be of + any type. NEW_DST should be true if the file DST_NAME cannot + exist because its parent directory was just created; NEW_DST should + be false if DST_NAME 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_NAME. COMMAND_LINE_ARG + is true iff SRC_NAME was specified on the command line. + Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the + same as) DST_NAME; otherwise, clear it. + Return true if successful. */ +static bool +copy_internal (char const *src_name, char const *dst_name, + bool new_dst, + dev_t device, + struct dir_list *ancestors, + const struct cp_options *x, + bool command_line_arg, + bool *copy_into_self, + bool *rename_succeeded) +{ + struct stat src_sb; + struct stat dst_sb; + mode_t src_mode; + mode_t dst_mode IF_LINT (= 0); + mode_t dst_mode_bits; + mode_t omitted_permissions; + bool restore_dst_mode = false; + char *earlier_file = NULL; + char *dst_backup = NULL; + bool backup_succeeded = false; + bool delayed_ok; + bool copied_as_regular = false; + bool preserve_metadata; + + if (x->move_mode && rename_succeeded) + *rename_succeeded = false; + + *copy_into_self = false; + + if (XSTAT (x, src_name, &src_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (src_name)); + return false; + } + + src_mode = src_sb.st_mode; + + if (S_ISDIR (src_mode) && !x->recursive) + { + error (0, 0, _("omitting directory %s"), quote (src_name)); + return false; + } + + /* Detect the case in which the same source file appears more than + once on the command line and no backup option has been selected. + If so, simply warn and don't copy it the second time. + This check is enabled only if x->src_info is non-NULL. */ + if (command_line_arg) + { + if ( ! S_ISDIR (src_sb.st_mode) + && x->backup_type == no_backups + && seen_file (x->src_info, src_name, &src_sb)) + { + error (0, 0, _("warning: source file %s specified more than once"), + quote (src_name)); + return true; + } + + record_file (x->src_info, src_name, &src_sb); + } + + if (!new_dst) + { + if (XSTAT (x, dst_name, &dst_sb) != 0) + { + if (errno != ENOENT) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + return false; + } + else + { + new_dst = true; + } + } + else + { /* Here, we know that dst_name exists, at least to the point + that it is XSTAT'able. */ + bool return_now; + bool unlink_src; + + if (! same_file_ok (src_name, &src_sb, dst_name, &dst_sb, + x, &return_now, &unlink_src)) + { + error (0, 0, _("%s and %s are the same file"), + quote_n (0, src_name), quote_n (1, dst_name)); + return false; + } + + /* When there is an existing destination file, we may end up + returning early, and hence not copying/moving the file. + This may be due to an interactive `negative' reply to the + prompt about the existing file. It may also be due to the + use of the --reply=no option. + + cp and mv treat -i and -f differently. */ + if (x->move_mode) + { + if (abandon_move (x, dst_name, &dst_sb) + || (unlink_src && unlink (src_name) == 0)) + { + /* Pretend the rename succeeded, so the caller (mv) + doesn't end up removing the source file. */ + if (rename_succeeded) + *rename_succeeded = true; + if (unlink_src && x->verbose) + printf (_("removed %s\n"), quote (src_name)); + return true; + } + if (unlink_src) + { + error (0, errno, _("cannot remove %s"), quote (src_name)); + return false; + } + } + else + { + if (! S_ISDIR (src_mode) + && (x->interactive == I_ALWAYS_NO + || (x->interactive == I_ASK_USER + && (overwrite_prompt (dst_name, &dst_sb), 1) + && ! yesno ()))) + return true; + } + + if (return_now) + return true; + + if (!S_ISDIR (dst_sb.st_mode)) + { + if (S_ISDIR (src_mode)) + { + if (x->move_mode && x->backup_type != no_backups) + { + /* Moving a directory onto an existing + non-directory is ok only with --backup. */ + } + else + { + error (0, 0, + _("cannot overwrite non-directory %s with directory %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + return false; + } + } + + /* Don't let the user destroy their data, even if they try hard: + This mv command must fail (likewise for cp): + rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c + Otherwise, the contents of b/f would be lost. + In the case of `cp', b/f would be lost if the user simulated + a move using cp and rm. + Note that it works fine if you use --backup=numbered. */ + if (command_line_arg + && x->backup_type != numbered_backups + && seen_file (x->dest_info, dst_name, &dst_sb)) + { + error (0, 0, + _("will not overwrite just-created %s with %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + return false; + } + } + + if (!S_ISDIR (src_mode)) + { + if (S_ISDIR (dst_sb.st_mode)) + { + if (x->move_mode && x->backup_type != no_backups) + { + /* Moving a non-directory onto an existing + directory is ok only with --backup. */ + } + else + { + error (0, 0, + _("cannot overwrite directory %s with non-directory"), + quote (dst_name)); + return false; + } + } + + if (x->update) + { + /* When preserving time stamps (but not moving within a file + system), don't worry if the destination time stamp is + less than the source merely because of time stamp + truncation. */ + int options = ((x->preserve_timestamps + && ! (x->move_mode + && dst_sb.st_dev == src_sb.st_dev)) + ? UTIMECMP_TRUNCATE_SOURCE + : 0); + + if (0 <= utimecmp (dst_name, &dst_sb, &src_sb, options)) + { + /* We're using --update and the destination is not older + than the source, so do not copy or move. Pretend the + rename succeeded, so the caller (if it's mv) doesn't + end up removing the source file. */ + if (rename_succeeded) + *rename_succeeded = true; + return true; + } + } + } + + if (x->move_mode) + { + /* Don't allow user to move a directory onto a non-directory. */ + if (S_ISDIR (src_sb.st_mode) && !S_ISDIR (dst_sb.st_mode) + && x->backup_type == no_backups) + { + error (0, 0, + _("cannot move directory onto non-directory: %s -> %s"), + quote_n (0, src_name), quote_n (0, dst_name)); + return false; + } + } + + if (x->backup_type != no_backups + /* Don't try to back up a destination if the last + component of src_name is "." or "..". */ + && ! dot_or_dotdot (last_component (src_name)) + /* Create a backup of each destination directory in move mode, + but not in copy mode. FIXME: it might make sense to add an + option to suppress backup creation also for move mode. + That would let one use mv to merge new content into an + existing hierarchy. */ + && (x->move_mode || ! S_ISDIR (dst_sb.st_mode))) + { + char *tmp_backup = find_backup_file_name (dst_name, + x->backup_type); + + /* 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=simple a~ a + would leave two zero-length files: a and a~. */ + /* FIXME: but simply change e.g., the final a~ to `./a~' + and the source will still be destroyed. */ + if (STREQ (tmp_backup, src_name)) + { + const char *fmt; + fmt = (x->move_mode + ? _("backing up %s would destroy source; %s not moved") + : _("backing up %s would destroy source; %s not copied")); + error (0, 0, fmt, + quote_n (0, dst_name), + quote_n (1, src_name)); + free (tmp_backup); + return false; + } + + /* FIXME: use fts: + Using alloca for a file name that may be arbitrarily + long is not recommended. In fact, even forming such a name + should be discouraged. Eventually, this code will be rewritten + to use fts, so using alloca here will be less of a problem. */ + ASSIGN_STRDUPA (dst_backup, tmp_backup); + free (tmp_backup); + if (rename (dst_name, dst_backup) != 0) + { + if (errno != ENOENT) + { + error (0, errno, _("cannot backup %s"), quote (dst_name)); + return false; + } + else + { + dst_backup = NULL; + } + } + else + { + backup_succeeded = true; + } + new_dst = true; + } + else if (! S_ISDIR (dst_sb.st_mode) + && (x->unlink_dest_before_opening + || (x->preserve_links && 1 < dst_sb.st_nlink) + || (!x->move_mode + && x->dereference == DEREF_NEVER + && S_ISLNK (src_sb.st_mode)) + )) + { + if (unlink (dst_name) != 0 && errno != ENOENT) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + return false; + } + new_dst = true; + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + } + } + } + + /* 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_mode)) + emit_verbose (src_name, dst_name, backup_succeeded ? dst_backup : NULL); + + /* Associate the destination file name with the source device and inode + so that if we encounter a matching dev/ino pair in the source tree + we can arrange to create a hard link between the corresponding names + in the destination tree. + + Sometimes, when preserving links, we have to record dev/ino even + though st_nlink == 1: + - when in move_mode, since we may be moving a group of N hard-linked + files (via two or more command line arguments) to a different + partition; the links may be distributed among the command line + arguments (possibly hierarchies) so that the link count of + the final, once-linked source file is reduced to 1 when it is + considered below. But in this case (for mv) we don't need to + incur the expense of recording the dev/ino => name mapping; all we + really need is a lookup, to see if the dev/ino pair has already + been copied. + - when using -H and processing a command line argument; + that command line argument could be a symlink pointing to another + command line argument. With `cp -H --preserve=link', we hard-link + those two destination files. + - likewise for -L except that it applies to all files, not just + command line arguments. + + Also record directory dev/ino when using --recursive. We'll use that + info to detect this problem: cp -R dir dir. FIXME-maybe: ideally, + directory info would be recorded in a separate hash table, since + such entries are useful only while a single command line hierarchy + is being copied -- so that separate table could be cleared between + command line args. Using the same hash table to preserve hard + links means that it may not be cleared. */ + + if (x->move_mode && src_sb.st_nlink == 1) + { + earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev); + } + else if ((x->preserve_links + && (1 < src_sb.st_nlink + || (command_line_arg + && x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) + || x->dereference == DEREF_ALWAYS)) + || (x->recursive && S_ISDIR (src_mode))) + { + earlier_file = remember_copied (dst_name, src_sb.st_ino, src_sb.st_dev); + } + + /* Did we copy this inode somewhere else (in this command line argument) + and therefore this is a second hard link to the inode? */ + + if (earlier_file) + { + /* Avoid damaging the destination file system by refusing to preserve + hard-linked directories (which are found at least in Netapp snapshot + directories). */ + if (S_ISDIR (src_mode)) + { + /* If src_name and earlier_file refer to the same directory entry, + then warn about copying a directory into itself. */ + if (same_name (src_name, earlier_file)) + { + error (0, 0, _("cannot copy a directory, %s, into itself, %s"), + quote_n (0, top_level_src_name), + quote_n (1, top_level_dst_name)); + *copy_into_self = true; + goto un_backup; + } + else if (x->dereference == DEREF_ALWAYS) + { + /* This happens when e.g., encountering a directory for the + second or subsequent time via symlinks when cp is invoked + with -R and -L. E.g., + rm -rf a b c d; mkdir a b c d; ln -s ../c a; ln -s ../c b; + cp -RL a b d + */ + } + else + { + error (0, 0, _("will not create hard link %s to directory %s"), + quote_n (0, dst_name), quote_n (1, earlier_file)); + goto un_backup; + } + } + else + { + bool link_failed = (link (earlier_file, dst_name) != 0); + + /* If the link failed because of an existing destination, + remove that file and then call link again. */ + if (link_failed && errno == EEXIST) + { + if (unlink (dst_name) != 0) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + goto un_backup; + } + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + link_failed = (link (earlier_file, dst_name) != 0); + } + + if (link_failed) + { + error (0, errno, _("cannot create hard link %s to %s"), + quote_n (0, dst_name), quote_n (1, earlier_file)); + goto un_backup; + } + + return true; + } + } + + if (x->move_mode) + { + if (rename (src_name, dst_name) == 0) + { + if (x->verbose && S_ISDIR (src_mode)) + emit_verbose (src_name, dst_name, + backup_succeeded ? dst_backup : NULL); + + if (rename_succeeded) + *rename_succeeded = true; + + if (command_line_arg) + { + /* Record destination dev/ino/name, so that if we are asked + to overwrite that file again, we can detect it and fail. */ + /* It's fine to use the _source_ stat buffer (src_sb) to get the + _destination_ dev/ino, since the rename above can't have + changed those, and `mv' always uses lstat. + We could limit it further by operating + only on non-directories. */ + record_file (x->dest_info, dst_name, &src_sb); + } + + return true; + } + + /* FIXME: someday, consider what to do when moving a directory into + itself but when source and destination are on different devices. */ + + /* This happens when attempting to rename a directory to a + subdirectory of itself. */ + if (errno == EINVAL) + { + /* FIXME: this is a little fragile in that it relies on rename(2) + failing with a specific errno value. Expect problems on + non-POSIX systems. */ + error (0, 0, _("cannot move %s to a subdirectory of itself, %s"), + quote_n (0, top_level_src_name), + quote_n (1, top_level_dst_name)); + + /* Note that there is no need to call forget_created here, + (compare with the other calls in this file) since the + destination directory didn't exist before. */ + + *copy_into_self = true; + /* FIXME-cleanup: Don't return true here; adjust mv.c accordingly. + The only caller that uses this code (mv.c) ends up setting its + exit status to nonzero when copy_into_self is nonzero. */ + return true; + } + + /* WARNING: there probably exist systems for which an inter-device + rename fails with a value of errno not handled here. + If/as those are reported, add them to the condition below. + If this happens to you, please do the following and send the output + to the bug-reporting address (e.g., in the output of cp --help): + touch k; perl -e 'rename "k","/tmp/k" or print "$!(",$!+0,")\n"' + where your current directory is on one partion and /tmp is the other. + Also, please try to find the E* errno macro name corresponding to + the diagnostic and parenthesized integer, and include that in your + e-mail. One way to do that is to run a command like this + find /usr/include/. -type f \ + | xargs grep 'define.*\<E[A-Z]*\>.*\<18\>' /dev/null + where you'd replace `18' with the integer in parentheses that + was output from the perl one-liner above. + If necessary, of course, change `/tmp' to some other directory. */ + if (errno != EXDEV) + { + /* There are many ways this can happen due to a race condition. + When something happens between the initial XSTAT and the + subsequent rename, we can get many different types of errors. + For example, if the destination is initially a non-directory + or non-existent, but it is created as a directory, the rename + fails. If two `mv' commands try to rename the same file at + about the same time, one will succeed and the other will fail. + If the permissions on the directory containing the source or + destination file are made too restrictive, the rename will + fail. Etc. */ + error (0, errno, + _("cannot move %s to %s"), + quote_n (0, src_name), quote_n (1, dst_name)); + forget_created (src_sb.st_ino, src_sb.st_dev); + return false; + } + + /* The rename attempt has failed. Remove any existing destination + file so that a cross-device `mv' acts as if it were really using + the rename syscall. */ + if (unlink (dst_name) != 0 && errno != ENOENT) + { + error (0, errno, + _("inter-device move failed: %s to %s; unable to remove target"), + quote_n (0, src_name), quote_n (1, dst_name)); + forget_created (src_sb.st_ino, src_sb.st_dev); + return false; + } + + new_dst = true; + } + + /* If the ownership might change, or if it is a directory (whose + special mode bits may change after the directory is created), + omit some permissions at first, so unauthorized users cannot nip + in before the file is ready. */ + dst_mode_bits = (x->set_mode ? x->mode : src_mode) & CHMOD_MODE_BITS; + omitted_permissions = + (dst_mode_bits + & (x->preserve_ownership ? S_IRWXG | S_IRWXO + : S_ISDIR (src_mode) ? S_IWGRP | S_IWOTH + : 0)); + + delayed_ok = true; + + /* In certain modes (cp's --symbolic-link), and for certain file types + (symlinks and hard links) it doesn't make sense to preserve metadata, + or it's possible to preserve only some of it. + In such cases, set this variable to zero. */ + preserve_metadata = true; + + if (S_ISDIR (src_mode)) + { + 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, _("cannot copy cyclic symbolic link %s"), + quote (src_name)); + goto un_backup; + } + + /* Insert the current directory in the list of parents. */ + + dir = alloca (sizeof *dir); + dir->parent = ancestors; + dir->ino = src_sb.st_ino; + dir->dev = src_sb.st_dev; + + if (new_dst || !S_ISDIR (dst_sb.st_mode)) + { + /* POSIX says mkdir's behavior is implementation-defined when + (src_mode & ~S_IRWXUGO) != 0. However, common practice is + to ask mkdir to copy all the CHMOD_MODE_BITS, letting mkdir + decide what to do with S_ISUID | S_ISGID | S_ISVTX. */ + if (mkdir (dst_name, dst_mode_bits & ~omitted_permissions) != 0) + { + error (0, errno, _("cannot create directory %s"), + quote (dst_name)); + goto un_backup; + } + + /* We need search and write permissions to the new directory + for writing the directory's contents. Check if these + permissions are there. */ + + if (lstat (dst_name, &dst_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + goto un_backup; + } + else if ((dst_sb.st_mode & S_IRWXU) != S_IRWXU) + { + /* Make the new directory searchable and writable. */ + + dst_mode = dst_sb.st_mode; + restore_dst_mode = true; + + if (lchmod (dst_name, dst_mode | S_IRWXU) != 0) + { + error (0, errno, _("setting permissions for %s"), + quote (dst_name)); + goto un_backup; + } + } + + /* Insert the created directory's inode and device + numbers into the search structure, so that we can + avoid copying it again. */ + + remember_copied (dst_name, dst_sb.st_ino, dst_sb.st_dev); + + if (x->verbose) + emit_verbose (src_name, dst_name, NULL); + } + + /* Decide whether to copy the contents of the directory. */ + if (x->one_file_system && device != 0 && device != src_sb.st_dev) + { + /* Here, we are crossing a file system boundary and cp's -x option + is in effect: so don't copy the contents of this directory. */ + } + else + { + /* Copy the contents of the directory. Don't just return if + this fails -- otherwise, the failure to read a single file + in a source directory would cause the containing destination + directory not to have owner/perms set properly. */ + delayed_ok = copy_dir (src_name, dst_name, new_dst, &src_sb, dir, x, + copy_into_self); + } + } + else if (x->symbolic_link) + { + preserve_metadata = false; + + if (*src_name != '/') + { + /* Check that DST_NAME denotes a file in the current directory. */ + struct stat dot_sb; + struct stat dst_parent_sb; + char *dst_parent; + bool in_current_dir; + + dst_parent = dir_name (dst_name); + + in_current_dir = (STREQ (".", dst_parent) + /* If either stat call fails, it's ok not to report + the failure and say dst_name is in the current + directory. Other things will fail later. */ + || stat (".", &dot_sb) != 0 + || stat (dst_parent, &dst_parent_sb) != 0 + || SAME_INODE (dot_sb, dst_parent_sb)); + free (dst_parent); + + if (! in_current_dir) + { + error (0, 0, + _("%s: can make relative symbolic links only in current directory"), + quote (dst_name)); + goto un_backup; + } + } + if (symlink (src_name, dst_name) != 0) + { + error (0, errno, _("cannot create symbolic link %s to %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + goto un_backup; + } + } + + else if (x->hard_link +#ifdef LINK_FOLLOWS_SYMLINKS + /* A POSIX-conforming link syscall dereferences a symlink, yet cp, + invoked with `--link --no-dereference', should not. Thus, with + a POSIX-conforming link system call, we can't use link() here, + since that would create a hard link to the referent (effectively + dereferencing the symlink), rather than to the symlink itself. + We can approximate the desired behavior by skipping this hard-link + creating block and instead copying the symlink, via the `S_ISLNK'- + copying code below. + When link operates on the symlinks themselves, we use this block + and just call link(). */ + && !(S_ISLNK (src_mode) && x->dereference == DEREF_NEVER) +#endif + ) + { + preserve_metadata = false; + if (link (src_name, dst_name)) + { + error (0, errno, _("cannot create link %s"), quote (dst_name)); + goto un_backup; + } + } + else if (S_ISREG (src_mode) + || (x->copy_as_regular && !S_ISLNK (src_mode))) + { + copied_as_regular = true; + /* POSIX says the permission bits of the source file must be + used as the 3rd argument in the open call. Historical + practice passed all the source mode bits to 'open', but the extra + bits were ignored, so it should be the same either way. */ + if (! copy_reg (src_name, dst_name, x, src_mode & S_IRWXUGO, + omitted_permissions, &new_dst, &src_sb)) + goto un_backup; + } + else if (S_ISFIFO (src_mode)) + { + /* Use mknod, rather than mkfifo, because the former preserves + the special mode bits of a fifo on Solaris 10, while mkfifo + does not. But fall back on mkfifo, because on some BSD systems, + mknod always fails when asked to create a FIFO. */ + if (mknod (dst_name, src_mode & ~omitted_permissions, 0) != 0) +#if HAVE_MKFIFO + if (mkfifo (dst_name, src_mode & ~S_IFIFO & ~omitted_permissions) != 0) +#endif + { + error (0, errno, _("cannot create fifo %s"), quote (dst_name)); + goto un_backup; + } + } + else if (S_ISBLK (src_mode) || S_ISCHR (src_mode) || S_ISSOCK (src_mode)) + { + if (mknod (dst_name, src_mode & ~omitted_permissions, src_sb.st_rdev) + != 0) + { + error (0, errno, _("cannot create special file %s"), + quote (dst_name)); + goto un_backup; + } + } + else if (S_ISLNK (src_mode)) + { + char *src_link_val = xreadlink_with_size (src_name, src_sb.st_size); + if (src_link_val == NULL) + { + error (0, errno, _("cannot read symbolic link %s"), quote (src_name)); + goto un_backup; + } + + if (symlink (src_link_val, dst_name) == 0) + free (src_link_val); + else + { + int saved_errno = errno; + bool same_link = false; + if (x->update && !new_dst && S_ISLNK (dst_sb.st_mode) + && dst_sb.st_size == strlen (src_link_val)) + { + /* See if the destination is already the desired symlink. + FIXME: This behavior isn't documented, and seems wrong + in some cases, e.g., if the destination symlink has the + wrong ownership, permissions, or time stamps. */ + char *dest_link_val = + xreadlink_with_size (dst_name, dst_sb.st_size); + if (STREQ (dest_link_val, src_link_val)) + same_link = true; + free (dest_link_val); + } + free (src_link_val); + + if (! same_link) + { + error (0, saved_errno, _("cannot create symbolic link %s"), + quote (dst_name)); + goto un_backup; + } + } + + /* There's no need to preserve timestamps or permissions. */ + preserve_metadata = false; + + if (x->preserve_ownership) + { + /* Preserve the owner and group of the just-`copied' + symbolic link, if possible. */ +#if HAVE_LCHOWN + if (lchown (dst_name, src_sb.st_uid, src_sb.st_gid) != 0 + && ! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + dst_name); + goto un_backup; + } +#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 + } + } + else + { + error (0, 0, _("%s has unknown file type"), quote (src_name)); + goto un_backup; + } + + if (command_line_arg) + record_file (x->dest_info, dst_name, NULL); + + if ( ! preserve_metadata) + return true; + + if (copied_as_regular) + return delayed_ok; + + /* POSIX says that `cp -p' must restore the following: + - permission bits + - setuid, setgid bits + - owner and group + If it fails to restore any of those, we may give a warning but + the destination must not be removed. + FIXME: implement the above. */ + + /* 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_timestamps) + { + struct timespec timespec[2]; + timespec[0] = get_stat_atime (&src_sb); + timespec[1] = get_stat_mtime (&src_sb); + + if (utimens (dst_name, timespec) != 0) + { + error (0, errno, _("preserving times for %s"), quote (dst_name)); + if (x->require_preserve) + return false; + } + } + + /* Avoid calling chown if we know it's not necessary. */ + if (x->preserve_ownership + && (new_dst || !SAME_OWNER_AND_GROUP (src_sb, dst_sb))) + { + switch (set_owner (x, dst_name, -1, src_sb.st_uid, src_sb.st_gid)) + { + case -1: + return false; + + case 0: + src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX); + break; + } + } + + set_author (dst_name, -1, &src_sb); + + if (x->preserve_mode || x->move_mode) + { + if (copy_acl (src_name, -1, dst_name, -1, src_mode) != 0 + && x->require_preserve) + return false; + } + else if (x->set_mode) + { + if (set_acl (dst_name, -1, x->mode) != 0) + return false; + } + else + { + if (omitted_permissions) + { + omitted_permissions &= ~ cached_umask (); + + if (omitted_permissions && !restore_dst_mode) + { + /* Permissions were deliberately omitted when the file + was created due to security concerns. See whether + they need to be re-added now. It'd be faster to omit + the lstat, but deducing the current destination mode + is tricky in the presence of implementation-defined + rules for special mode bits. */ + if (new_dst && lstat (dst_name, &dst_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + return false; + } + dst_mode = dst_sb.st_mode; + if (omitted_permissions & ~dst_mode) + restore_dst_mode = true; + } + } + + if (restore_dst_mode) + { + if (lchmod (dst_name, dst_mode | omitted_permissions) != 0) + { + error (0, errno, _("preserving permissions for %s"), + quote (dst_name)); + if (x->require_preserve) + return false; + } + } + } + + return delayed_ok; + +un_backup: + + /* We have failed to create the destination file. + If we've just added a dev/ino entry via the remember_copied + call above (i.e., unless we've just failed to create a hard link), + remove the entry associating the source dev/ino with the + destination file name, so we don't try to `preserve' a link + to a file we didn't create. */ + if (earlier_file == NULL) + forget_created (src_sb.st_ino, src_sb.st_dev); + + if (dst_backup) + { + if (rename (dst_backup, dst_name) != 0) + error (0, errno, _("cannot un-backup %s"), quote (dst_name)); + else + { + if (x->verbose) + printf (_("%s -> %s (unbackup)\n"), + quote_n (0, dst_backup), quote_n (1, dst_name)); + } + } + return false; +} + +static bool +valid_options (const struct cp_options *co) +{ + assert (co != NULL); + assert (VALID_BACKUP_TYPE (co->backup_type)); + assert (VALID_SPARSE_MODE (co->sparse_mode)); + assert (!(co->hard_link && co->symbolic_link)); + return true; +} + +/* Copy the file SRC_NAME to the file DST_NAME. The files may be of + any type. NONEXISTENT_DST should be true if the file DST_NAME + is known not to exist (e.g., because its parent directory was just + created); NONEXISTENT_DST should be false if DST_NAME might already + exist. OPTIONS is ... FIXME-describe + Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the + same as) DST_NAME; otherwise, set clear it. + Return true if successful. */ + +extern bool +copy (char const *src_name, char const *dst_name, + bool nonexistent_dst, const struct cp_options *options, + bool *copy_into_self, bool *rename_succeeded) +{ + assert (valid_options (options)); + + /* Record the file names: they're used in case of error, when copying + a directory into itself. I don't like to make these tools do *any* + extra work in the common case when that work is solely to handle + exceptional cases, but in this case, I don't see a way to derive the + top level source and destination directory names where they're used. + An alternative is to use COPY_INTO_SELF and print the diagnostic + from every caller -- but I don't want to do that. */ + top_level_src_name = src_name; + top_level_dst_name = dst_name; + + return copy_internal (src_name, dst_name, nonexistent_dst, 0, NULL, + options, true, copy_into_self, rename_succeeded); +} + +/* Return true if this process has appropriate privileges to chown a + file whose owner is not the effective user ID. */ + +extern bool +chown_privileges (void) +{ +#ifdef PRIV_FILE_CHOWN + bool result; + priv_set_t *pset = priv_allocset (); + if (!pset) + xalloc_die (); + result = (getppriv (PRIV_EFFECTIVE, pset) == 0 + && priv_ismember (pset, PRIV_FILE_CHOWN)); + priv_freeset (pset); + return result; +#else + return (geteuid () == 0); +#endif +} + +/* Return true if it's OK for chown to fail, where errno is + the error number that chown failed with and X is the copying + option set. */ + +extern bool +chown_failure_ok (struct cp_options const *x) +{ + /* 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. */ + + return ((errno == EPERM || errno == EINVAL) && !x->chown_privileges); +} + +/* Return the user's umask, caching the result. */ + +extern mode_t +cached_umask (void) +{ + static mode_t mask = (mode_t) -1; + if (mask == (mode_t) -1) + { + mask = umask (0); + umask (mask); + } + return mask; +} |