summaryrefslogtreecommitdiffstats
path: root/src/copy.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:11:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:11:47 +0000
commit758f820bcc0f68aeebac1717e537ca13a320b909 (patch)
tree48111ece75cf4f98316848b37a7e26356e00669e /src/copy.c
parentInitial commit. (diff)
downloadcoreutils-upstream.tar.xz
coreutils-upstream.zip
Adding upstream version 9.1.upstream/9.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/copy.c')
-rw-r--r--src/copy.c3198
1 files changed, 3198 insertions, 0 deletions
diff --git a/src/copy.c b/src/copy.c
new file mode 100644
index 0000000..b15d919
--- /dev/null
+++ b/src/copy.c
@@ -0,0 +1,3198 @@
+/* copy.c -- core functions for copying files and directories
+ Copyright (C) 1989-2022 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <https://www.gnu.org/licenses/>. */
+
+/* Extracted from cp.c and librarified by Jim Meyering. */
+
+#include <config.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <selinux/selinux.h>
+
+#if HAVE_HURD_H
+# include <hurd.h>
+#endif
+#if HAVE_PRIV_H
+# include <priv.h>
+#endif
+
+#include "system.h"
+#include "acl.h"
+#include "alignalloc.h"
+#include "backupfile.h"
+#include "buffer-lcm.h"
+#include "canonicalize.h"
+#include "copy.h"
+#include "cp-hash.h"
+#include "die.h"
+#include "error.h"
+#include "fadvise.h"
+#include "fcntl--.h"
+#include "file-set.h"
+#include "filemode.h"
+#include "filenamecat.h"
+#include "force-link.h"
+#include "full-write.h"
+#include "hash.h"
+#include "hash-triple.h"
+#include "ignore-value.h"
+#include "ioblksize.h"
+#include "quote.h"
+#include "renameatu.h"
+#include "root-uid.h"
+#include "same.h"
+#include "savedir.h"
+#include "stat-size.h"
+#include "stat-time.h"
+#include "utimecmp.h"
+#include "utimens.h"
+#include "write-any-file.h"
+#include "areadlink.h"
+#include "yesno.h"
+#include "selinux.h"
+
+#ifndef USE_XATTR
+# define USE_XATTR false
+#endif
+
+#if USE_XATTR
+# include <attr/error_context.h>
+# include <attr/libattr.h>
+# include <stdarg.h>
+# include "verror.h"
+#endif
+
+#if HAVE_LINUX_FALLOC_H
+# include <linux/falloc.h>
+#endif
+
+/* See HAVE_FALLOCATE workaround when including this file. */
+#ifdef HAVE_LINUX_FS_H
+# include <linux/fs.h>
+#endif
+
+#if !defined FICLONE && defined __linux__
+# define FICLONE _IOW (0x94, 9, int)
+#endif
+
+#if HAVE_FCLONEFILEAT && !USE_XATTR
+# include <sys/clonefile.h>
+#endif
+
+#ifndef HAVE_FCHOWN
+# define HAVE_FCHOWN false
+# define fchown(fd, uid, gid) (-1)
+#endif
+
+#ifndef USE_ACL
+# define USE_ACL 0
+#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))
+
+/* LINK_FOLLOWS_SYMLINKS is tri-state; if it is -1, we don't know
+ how link() behaves, so assume we can't hardlink symlinks in that case. */
+#if (defined HAVE_LINKAT && ! LINKAT_SYMLINK_NOTSUP) || ! LINK_FOLLOWS_SYMLINKS
+# define CAN_HARDLINK_SYMLINKS 1
+#else
+# define CAN_HARDLINK_SYMLINKS 0
+#endif
+
+struct dir_list
+{
+ struct dir_list *parent;
+ ino_t ino;
+ dev_t dev;
+};
+
+/* Initial size of the cp.dest_info hash table. */
+#define DEST_INFO_INITIAL_CAPACITY 61
+
+static bool copy_internal (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
+ int nonexistent_dst, struct stat const *parent,
+ struct dir_list *ancestors,
+ const struct cp_options *x,
+ bool command_line_arg,
+ bool *first_dir_created_per_command_line_arg,
+ bool *copy_into_self,
+ bool *rename_succeeded);
+static bool owner_failure_ok (struct cp_options const *x);
+
+/* 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;
+
+#ifndef DEV_FD_MIGHT_BE_CHR
+# define DEV_FD_MIGHT_BE_CHR false
+#endif
+
+/* Act like fstat (DIRFD, FILENAME, ST, FLAGS), except when following
+ symbolic links on Solaris-like systems, treat any character-special
+ device like /dev/fd/0 as if it were the file it is open on. */
+static int
+follow_fstatat (int dirfd, char const *filename, struct stat *st, int flags)
+{
+ int result = fstatat (dirfd, filename, st, flags);
+
+ if (DEV_FD_MIGHT_BE_CHR && result == 0 && !(flags & AT_SYMLINK_NOFOLLOW)
+ && S_ISCHR (st->st_mode))
+ {
+ static dev_t stdin_rdev;
+ static signed char stdin_rdev_status;
+ if (stdin_rdev_status == 0)
+ {
+ struct stat stdin_st;
+ if (stat ("/dev/stdin", &stdin_st) == 0 && S_ISCHR (stdin_st.st_mode)
+ && minor (stdin_st.st_rdev) == STDIN_FILENO)
+ {
+ stdin_rdev = stdin_st.st_rdev;
+ stdin_rdev_status = 1;
+ }
+ else
+ stdin_rdev_status = -1;
+ }
+ if (0 < stdin_rdev_status && major (stdin_rdev) == major (st->st_rdev))
+ result = fstat (minor (st->st_rdev), st);
+ }
+
+ return result;
+}
+
+/* Attempt to punch a hole to avoid any permanent
+ speculative preallocation on file systems such as XFS.
+ Return values as per fallocate(2) except ENOSYS etc. are ignored. */
+
+static int
+punch_hole (int fd, off_t offset, off_t length)
+{
+ int ret = 0;
+/* +0 is to work around older <linux/fs.h> defining HAVE_FALLOCATE to empty. */
+#if HAVE_FALLOCATE + 0
+# if defined FALLOC_FL_PUNCH_HOLE && defined FALLOC_FL_KEEP_SIZE
+ ret = fallocate (fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ offset, length);
+ if (ret < 0 && (is_ENOTSUP (errno) || errno == ENOSYS))
+ ret = 0;
+# endif
+#endif
+ return ret;
+}
+
+/* Create a hole at the end of a file,
+ avoiding preallocation if requested. */
+
+static bool
+create_hole (int fd, char const *name, bool punch_holes, off_t size)
+{
+ off_t file_end = lseek (fd, size, SEEK_CUR);
+
+ if (file_end < 0)
+ {
+ error (0, errno, _("cannot lseek %s"), quoteaf (name));
+ return false;
+ }
+
+ /* Some file systems (like XFS) preallocate when write extending a file.
+ I.e., a previous write() may have preallocated extra space
+ that the seek above will not discard. A subsequent write() could
+ then make this allocation permanent. */
+ if (punch_holes && punch_hole (fd, file_end - size, size) < 0)
+ {
+ error (0, errno, _("error deallocating %s"), quoteaf (name));
+ return false;
+ }
+
+ return true;
+}
+
+
+/* Copy the regular file open on SRC_FD/SRC_NAME to DST_FD/DST_NAME,
+ honoring the MAKE_HOLES setting and using the BUF_SIZE-byte buffer
+ *ABUF for temporary storage, allocating it lazily if *ABUF is null.
+ Copy no more than MAX_N_READ bytes.
+ Return true upon successful completion;
+ print a diagnostic and return false upon error.
+ Note that for best results, BUF should be "well"-aligned.
+ Set *LAST_WRITE_MADE_HOLE to true if the final operation on
+ DEST_FD introduced a hole. Set *TOTAL_N_READ to the number of
+ bytes read. */
+static bool
+sparse_copy (int src_fd, int dest_fd, char **abuf, size_t buf_size,
+ size_t hole_size, bool punch_holes, bool allow_reflink,
+ char const *src_name, char const *dst_name,
+ uintmax_t max_n_read, off_t *total_n_read,
+ bool *last_write_made_hole)
+{
+ *last_write_made_hole = false;
+ *total_n_read = 0;
+
+ /* If not looking for holes, use copy_file_range if functional,
+ but don't use if reflink disallowed as that may be implicit. */
+ if (!hole_size && allow_reflink)
+ while (max_n_read)
+ {
+ /* Copy at most COPY_MAX bytes at a time; this is min
+ (SSIZE_MAX, SIZE_MAX) truncated to a value that is
+ surely aligned well. */
+ ssize_t copy_max = MIN (SSIZE_MAX, SIZE_MAX) >> 30 << 30;
+ ssize_t n_copied = copy_file_range (src_fd, NULL, dest_fd, NULL,
+ MIN (max_n_read, copy_max), 0);
+ if (n_copied == 0)
+ {
+ /* copy_file_range incorrectly returns 0 when reading from
+ the proc file system on the Linux kernel through at
+ least 5.6.19 (2020), so fall back on 'read' if the
+ input file seems empty. */
+ if (*total_n_read == 0)
+ break;
+ return true;
+ }
+ if (n_copied < 0)
+ {
+ if (errno == ENOSYS || is_ENOTSUP (errno)
+ || errno == EINVAL || errno == EBADF
+ || errno == EXDEV || errno == ETXTBSY)
+ break;
+
+ /* copy_file_range might not be enabled in seccomp filters,
+ so retry with a standard copy. EPERM can also occur
+ for immutable files, but that would only be in the edge case
+ where the file is made immutable after creating/truncating,
+ in which case the (more accurate) error is still shown. */
+ if (errno == EPERM && *total_n_read == 0)
+ break;
+
+ if (errno == EINTR)
+ n_copied = 0;
+ else
+ {
+ error (0, errno, _("error copying %s to %s"),
+ quoteaf_n (0, src_name), quoteaf_n (1, dst_name));
+ return false;
+ }
+ }
+ max_n_read -= n_copied;
+ *total_n_read += n_copied;
+ }
+
+ bool make_hole = false;
+ off_t psize = 0;
+
+ while (max_n_read)
+ {
+ if (!*abuf)
+ *abuf = xalignalloc (getpagesize (), buf_size);
+ char *buf = *abuf;
+ ssize_t n_read = read (src_fd, buf, MIN (max_n_read, buf_size));
+ if (n_read < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ error (0, errno, _("error reading %s"), quoteaf (src_name));
+ return false;
+ }
+ if (n_read == 0)
+ break;
+ max_n_read -= n_read;
+ *total_n_read += n_read;
+
+ /* Loop over the input buffer in chunks of hole_size. */
+ size_t csize = hole_size ? hole_size : buf_size;
+ char *cbuf = buf;
+ char *pbuf = buf;
+
+ while (n_read)
+ {
+ bool prev_hole = make_hole;
+ csize = MIN (csize, n_read);
+
+ if (hole_size && csize)
+ make_hole = is_nul (cbuf, csize);
+
+ bool transition = (make_hole != prev_hole) && psize;
+ bool last_chunk = (n_read == csize && ! make_hole) || ! csize;
+
+ if (transition || last_chunk)
+ {
+ if (! transition)
+ psize += csize;
+
+ if (! prev_hole)
+ {
+ if (full_write (dest_fd, pbuf, psize) != psize)
+ {
+ error (0, errno, _("error writing %s"),
+ quoteaf (dst_name));
+ return false;
+ }
+ }
+ else
+ {
+ if (! create_hole (dest_fd, dst_name, punch_holes, psize))
+ return false;
+ }
+
+ pbuf = cbuf;
+ psize = csize;
+
+ if (last_chunk)
+ {
+ if (! csize)
+ n_read = 0; /* Finished processing buffer. */
+
+ if (transition)
+ csize = 0; /* Loop again to deal with last chunk. */
+ else
+ psize = 0; /* Reset for next read loop. */
+ }
+ }
+ else /* Coalesce writes/seeks. */
+ {
+ if (INT_ADD_WRAPV (psize, csize, &psize))
+ {
+ error (0, 0, _("overflow reading %s"), quoteaf (src_name));
+ return false;
+ }
+ }
+
+ n_read -= csize;
+ cbuf += csize;
+ }
+
+ *last_write_made_hole = make_hole;
+
+ /* It's tempting to break early here upon a short read from
+ a regular file. That would save the final read syscall
+ for each file. Unfortunately that doesn't work for
+ certain files in /proc or /sys with linux kernels. */
+ }
+
+ /* Ensure a trailing hole is created, so that subsequent
+ calls of sparse_copy() start at the correct offset. */
+ if (make_hole && ! create_hole (dest_fd, dst_name, punch_holes, psize))
+ return false;
+ else
+ return true;
+}
+
+/* Perform the O(1) btrfs clone operation, if possible.
+ Upon success, return 0. Otherwise, return -1 and set errno. */
+static inline int
+clone_file (int dest_fd, int src_fd)
+{
+#ifdef FICLONE
+ return ioctl (dest_fd, FICLONE, src_fd);
+#else
+ (void) dest_fd;
+ (void) src_fd;
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
+/* Write N_BYTES zero bytes to file descriptor FD. Return true if successful.
+ Upon write failure, set errno and return false. */
+static bool
+write_zeros (int fd, off_t n_bytes)
+{
+ static char *zeros;
+ static size_t nz = IO_BUFSIZE;
+
+ /* Attempt to use a relatively large calloc'd source buffer for
+ efficiency, but if that allocation fails, resort to a smaller
+ statically allocated one. */
+ if (zeros == NULL)
+ {
+ static char fallback[1024];
+ zeros = calloc (nz, 1);
+ if (zeros == NULL)
+ {
+ zeros = fallback;
+ nz = sizeof fallback;
+ }
+ }
+
+ while (n_bytes)
+ {
+ size_t n = MIN (nz, n_bytes);
+ if ((full_write (fd, zeros, n)) != n)
+ return false;
+ n_bytes -= n;
+ }
+
+ return true;
+}
+
+#ifdef SEEK_HOLE
+/* Perform an efficient extent copy, if possible. This avoids
+ the overhead of detecting holes in hole-introducing/preserving
+ copy, and thus makes copying sparse files much more efficient.
+ Copy from SRC_FD to DEST_FD, using *ABUF (of size BUF_SIZE) for a buffer.
+ Allocate *ABUF lazily if *ABUF is null.
+ Look for holes of size HOLE_SIZE in the input.
+ The input file is of size SRC_TOTAL_SIZE.
+ Use SPARSE_MODE to determine whether to create holes in the output.
+ SRC_NAME and DST_NAME are the input and output file names.
+ Return true if successful, false (with a diagnostic) otherwise. */
+
+static bool
+lseek_copy (int src_fd, int dest_fd, char **abuf, size_t buf_size,
+ size_t hole_size, off_t ext_start, off_t src_total_size,
+ enum Sparse_type sparse_mode,
+ bool allow_reflink,
+ char const *src_name, char const *dst_name)
+{
+ off_t last_ext_start = 0;
+ off_t last_ext_len = 0;
+ off_t dest_pos = 0;
+ bool wrote_hole_at_eof = true;
+
+ while (0 <= ext_start)
+ {
+ off_t ext_end = lseek (src_fd, ext_start, SEEK_HOLE);
+ if (ext_end < 0)
+ {
+ if (errno != ENXIO)
+ goto cannot_lseek;
+ ext_end = src_total_size;
+ if (ext_end <= ext_start)
+ {
+ /* The input file grew; get its current size. */
+ src_total_size = lseek (src_fd, 0, SEEK_END);
+ if (src_total_size < 0)
+ goto cannot_lseek;
+
+ /* If the input file shrank after growing, stop copying. */
+ if (src_total_size <= ext_start)
+ break;
+
+ ext_end = src_total_size;
+ }
+ }
+ /* If the input file must have grown, increase its measured size. */
+ if (src_total_size < ext_end)
+ src_total_size = ext_end;
+
+ if (lseek (src_fd, ext_start, SEEK_SET) < 0)
+ goto cannot_lseek;
+
+ wrote_hole_at_eof = false;
+ off_t ext_hole_size = ext_start - last_ext_start - last_ext_len;
+
+ if (ext_hole_size)
+ {
+ if (sparse_mode != SPARSE_NEVER)
+ {
+ if (! create_hole (dest_fd, dst_name,
+ sparse_mode == SPARSE_ALWAYS,
+ ext_hole_size))
+ return false;
+ wrote_hole_at_eof = true;
+ }
+ else
+ {
+ /* When not inducing holes and when there is a hole between
+ the end of the previous extent and the beginning of the
+ current one, write zeros to the destination file. */
+ if (! write_zeros (dest_fd, ext_hole_size))
+ {
+ error (0, errno, _("%s: write failed"),
+ quotef (dst_name));
+ return false;
+ }
+ }
+ }
+
+ off_t ext_len = ext_end - ext_start;
+ last_ext_start = ext_start;
+ last_ext_len = ext_len;
+
+ /* Copy this extent, looking for further opportunities to not
+ bother to write zeros unless --sparse=never, since SEEK_HOLE
+ is conservative and may miss some holes. */
+ off_t n_read;
+ bool read_hole;
+ if ( ! sparse_copy (src_fd, dest_fd, abuf, buf_size,
+ sparse_mode == SPARSE_NEVER ? 0 : hole_size,
+ true, allow_reflink, src_name, dst_name,
+ ext_len, &n_read, &read_hole))
+ return false;
+
+ dest_pos = ext_start + n_read;
+ if (n_read)
+ wrote_hole_at_eof = read_hole;
+ if (n_read < ext_len)
+ {
+ /* The input file shrank. */
+ src_total_size = dest_pos;
+ break;
+ }
+
+ ext_start = lseek (src_fd, dest_pos, SEEK_DATA);
+ if (ext_start < 0 && errno != ENXIO)
+ goto cannot_lseek;
+ }
+
+ /* When the source file ends with a hole, we have to do a little more work,
+ since the above copied only up to and including the final extent.
+ In order to complete the copy, we may have to insert a hole or write
+ zeros in the destination corresponding to the source file's hole-at-EOF.
+
+ In addition, if the final extent was a block of zeros at EOF and we've
+ just converted them to a hole in the destination, we must call ftruncate
+ here in order to record the proper length in the destination. */
+ if ((dest_pos < src_total_size || wrote_hole_at_eof)
+ && ! (sparse_mode == SPARSE_NEVER
+ ? write_zeros (dest_fd, src_total_size - dest_pos)
+ : ftruncate (dest_fd, src_total_size) == 0))
+ {
+ error (0, errno, _("failed to extend %s"), quoteaf (dst_name));
+ return false;
+ }
+
+ if (sparse_mode == SPARSE_ALWAYS && dest_pos < src_total_size
+ && punch_hole (dest_fd, dest_pos, src_total_size - dest_pos) < 0)
+ {
+ error (0, errno, _("error deallocating %s"), quoteaf (dst_name));
+ return false;
+ }
+
+ return true;
+
+ cannot_lseek:
+ error (0, errno, _("cannot lseek %s"), quoteaf (src_name));
+ return false;
+}
+#endif
+
+/* 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 */
+
+ATTRIBUTE_PURE
+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;
+}
+
+static bool
+errno_unsupported (int err)
+{
+ return err == ENOTSUP || err == ENODATA;
+}
+
+#if USE_XATTR
+ATTRIBUTE_FORMAT ((printf, 2, 3))
+static void
+copy_attr_error (MAYBE_UNUSED struct error_context *ctx,
+ char const *fmt, ...)
+{
+ if (!errno_unsupported (errno))
+ {
+ int err = errno;
+ va_list ap;
+
+ /* use verror module to print error message */
+ va_start (ap, fmt);
+ verror (0, err, fmt, ap);
+ va_end (ap);
+ }
+}
+
+ATTRIBUTE_FORMAT ((printf, 2, 3))
+static void
+copy_attr_allerror (MAYBE_UNUSED struct error_context *ctx,
+ char const *fmt, ...)
+{
+ int err = errno;
+ va_list ap;
+
+ /* use verror module to print error message */
+ va_start (ap, fmt);
+ verror (0, err, fmt, ap);
+ va_end (ap);
+}
+
+static char const *
+copy_attr_quote (MAYBE_UNUSED struct error_context *ctx, char const *str)
+{
+ return quoteaf (str);
+}
+
+static void
+copy_attr_free (MAYBE_UNUSED struct error_context *ctx,
+ MAYBE_UNUSED char const *str)
+{
+}
+
+/* Exclude SELinux extended attributes that are otherwise handled,
+ and are problematic to copy again. Also honor attributes
+ configured for exclusion in /etc/xattr.conf.
+ FIXME: Should we handle POSIX ACLs similarly?
+ Return zero to skip. */
+static int
+check_selinux_attr (char const *name, struct error_context *ctx)
+{
+ return STRNCMP_LIT (name, "security.selinux")
+ && attr_copy_check_permissions (name, ctx);
+}
+
+/* If positive SRC_FD and DST_FD descriptors are passed,
+ then copy by fd, otherwise copy by name. */
+
+static bool
+copy_attr (char const *src_path, int src_fd,
+ char const *dst_path, int dst_fd, struct cp_options const *x)
+{
+ bool all_errors = (!x->data_copy_required || x->require_preserve_xattr);
+ bool some_errors = (!all_errors && !x->reduce_diagnostics);
+ int (*check) (char const *, struct error_context *)
+ = (x->preserve_security_context || x->set_security_context
+ ? check_selinux_attr : NULL);
+
+# if 4 < __GNUC__ + (8 <= __GNUC_MINOR__)
+ /* Pacify gcc -Wsuggest-attribute=format through at least GCC 11.2.1. */
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+# endif
+ struct error_context *ctx
+ = (all_errors || some_errors
+ ? (&(struct error_context) {
+ .error = all_errors ? copy_attr_allerror : copy_attr_error,
+ .quote = copy_attr_quote,
+ .quote_free = copy_attr_free
+ })
+ : NULL);
+# if 4 < __GNUC__ + (8 <= __GNUC_MINOR__)
+# pragma GCC diagnostic pop
+# endif
+
+ return ! (0 <= src_fd && 0 <= dst_fd
+ ? attr_copy_fd (src_path, src_fd, dst_path, dst_fd, check, ctx)
+ : attr_copy_file (src_path, dst_path, check, ctx));
+}
+#else /* USE_XATTR */
+
+static bool
+copy_attr (MAYBE_UNUSED char const *src_path,
+ MAYBE_UNUSED int src_fd,
+ MAYBE_UNUSED char const *dst_path,
+ MAYBE_UNUSED int dst_fd,
+ MAYBE_UNUSED struct cp_options const *x)
+{
+ return true;
+}
+#endif /* USE_XATTR */
+
+/* Read the contents of the directory SRC_NAME_IN, and recursively
+ copy the contents to DST_NAME_IN aka DST_DIRFD+DST_RELNAME_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.
+ Propagate *FIRST_DIR_CREATED_PER_COMMAND_LINE_ARG from
+ caller to each invocation of copy_internal. Be careful to
+ pass the address of a temporary, and to update
+ *FIRST_DIR_CREATED_PER_COMMAND_LINE_ARG only upon completion.
+ Return true if successful. */
+
+static bool
+copy_dir (char const *src_name_in, char const *dst_name_in,
+ int dst_dirfd, char const *dst_relname_in, bool new_dst,
+ const struct stat *src_sb, struct dir_list *ancestors,
+ const struct cp_options *x,
+ bool *first_dir_created_per_command_line_arg,
+ 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, SAVEDIR_SORT_FASTREAD);
+ if (name_space == NULL)
+ {
+ /* This diagnostic is a bit vague because savedir can fail in
+ several different ways. */
+ error (0, errno, _("cannot access %s"), quoteaf (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;
+
+ bool new_first_dir_created = false;
+ 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);
+ bool first_dir_created = *first_dir_created_per_command_line_arg;
+ bool rename_succeeded;
+
+ ok &= copy_internal (src_name, dst_name, dst_dirfd,
+ dst_name + (dst_relname_in - dst_name_in),
+ new_dst, src_sb,
+ ancestors, &non_command_line_options, false,
+ &first_dir_created,
+ &local_copy_into_self, &rename_succeeded);
+ *copy_into_self |= local_copy_into_self;
+
+ free (dst_name);
+ free (src_name);
+
+ /* If we're copying into self, there's no point in continuing,
+ and in fact, that would even infloop, now that we record only
+ the first created directory per command line argument. */
+ if (local_copy_into_self)
+ break;
+
+ new_first_dir_created |= first_dir_created;
+ namep += strlen (namep) + 1;
+ }
+ free (name_space);
+ *first_dir_created_per_command_line_arg = new_first_dir_created;
+
+ 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 aka DST_DIRFD+DST_RELNAME
+ instead; for safety prefer lchownat since no
+ symbolic links should be involved. DEST_DESC must
+ refer to the same file as DST_NAME if defined.
+ Upon failure to set both UID and GID, try to set only the GID.
+ NEW_DST is true if the file was newly created; otherwise,
+ DST_SB is the status of the destination.
+ Return 1 if the initial 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 dst_dirfd, char const *dst_relname, int dest_desc,
+ struct stat const *src_sb, bool new_dst,
+ struct stat const *dst_sb)
+{
+ uid_t uid = src_sb->st_uid;
+ gid_t gid = src_sb->st_gid;
+
+ /* Naively changing the ownership of an already-existing file before
+ changing its permissions would create a window of vulnerability if
+ the file's old permissions are too generous for the new owner and
+ group. Avoid the window by first changing to a restrictive
+ temporary mode if necessary. */
+
+ if (!new_dst && (x->preserve_mode || x->move_mode || x->set_mode))
+ {
+ mode_t old_mode = dst_sb->st_mode;
+ mode_t new_mode =
+ (x->preserve_mode || x->move_mode ? src_sb->st_mode : x->mode);
+ mode_t restrictive_temp_mode = old_mode & new_mode & S_IRWXU;
+
+ if ((USE_ACL
+ || (old_mode & CHMOD_MODE_BITS
+ & (~new_mode | S_ISUID | S_ISGID | S_ISVTX)))
+ && qset_acl (dst_name, dest_desc, restrictive_temp_mode) != 0)
+ {
+ if (! owner_failure_ok (x))
+ error (0, errno, _("clearing permissions for %s"),
+ quoteaf (dst_name));
+ return -x->require_preserve;
+ }
+ }
+
+ if (HAVE_FCHOWN && dest_desc != -1)
+ {
+ if (fchown (dest_desc, uid, gid) == 0)
+ return 1;
+ if (errno == EPERM || errno == EINVAL)
+ {
+ /* We've failed to set *both*. Now, try to set just the group
+ ID, but ignore any failure here, and don't change errno. */
+ int saved_errno = errno;
+ ignore_value (fchown (dest_desc, -1, gid));
+ errno = saved_errno;
+ }
+ }
+ else
+ {
+ if (lchownat (dst_dirfd, dst_relname, uid, gid) == 0)
+ return 1;
+ if (errno == EPERM || errno == EINVAL)
+ {
+ /* We've failed to set *both*. Now, try to set just the group
+ ID, but ignore any failure here, and don't change errno. */
+ int saved_errno = errno;
+ ignore_value (lchownat (dst_dirfd, dst_relname, -1, gid));
+ errno = saved_errno;
+ }
+ }
+
+ if (! chown_failure_ok (x))
+ {
+ error (0, errno, _("failed to preserve ownership for %s"),
+ quoteaf (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
+ DST_NAME if defined. */
+
+static void
+set_author (char const *dst_name, int dest_desc, const struct stat *src_sb)
+{
+#if HAVE_STRUCT_STAT_ST_AUTHOR
+ /* FIXME: Modify the following code so that it does not
+ follow symbolic links. */
+
+ /* 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"), quoteaf (dst_name));
+ else
+ {
+ error_t err = file_chauthor (file, src_sb->st_author);
+ if (err)
+ error (0, err, _("failed to preserve authorship for %s"),
+ quoteaf (dst_name));
+ mach_port_deallocate (mach_task_self (), file);
+ }
+#else
+ (void) dst_name;
+ (void) dest_desc;
+ (void) src_sb;
+#endif
+}
+
+/* Set the default security context for the process. New files will
+ have this security context set. Also existing files can have their
+ context adjusted based on this process context, by
+ set_file_security_ctx() called with PROCESS_LOCAL=true.
+ This should be called before files are created so there is no race
+ where a file may be present without an appropriate security context.
+ Based on CP_OPTIONS, diagnose warnings and fail when appropriate.
+ Return FALSE on failure, TRUE on success. */
+
+bool
+set_process_security_ctx (char const *src_name, char const *dst_name,
+ mode_t mode, bool new_dst, const struct cp_options *x)
+{
+ if (x->preserve_security_context)
+ {
+ /* Set the default context for the process to match the source. */
+ bool all_errors = !x->data_copy_required || x->require_preserve_context;
+ bool some_errors = !all_errors && !x->reduce_diagnostics;
+ char *con;
+
+ if (0 <= lgetfilecon (src_name, &con))
+ {
+ if (setfscreatecon (con) < 0)
+ {
+ if (all_errors || (some_errors && !errno_unsupported (errno)))
+ error (0, errno,
+ _("failed to set default file creation context to %s"),
+ quote (con));
+ if (x->require_preserve_context)
+ {
+ freecon (con);
+ return false;
+ }
+ }
+ freecon (con);
+ }
+ else
+ {
+ if (all_errors || (some_errors && !errno_unsupported (errno)))
+ {
+ error (0, errno,
+ _("failed to get security context of %s"),
+ quoteaf (src_name));
+ }
+ if (x->require_preserve_context)
+ return false;
+ }
+ }
+ else if (x->set_security_context)
+ {
+ /* With -Z, adjust the default context for the process
+ to have the type component adjusted as per the destination path. */
+ if (new_dst && defaultcon (x->set_security_context, dst_name, mode) < 0
+ && ! ignorable_ctx_err (errno))
+ {
+ error (0, errno,
+ _("failed to set default file creation context for %s"),
+ quoteaf (dst_name));
+ }
+ }
+
+ return true;
+}
+
+/* Reset the security context of DST_NAME, to that already set
+ as the process default if !X->set_security_context. Otherwise
+ adjust the type component of DST_NAME's security context as
+ per the system default for that path. Issue warnings upon
+ failure, when allowed by various settings in X.
+ Return false on failure, true on success. */
+
+bool
+set_file_security_ctx (char const *dst_name,
+ bool recurse, const struct cp_options *x)
+{
+ bool all_errors = (!x->data_copy_required
+ || x->require_preserve_context);
+ bool some_errors = !all_errors && !x->reduce_diagnostics;
+
+ if (! restorecon (x->set_security_context, dst_name, recurse))
+ {
+ if (all_errors || (some_errors && !errno_unsupported (errno)))
+ error (0, errno, _("failed to set the security context of %s"),
+ quoteaf_n (0, dst_name));
+ return false;
+ }
+
+ return true;
+}
+
+/* Change the file mode bits of the file identified by DESC or
+ DIRFD+NAME to MODE. Use DESC if DESC is valid and fchmod is
+ available, DIRFD+NAME otherwise. */
+
+static int
+fchmod_or_lchmod (int desc, int dirfd, char const *name, mode_t mode)
+{
+#if HAVE_FCHMOD
+ if (0 <= desc)
+ return fchmod (desc, mode);
+#endif
+ return lchmodat (dirfd, name, mode);
+}
+
+#ifndef HAVE_STRUCT_STAT_ST_BLOCKS
+# define HAVE_STRUCT_STAT_ST_BLOCKS 0
+#endif
+
+/* Type of scan being done on the input when looking for sparseness. */
+enum scantype
+ {
+ /* An error was found when determining scantype. */
+ ERROR_SCANTYPE,
+
+ /* No fancy scanning; just read and write. */
+ PLAIN_SCANTYPE,
+
+ /* Read and examine data looking for zero blocks; useful when
+ attempting to create sparse output. */
+ ZERO_SCANTYPE,
+
+ /* lseek information is available. */
+ LSEEK_SCANTYPE,
+ };
+
+/* Result of infer_scantype. */
+union scan_inference
+{
+ /* Used if infer_scantype returns LSEEK_SCANTYPE. This is the
+ offset of the first data block, or -1 if the file has no data. */
+ off_t ext_start;
+};
+
+/* Return how to scan a file with descriptor FD and stat buffer SB.
+ Store any information gathered into *SCAN_INFERENCE. */
+static enum scantype
+infer_scantype (int fd, struct stat const *sb,
+ union scan_inference *scan_inference)
+{
+ if (! (HAVE_STRUCT_STAT_ST_BLOCKS
+ && S_ISREG (sb->st_mode)
+ && ST_NBLOCKS (*sb) < sb->st_size / ST_NBLOCKSIZE))
+ return PLAIN_SCANTYPE;
+
+#ifdef SEEK_HOLE
+ scan_inference->ext_start = lseek (fd, 0, SEEK_DATA);
+ if (0 <= scan_inference->ext_start || errno == ENXIO)
+ return LSEEK_SCANTYPE;
+ else if (errno != EINVAL && !is_ENOTSUP (errno))
+ return ERROR_SCANTYPE;
+#endif
+
+ return ZERO_SCANTYPE;
+}
+
+
+/* Copy a regular file from SRC_NAME to DST_NAME aka DST_DIRFD+DST_RELNAME.
+ 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 initially as in copy_internal.
+ If successful, set *NEW_DST to true if the destination file was created and
+ to false otherwise; if unsuccessful, perhaps set *NEW_DST to some value.
+ SRC_SB is the result of calling follow_fstatat on SRC_NAME. */
+
+static bool
+copy_reg (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
+ const struct cp_options *x,
+ mode_t dst_mode, mode_t omitted_permissions, bool *new_dst,
+ struct stat const *src_sb)
+{
+ char *buf = NULL;
+ int dest_desc;
+ int dest_errno;
+ int source_desc;
+ mode_t src_mode = src_sb->st_mode;
+ mode_t extra_permissions;
+ struct stat sb;
+ struct stat src_open_sb;
+ union scan_inference scan_inference;
+ bool return_val = true;
+ bool data_copy_required = x->data_copy_required;
+ bool preserve_xattr = USE_XATTR & x->preserve_xattr;
+
+ source_desc = open (src_name,
+ (O_RDONLY | O_BINARY
+ | (x->dereference == DEREF_NEVER ? O_NOFOLLOW : 0)));
+ if (source_desc < 0)
+ {
+ error (0, errno, _("cannot open %s for reading"), quoteaf (src_name));
+ return false;
+ }
+
+ if (fstat (source_desc, &src_open_sb) != 0)
+ {
+ error (0, errno, _("cannot fstat %s"), quoteaf (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"),
+ quoteaf (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)
+ {
+ int open_flags =
+ O_WRONLY | O_BINARY | (data_copy_required ? O_TRUNC : 0);
+ dest_desc = openat (dst_dirfd, dst_relname, open_flags);
+ dest_errno = errno;
+
+ /* When using cp --preserve=context to copy to an existing destination,
+ reset the context as per the default context, which has already been
+ set according to the src.
+ When using the mutually exclusive -Z option, then adjust the type of
+ the existing context according to the system default for the dest.
+ Note we set the context here, _after_ the file is opened, lest the
+ new context disallow that. */
+ if (0 <= dest_desc
+ && (x->set_security_context || x->preserve_security_context))
+ {
+ if (! set_file_security_ctx (dst_name, false, x))
+ {
+ if (x->require_preserve_context)
+ {
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
+ }
+ }
+
+ if (dest_desc < 0 && dest_errno != ENOENT
+ && x->unlink_dest_after_failed_open)
+ {
+ if (unlinkat (dst_dirfd, dst_relname, 0) == 0)
+ {
+ if (x->verbose)
+ printf (_("removed %s\n"), quoteaf (dst_name));
+ }
+ else if (errno != ENOENT)
+ {
+ error (0, errno, _("cannot remove %s"), quoteaf (dst_name));
+ return_val = false;
+ goto close_src_desc;
+ }
+
+ dest_errno = ENOENT;
+ }
+
+ if (dest_desc < 0 && dest_errno == ENOENT)
+ {
+ /* Ensure there is no race where a file may be left without
+ an appropriate security context. */
+ if (x->set_security_context)
+ {
+ if (! set_process_security_ctx (src_name, dst_name, dst_mode,
+ true, x))
+ {
+ return_val = false;
+ goto close_src_desc;
+ }
+ }
+
+ /* Tell caller that the destination file is created. */
+ *new_dst = true;
+ }
+ }
+
+ if (*new_dst)
+ {
+#if HAVE_FCLONEFILEAT && !USE_XATTR
+/* CLONE_NOOWNERCOPY only available on macos >= 10.13. */
+# ifndef CLONE_NOOWNERCOPY
+# define CLONE_NOOWNERCOPY 0
+# endif
+ int clone_flags = x->preserve_ownership ? 0 : CLONE_NOOWNERCOPY;
+ if (data_copy_required && x->reflink_mode
+ && x->preserve_mode && x->preserve_timestamps
+ && (x->preserve_ownership || CLONE_NOOWNERCOPY)
+ && (fclonefileat (source_desc, dst_dirfd, dst_relname, clone_flags)
+ == 0))
+ goto close_src_desc;
+#endif
+
+ /* To allow copying xattrs on read-only files, create with u+w.
+ This satisfies an inode permission check done by
+ xattr_permission in fs/xattr.c of the GNU/Linux kernel. */
+ mode_t open_mode =
+ ((dst_mode & ~omitted_permissions)
+ | (preserve_xattr && !x->owner_privileges ? S_IWUSR : 0));
+ extra_permissions = open_mode & ~dst_mode; /* either 0 or S_IWUSR */
+
+ int open_flags = O_WRONLY | O_CREAT | O_BINARY;
+ dest_desc = openat (dst_dirfd, dst_relname, open_flags | O_EXCL,
+ open_mode);
+ dest_errno = errno;
+
+ /* When trying to copy through a dangling destination symlink,
+ the above open fails with EEXIST. If that happens, and
+ readlinkat shows that it is a symlink, then we
+ have a problem: trying to resolve this dangling symlink to
+ a directory/destination-entry pair is fundamentally racy,
+ so punt. If x->open_dangling_dest_symlink is set (cp sets
+ that when POSIXLY_CORRECT is set in the environment), simply
+ call open again, but without O_EXCL (potentially dangerous).
+ If not, fail with a diagnostic. These shenanigans are necessary
+ only when copying, i.e., not in move_mode. */
+ if (dest_desc < 0 && dest_errno == EEXIST && ! x->move_mode)
+ {
+ char dummy[1];
+ if (0 <= readlinkat (dst_dirfd, dst_relname, dummy, sizeof dummy))
+ {
+ if (x->open_dangling_dest_symlink)
+ {
+ dest_desc = openat (dst_dirfd, dst_relname,
+ open_flags, open_mode);
+ dest_errno = errno;
+ }
+ else
+ {
+ error (0, 0, _("not writing through dangling symlink %s"),
+ quoteaf (dst_name));
+ return_val = false;
+ goto close_src_desc;
+ }
+ }
+ }
+
+ /* Improve quality of diagnostic when a nonexistent dst_name
+ ends in a slash and open fails with errno == EISDIR. */
+ if (dest_desc < 0 && dest_errno == EISDIR
+ && *dst_name && dst_name[strlen (dst_name) - 1] == '/')
+ dest_errno = ENOTDIR;
+ }
+ else
+ {
+ omitted_permissions = extra_permissions = 0;
+ }
+
+ if (dest_desc < 0)
+ {
+ error (0, dest_errno, _("cannot create regular file %s"),
+ quoteaf (dst_name));
+ return_val = false;
+ goto close_src_desc;
+ }
+
+ /* --attributes-only overrides --reflink. */
+ if (data_copy_required && x->reflink_mode)
+ {
+ if (clone_file (dest_desc, source_desc) == 0)
+ data_copy_required = false;
+ else if (x->reflink_mode == REFLINK_ALWAYS)
+ {
+ error (0, errno, _("failed to clone %s from %s"),
+ quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
+ }
+
+ if (! (data_copy_required | x->preserve_ownership | extra_permissions))
+ sb.st_mode = 0;
+ else if (fstat (dest_desc, &sb) != 0)
+ {
+ error (0, errno, _("cannot fstat %s"), quoteaf (dst_name));
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
+
+ /* If extra permissions needed for copy_xattr didn't happen (e.g.,
+ due to umask) chmod to add them temporarily; if that fails give
+ up with extra permissions, letting copy_attr fail later. */
+ mode_t temporary_mode = sb.st_mode | extra_permissions;
+ if (temporary_mode != sb.st_mode
+ && (fchmod_or_lchmod (dest_desc, dst_dirfd, dst_relname, temporary_mode)
+ != 0))
+ extra_permissions = 0;
+
+ if (data_copy_required)
+ {
+ /* Choose a suitable buffer size; it may be adjusted later. */
+ size_t buf_size = io_blksize (sb);
+ size_t hole_size = ST_BLKSIZE (sb);
+
+ /* Deal with sparse files. */
+ enum scantype scantype = infer_scantype (source_desc, &src_open_sb,
+ &scan_inference);
+ if (scantype == ERROR_SCANTYPE)
+ {
+ error (0, errno, _("cannot lseek %s"), quoteaf (src_name));
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
+ bool make_holes
+ = (S_ISREG (sb.st_mode)
+ && (x->sparse_mode == SPARSE_ALWAYS
+ || (x->sparse_mode == SPARSE_AUTO
+ && scantype != PLAIN_SCANTYPE)));
+
+ fdadvise (source_desc, 0, 0, FADVISE_SEQUENTIAL);
+
+ /* If not making a sparse file, try to use a more-efficient
+ buffer size. */
+ if (! make_holes)
+ {
+ /* 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);
+ size_t blcm = buffer_lcm (io_blksize (src_open_sb), buf_size,
+ blcm_max);
+
+ /* 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;
+ }
+
+ off_t n_read;
+ bool wrote_hole_at_eof = false;
+ if (! (
+#ifdef SEEK_HOLE
+ scantype == LSEEK_SCANTYPE
+ ? lseek_copy (source_desc, dest_desc, &buf, buf_size, hole_size,
+ scan_inference.ext_start, src_open_sb.st_size,
+ make_holes ? x->sparse_mode : SPARSE_NEVER,
+ x->reflink_mode != REFLINK_NEVER,
+ src_name, dst_name)
+ :
+#endif
+ sparse_copy (source_desc, dest_desc, &buf, buf_size,
+ make_holes ? hole_size : 0,
+ x->sparse_mode == SPARSE_ALWAYS,
+ x->reflink_mode != REFLINK_NEVER,
+ src_name, dst_name, UINTMAX_MAX, &n_read,
+ &wrote_hole_at_eof)))
+ {
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
+ else if (wrote_hole_at_eof && ftruncate (dest_desc, n_read) < 0)
+ {
+ error (0, errno, _("failed to extend %s"), quoteaf (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 (fdutimensat (dest_desc, dst_dirfd, dst_relname, timespec, 0) != 0)
+ {
+ error (0, errno, _("preserving times for %s"), quoteaf (dst_name));
+ if (x->require_preserve)
+ {
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
+ }
+ }
+
+ /* Set ownership before xattrs as changing owners will
+ clear capabilities. */
+ if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb))
+ {
+ switch (set_owner (x, dst_name, dst_dirfd, dst_relname, dest_desc,
+ src_sb, *new_dst, &sb))
+ {
+ case -1:
+ return_val = false;
+ goto close_src_and_dst_desc;
+
+ case 0:
+ src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX);
+ break;
+ }
+ }
+
+ if (preserve_xattr)
+ {
+ if (!copy_attr (src_name, source_desc, dst_name, dest_desc, x)
+ && x->require_preserve_xattr)
+ return_val = false;
+ }
+
+ 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 (x->explicit_no_preserve_mode && *new_dst)
+ {
+ if (set_acl (dst_name, dest_desc, MODE_RW_UGO & ~cached_umask ()) != 0)
+ return_val = false;
+ }
+ else if (omitted_permissions | extra_permissions)
+ {
+ omitted_permissions &= ~ cached_umask ();
+ if ((omitted_permissions | extra_permissions)
+ && (fchmod_or_lchmod (dest_desc, dst_dirfd, dst_relname,
+ dst_mode & ~ cached_umask ())
+ != 0))
+ {
+ error (0, errno, _("preserving permissions for %s"),
+ quoteaf (dst_name));
+ if (x->require_preserve)
+ return_val = false;
+ }
+ }
+
+close_src_and_dst_desc:
+ if (close (dest_desc) < 0)
+ {
+ error (0, errno, _("failed to close %s"), quoteaf (dst_name));
+ return_val = false;
+ }
+close_src_desc:
+ if (close (source_desc) < 0)
+ {
+ error (0, errno, _("failed to close %s"), quoteaf (src_name));
+ return_val = false;
+ }
+
+ alignfree (buf);
+ return return_val;
+}
+
+/* Return whether it's OK that two files are the "same" by some measure.
+ The first file is SRC_NAME and has status SRC_SB.
+ The second is DST_DIRFD+DST_RELNAME and has status DST_SB.
+ The copying options are X. 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. */
+
+static bool
+same_file_ok (char const *src_name, struct stat const *src_sb,
+ int dst_dirfd, char const *dst_relname, struct stat const *dst_sb,
+ const struct cp_options *x, bool *return_now)
+{
+ 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;
+
+ /* 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 usually ok
+ when they are distinct. */
+ if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
+ {
+ bool sn = same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname);
+ if ( ! sn)
+ {
+ /* It's fine when we're making any type of backup. */
+ if (x->backup_type != no_backups)
+ return true;
+
+ /* Here we have two symlinks that are hard-linked together,
+ and we're not making backups. In this unusual case, simply
+ returning true would lead to mv calling "rename(A,B)",
+ which would do nothing and return 0. */
+ if (same_link)
+ {
+ *return_now = true;
+ return ! x->move_mode;
+ }
+ }
+
+ return ! sn;
+ }
+
+ src_sb_link = src_sb;
+ dst_sb_link = dst_sb;
+ }
+ else
+ {
+ if (!same)
+ return true;
+
+ if (fstatat (dst_dirfd, dst_relname, &tmp_dst_sb,
+ AT_SYMLINK_NOFOLLOW) != 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;
+ }
+
+ /* FIXME: What about case insensitive file systems ? */
+ return ! same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname);
+ }
+
+#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_RELNAME 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
+
+ if (x->move_mode || x->unlink_dest_before_opening)
+ {
+ /* 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 unlinkat otherwise. */
+ if (S_ISLNK (dst_sb_link->st_mode))
+ return true;
+
+ /* It's not ok if they're distinct hard links to the same file as
+ this causes a race condition and we may lose data in this case. */
+ if (same_link
+ && 1 < dst_sb_link->st_nlink
+ && ! same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname))
+ return ! x->move_mode;
+ }
+
+ /* 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;
+ }
+ }
+
+ /* At this point, it is normally an error (data loss) to move a symlink
+ onto its referent, but in at least one narrow case, it is not:
+ In move mode, when
+ 1) src is a symlink,
+ 2) dest has a link count of 2 or more and
+ 3) dest and the referent of src are not the same directory entry,
+ then it's ok, since while we'll lose one of those hard links,
+ src will still point to a remaining link.
+ Note that technically, condition #3 obviates condition #2, but we
+ retain the 1 < st_nlink condition because that means fewer invocations
+ of the more expensive #3.
+
+ Given this,
+ $ touch f && ln f l && ln -s f s
+ $ ls -og f l s
+ -rw-------. 2 0 Jan 4 22:46 f
+ -rw-------. 2 0 Jan 4 22:46 l
+ lrwxrwxrwx. 1 1 Jan 4 22:46 s -> f
+ this must fail: mv s f
+ this must succeed: mv s l */
+ if (x->move_mode
+ && S_ISLNK (src_sb->st_mode)
+ && 1 < dst_sb_link->st_nlink)
+ {
+ char *abs_src = canonicalize_file_name (src_name);
+ if (abs_src)
+ {
+ bool result = ! same_nameat (AT_FDCWD, abs_src,
+ dst_dirfd, dst_relname);
+ free (abs_src);
+ return result;
+ }
+ }
+
+ /* It's ok to recreate a destination symlink. */
+ if (x->symbolic_link && S_ISLNK (dst_sb_link->st_mode))
+ return true;
+
+ 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 (fstatat (dst_dirfd, dst_relname, &tmp_dst_sb, 0) != 0)
+ return true;
+
+ if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb))
+ return true;
+
+ if (x->hard_link)
+ {
+ /* It's ok to attempt to hardlink the same file,
+ and return early if not replacing a symlink.
+ Note we need to return early to avoid a later
+ unlink() of DST (when SRC is a symlink). */
+ *return_now = ! S_ISLNK (dst_sb_link->st_mode);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Return whether DST_DIRFD+DST_RELNAME, with mode MODE,
+ is writable in the sense of 'mv'.
+ Always consider a symbolic link to be writable. */
+static bool
+writable_destination (int dst_dirfd, char const *dst_relname, mode_t mode)
+{
+ return (S_ISLNK (mode)
+ || can_write_any_file ()
+ || faccessat (dst_dirfd, dst_relname, W_OK, AT_EACCESS) == 0);
+}
+
+static bool
+overwrite_ok (struct cp_options const *x, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
+ struct stat const *dst_sb)
+{
+ if (! writable_destination (dst_dirfd, dst_relname, dst_sb->st_mode))
+ {
+ char perms[12]; /* "-rwxrwxrwx " ls-style modes. */
+ strmode (dst_sb->st_mode, perms);
+ perms[10] = '\0';
+ fprintf (stderr,
+ (x->move_mode || x->unlink_dest_before_opening
+ || x->unlink_dest_after_failed_open)
+ ? _("%s: replace %s, overriding mode %04lo (%s)? ")
+ : _("%s: unwritable %s (mode %04lo, %s); try anyway? "),
+ program_name, quoteaf (dst_name),
+ (unsigned long int) (dst_sb->st_mode & CHMOD_MODE_BITS),
+ &perms[1]);
+ }
+ else
+ {
+ fprintf (stderr, _("%s: overwrite %s? "),
+ program_name, quoteaf (dst_name));
+ }
+
+ return yesno ();
+}
+
+/* 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);
+ if (! x->dest_info)
+ xalloc_die ();
+}
+
+/* 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);
+ if (! x->src_info)
+ xalloc_die ();
+}
+
+/* When effecting a move (e.g., for mv(1)), and given the name DST_NAME
+ aka DST_DIRFD+DST_RELNAME
+ 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,
+ int dst_dirfd, char const *dst_relname,
+ 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
+ && ! writable_destination (dst_dirfd, dst_relname,
+ dst_sb->st_mode)))
+ && ! overwrite_ok (x, dst_name, dst_dirfd, dst_relname, dst_sb)));
+}
+
+/* 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", quoteaf_n (0, src), quoteaf_n (1, dst));
+ if (backup_dst_name)
+ printf (_(" (backup: %s)"), quoteaf (backup_dst_name));
+ putchar ('\n');
+}
+
+/* A wrapper around "setfscreatecon (NULL)" that exits upon failure. */
+static void
+restore_default_fscreatecon_or_die (void)
+{
+ if (setfscreatecon (NULL) != 0)
+ die (EXIT_FAILURE, errno,
+ _("failed to restore the default file creation context"));
+}
+
+/* Return a newly-allocated string that is like STR
+ except replace its suffix SUFFIX with NEWSUFFIX. */
+static char *
+subst_suffix (char const *str, char const *suffix, char const *newsuffix)
+{
+ idx_t prefixlen = suffix - str;
+ idx_t newsuffixsize = strlen (newsuffix) + 1;
+ char *r = ximalloc (prefixlen + newsuffixsize);
+ memcpy (r + prefixlen, newsuffix, newsuffixsize);
+ return memcpy (r, str, prefixlen);
+}
+
+/* Create a hard link to SRC_NAME aka SRC_DIRFD+SRC_RELNAME;
+ the new link is at DST_NAME aka DST_DIRFD+DST_RELNAME.
+ A null SRC_NAME stands for the file whose name is like DST_NAME
+ except with DST_RELNAME replaced with SRC_RELNAME.
+ Honor the REPLACE, VERBOSE and DEREFERENCE settings.
+ Return true upon success. Otherwise, diagnose the
+ failure and return false. If SRC_NAME is a symbolic link, then it will not
+ be followed unless DEREFERENCE is true.
+ If the system doesn't support hard links to symbolic links, then DST_NAME
+ will be created as a symbolic link to SRC_NAME. */
+static bool
+create_hard_link (char const *src_name, int src_dirfd, char const *src_relname,
+ char const *dst_name, int dst_dirfd, char const *dst_relname,
+ bool replace, bool verbose, bool dereference)
+{
+ int err = force_linkat (src_dirfd, src_relname, dst_dirfd, dst_relname,
+ dereference ? AT_SYMLINK_FOLLOW : 0,
+ replace, -1);
+ if (0 < err)
+ {
+
+ char *a_src_name = NULL;
+ if (!src_name)
+ src_name = a_src_name = subst_suffix (dst_name, dst_relname,
+ src_relname);
+ error (0, err, _("cannot create hard link %s to %s"),
+ quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
+ free (a_src_name);
+ return false;
+ }
+ if (err < 0 && verbose)
+ printf (_("removed %s\n"), quoteaf (dst_name));
+ return true;
+}
+
+/* Return true if the current file should be (tried to be) dereferenced:
+ either for DEREF_ALWAYS or for DEREF_COMMAND_LINE_ARGUMENTS in the case
+ where the current file is a COMMAND_LINE_ARG; otherwise return false. */
+ATTRIBUTE_PURE
+static inline bool
+should_dereference (const struct cp_options *x, bool command_line_arg)
+{
+ return x->dereference == DEREF_ALWAYS
+ || (x->dereference == DEREF_COMMAND_LINE_ARGUMENTS
+ && command_line_arg);
+}
+
+/* Return true if the source file with basename SRCBASE and status SRC_ST
+ is likely to be the simple backup file for DST_DIRFD+DST_RELNAME. */
+static bool
+source_is_dst_backup (char const *srcbase, struct stat const *src_st,
+ int dst_dirfd, char const *dst_relname)
+{
+ size_t srcbaselen = strlen (srcbase);
+ char const *dstbase = last_component (dst_relname);
+ size_t dstbaselen = strlen (dstbase);
+ size_t suffixlen = strlen (simple_backup_suffix);
+ if (! (srcbaselen == dstbaselen + suffixlen
+ && memcmp (srcbase, dstbase, dstbaselen) == 0
+ && STREQ (srcbase + dstbaselen, simple_backup_suffix)))
+ return false;
+ char *dst_back = subst_suffix (dst_relname,
+ dst_relname + strlen (dst_relname),
+ simple_backup_suffix);
+ struct stat dst_back_sb;
+ int dst_back_status = fstatat (dst_dirfd, dst_back, &dst_back_sb, 0);
+ free (dst_back);
+ return dst_back_status == 0 && SAME_INODE (*src_st, dst_back_sb);
+}
+
+/* Copy the file SRC_NAME to the file DST_NAME aka DST_DIRFD+DST_RELNAME.
+ If NONEXISTENT_DST is positive, DST_NAME does not exist even as a
+ dangling symlink; if negative, it does not exist except possibly
+ as a dangling symlink; if zero, its existence status is unknown.
+ A non-null PARENT describes the parent directory.
+ ANCESTORS points to a linked, null terminated list of
+ devices and inodes of parent directories of SRC_NAME.
+ X summarizes the command-line options.
+ COMMAND_LINE_ARG means SRC_NAME was specified on the command line.
+ FIRST_DIR_CREATED_PER_COMMAND_LINE_ARG is both input and output.
+ Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the
+ same as) DST_NAME; otherwise, clear it.
+ If X->move_mode, set *RENAME_SUCCEEDED according to whether
+ the source was simply renamed to the destination.
+ Return true if successful. */
+static bool
+copy_internal (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
+ int nonexistent_dst,
+ struct stat const *parent,
+ struct dir_list *ancestors,
+ const struct cp_options *x,
+ bool command_line_arg,
+ bool *first_dir_created_per_command_line_arg,
+ bool *copy_into_self,
+ bool *rename_succeeded)
+{
+ struct stat src_sb;
+ struct stat dst_sb;
+ mode_t src_mode IF_LINT ( = 0);
+ 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 delayed_ok;
+ bool copied_as_regular = false;
+ bool dest_is_symlink = false;
+ bool have_dst_lstat = false;
+
+ /* Whether the destination is (or was) known to be new, updated as
+ more info comes in. This may become true if the destination is a
+ dangling symlink, in contexts where dangling symlinks should be
+ treated the same as nonexistent files. */
+ bool new_dst = 0 < nonexistent_dst;
+
+ *copy_into_self = false;
+
+ int rename_errno = x->rename_errno;
+ if (x->move_mode)
+ {
+ if (rename_errno < 0)
+ rename_errno = (renameatu (AT_FDCWD, src_name, dst_dirfd, dst_relname,
+ RENAME_NOREPLACE)
+ ? errno : 0);
+ nonexistent_dst = *rename_succeeded = new_dst = rename_errno == 0;
+ }
+
+ if (rename_errno == 0
+ ? !x->last_file
+ : rename_errno != EEXIST || x->interactive != I_ALWAYS_NO)
+ {
+ char const *name = rename_errno == 0 ? dst_name : src_name;
+ int dirfd = rename_errno == 0 ? dst_dirfd : AT_FDCWD;
+ char const *relname = rename_errno == 0 ? dst_relname : src_name;
+ int fstatat_flags
+ = x->dereference == DEREF_NEVER ? AT_SYMLINK_NOFOLLOW : 0;
+ if (follow_fstatat (dirfd, relname, &src_sb, fstatat_flags) != 0)
+ {
+ error (0, errno, _("cannot stat %s"), quoteaf (name));
+ return false;
+ }
+
+ src_mode = src_sb.st_mode;
+
+ if (S_ISDIR (src_mode) && !x->recursive)
+ {
+ error (0, 0, ! x->install_mode /* cp */
+ ? _("-r not specified; omitting directory %s")
+ : _("omitting directory %s"),
+ quoteaf (src_name));
+ return false;
+ }
+ }
+ else
+ {
+#if defined lint && (defined __clang__ || defined __COVERITY__)
+ assert (x->move_mode);
+ memset (&src_sb, 0, sizeof src_sb);
+#endif
+ }
+
+ /* 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 && x->src_info)
+ {
+ if ( ! S_ISDIR (src_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"),
+ quoteaf (src_name));
+ return true;
+ }
+
+ record_file (x->src_info, src_name, &src_sb);
+ }
+
+ bool dereference = should_dereference (x, command_line_arg);
+
+ if (nonexistent_dst <= 0)
+ {
+ if (! (rename_errno == EEXIST && x->interactive == I_ALWAYS_NO))
+ {
+ /* Regular files can be created by writing through symbolic
+ links, but other files cannot. So use stat on the
+ destination when copying a regular file, and lstat otherwise.
+ However, if we intend to unlink or remove the destination
+ first, use lstat, since a copy won't actually be made to the
+ destination in that case. */
+ bool use_lstat
+ = ((! S_ISREG (src_mode)
+ && (! x->copy_as_regular
+ || S_ISDIR (src_mode) || S_ISLNK (src_mode)))
+ || x->move_mode || x->symbolic_link || x->hard_link
+ || x->backup_type != no_backups
+ || x->unlink_dest_before_opening);
+ int fstatat_flags = use_lstat ? AT_SYMLINK_NOFOLLOW : 0;
+ if (!use_lstat && nonexistent_dst < 0)
+ new_dst = true;
+ else if (follow_fstatat (dst_dirfd, dst_relname, &dst_sb,
+ fstatat_flags)
+ == 0)
+ {
+ have_dst_lstat = use_lstat;
+ rename_errno = EEXIST;
+ }
+ else
+ {
+ if (errno == ELOOP && x->unlink_dest_after_failed_open)
+ /* leave new_dst=false so we unlink later. */;
+ else if (errno != ENOENT)
+ {
+ error (0, errno, _("cannot stat %s"), quoteaf (dst_name));
+ return false;
+ }
+ else
+ new_dst = true;
+ }
+ }
+
+ if (rename_errno == EEXIST)
+ {
+ bool return_now = false;
+
+ if (x->interactive != I_ALWAYS_NO
+ && ! same_file_ok (src_name, &src_sb, dst_dirfd, dst_relname,
+ &dst_sb, x, &return_now))
+ {
+ error (0, 0, _("%s and %s are the same file"),
+ quoteaf_n (0, src_name), quoteaf_n (1, dst_name));
+ return false;
+ }
+
+ if (x->update && !S_ISDIR (src_mode))
+ {
+ /* When preserving timestamps (but not moving within a file
+ system), don't worry if the destination timestamp is
+ less than the source merely because of timestamp
+ truncation. */
+ int options = ((x->preserve_timestamps
+ && ! (x->move_mode
+ && dst_sb.st_dev == src_sb.st_dev))
+ ? UTIMECMP_TRUNCATE_SOURCE
+ : 0);
+
+ if (0 <= utimecmpat (dst_dirfd, dst_relname, &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;
+
+ /* However, we still must record that we've processed
+ this src/dest pair, in case this source file is
+ hard-linked to another one. In that case, we'll use
+ the mapping information to link the corresponding
+ destination names. */
+ earlier_file = remember_copied (dst_relname, src_sb.st_ino,
+ src_sb.st_dev);
+ if (earlier_file)
+ {
+ /* Note we currently replace DST_NAME unconditionally,
+ even if it was a newer separate file. */
+ if (! create_hard_link (NULL, dst_dirfd, earlier_file,
+ dst_name, dst_dirfd, dst_relname,
+ true,
+ x->verbose, dereference))
+ {
+ goto un_backup;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /* 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 --no-clobber option.
+
+ cp and mv treat -i and -f differently. */
+ if (x->move_mode)
+ {
+ if (abandon_move (x, dst_name, dst_dirfd, dst_relname, &dst_sb))
+ {
+ /* Pretend the rename succeeded, so the caller (mv)
+ doesn't end up removing the source file. */
+ if (rename_succeeded)
+ *rename_succeeded = true;
+ return true;
+ }
+ }
+ else
+ {
+ if (! S_ISDIR (src_mode)
+ && (x->interactive == I_ALWAYS_NO
+ || (x->interactive == I_ASK_USER
+ && ! overwrite_ok (x, dst_name, dst_dirfd,
+ dst_relname, &dst_sb))))
+ 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"),
+ quoteaf_n (0, dst_name), quoteaf_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_relname, &dst_sb))
+ {
+ error (0, 0,
+ _("will not overwrite just-created %s with %s"),
+ quoteaf_n (0, dst_name), quoteaf_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"),
+ quoteaf (dst_name));
+ return false;
+ }
+ }
+ }
+
+ 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"),
+ quotef_n (0, src_name), quotef_n (0, dst_name));
+ return false;
+ }
+ }
+
+ char const *srcbase;
+ 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 (srcbase = 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)))
+ {
+ /* Fail if creating the backup file would likely destroy
+ the source file. Otherwise, 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~. */
+ if (x->backup_type != numbered_backups
+ && source_is_dst_backup (srcbase, &src_sb,
+ dst_dirfd, dst_relname))
+ {
+ char const *fmt;
+ fmt = (x->move_mode
+ ? _("backing up %s might destroy source; %s not moved")
+ : _("backing up %s might destroy source; %s not copied"));
+ error (0, 0, fmt,
+ quoteaf_n (0, dst_name),
+ quoteaf_n (1, src_name));
+ return false;
+ }
+
+ char *tmp_backup = backup_file_rename (dst_dirfd, dst_relname,
+ x->backup_type);
+
+ /* 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. */
+ if (tmp_backup)
+ {
+ idx_t dirlen = dst_relname - dst_name;
+ idx_t backupsize = strlen (tmp_backup) + 1;
+ dst_backup = alloca (dirlen + backupsize);
+ memcpy (mempcpy (dst_backup, dst_name, dirlen),
+ tmp_backup, backupsize);
+ free (tmp_backup);
+ }
+ else if (errno != ENOENT)
+ {
+ error (0, errno, _("cannot backup %s"), quoteaf (dst_name));
+ return false;
+ }
+ new_dst = true;
+ }
+ else if (! S_ISDIR (dst_sb.st_mode)
+ /* Never unlink dst_name when in move mode. */
+ && ! x->move_mode
+ && (x->unlink_dest_before_opening
+ || (x->data_copy_required
+ && ((x->preserve_links && 1 < dst_sb.st_nlink)
+ || (x->dereference == DEREF_NEVER
+ && ! S_ISREG (src_sb.st_mode))))
+ ))
+ {
+ if (unlinkat (dst_dirfd, dst_relname, 0) != 0 && errno != ENOENT)
+ {
+ error (0, errno, _("cannot remove %s"), quoteaf (dst_name));
+ return false;
+ }
+ new_dst = true;
+ if (x->verbose)
+ printf (_("removed %s\n"), quoteaf (dst_name));
+ }
+ }
+ }
+
+ /* Ensure we don't try to copy through a symlink that was
+ created by a prior call to this function. */
+ if (command_line_arg
+ && x->dest_info
+ && ! x->move_mode
+ && x->backup_type == no_backups)
+ {
+ bool lstat_ok = true;
+ struct stat tmp_buf;
+ struct stat *dst_lstat_sb;
+
+ /* If we did not follow symlinks above, good: use that data.
+ Otherwise, use AT_SYMLINK_NOFOLLOW, in case dst_name is a symlink. */
+ if (have_dst_lstat)
+ dst_lstat_sb = &dst_sb;
+ else
+ {
+ if (fstatat (dst_dirfd, dst_relname, &tmp_buf,
+ AT_SYMLINK_NOFOLLOW) == 0)
+ dst_lstat_sb = &tmp_buf;
+ else
+ lstat_ok = false;
+ }
+
+ /* Never copy through a symlink we've just created. */
+ if (lstat_ok
+ && S_ISLNK (dst_lstat_sb->st_mode)
+ && seen_file (x->dest_info, dst_relname, dst_lstat_sb))
+ {
+ error (0, 0,
+ _("will not copy %s through just-created symlink %s"),
+ quoteaf_n (0, src_name), quoteaf_n (1, dst_name));
+ return false;
+ }
+ }
+
+ /* 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. Also don't announce yet when moving
+ so we can distinguish renames versus copies. */
+ if (x->verbose && !x->move_mode && !S_ISDIR (src_mode))
+ emit_verbose (src_name, dst_name, dst_backup);
+
+ /* 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.
+
+ When using the --link (-l) option, there is no need to take special
+ measures, because (barring race conditions) files that are hard-linked
+ in the source tree will also be hard-linked 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, with --recursive, record dev/ino of each command-line directory.
+ We'll use that info to detect this problem: cp -R dir dir. */
+
+ if (rename_errno == 0)
+ earlier_file = NULL;
+ else if (x->recursive && S_ISDIR (src_mode))
+ {
+ if (command_line_arg)
+ earlier_file = remember_copied (dst_relname,
+ src_sb.st_ino, src_sb.st_dev);
+ else
+ earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev);
+ }
+ else 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
+ && !x->hard_link
+ && (1 < src_sb.st_nlink
+ || (command_line_arg
+ && x->dereference == DEREF_COMMAND_LINE_ARGUMENTS)
+ || x->dereference == DEREF_ALWAYS))
+ {
+ earlier_file = remember_copied (dst_relname,
+ 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_nameat (AT_FDCWD, src_name, dst_dirfd, earlier_file))
+ {
+ error (0, 0, _("cannot copy a directory, %s, into itself, %s"),
+ quoteaf_n (0, top_level_src_name),
+ quoteaf_n (1, top_level_dst_name));
+ *copy_into_self = true;
+ goto un_backup;
+ }
+ else if (same_nameat (dst_dirfd, dst_relname,
+ dst_dirfd, earlier_file))
+ {
+ error (0, 0, _("warning: source directory %s "
+ "specified more than once"),
+ quoteaf (top_level_src_name));
+ /* In move mode, if a previous rename succeeded, then
+ we won't be in this path as the source is missing. If the
+ rename previously failed, then that has been handled, so
+ pretend this attempt succeeded so the source isn't removed. */
+ if (x->move_mode && rename_succeeded)
+ *rename_succeeded = true;
+ /* We only do backups in move mode, and for non directories.
+ So just ignore this repeated entry. */
+ return true;
+ }
+ else if (x->dereference == DEREF_ALWAYS
+ || (command_line_arg
+ && x->dereference == DEREF_COMMAND_LINE_ARGUMENTS))
+ {
+ /* 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
+ {
+ char *earlier = subst_suffix (dst_name, dst_relname,
+ earlier_file);
+ error (0, 0, _("will not create hard link %s to directory %s"),
+ quoteaf_n (0, dst_name), quoteaf_n (1, earlier));
+ free (earlier);
+ goto un_backup;
+ }
+ }
+ else
+ {
+ if (! create_hard_link (NULL, dst_dirfd, earlier_file,
+ dst_name, dst_dirfd, dst_relname,
+ true, x->verbose, dereference))
+ goto un_backup;
+
+ return true;
+ }
+ }
+
+ if (x->move_mode)
+ {
+ if (rename_errno == EEXIST)
+ rename_errno = ((renameat (AT_FDCWD, src_name, dst_dirfd, dst_relname)
+ == 0)
+ ? 0 : errno);
+
+ if (rename_errno == 0)
+ {
+ if (x->verbose)
+ {
+ printf (_("renamed "));
+ emit_verbose (src_name, dst_name, dst_backup);
+ }
+
+ if (x->set_security_context)
+ {
+ /* -Z failures are only warnings currently. */
+ (void) set_file_security_ctx (dst_name, true, x);
+ }
+
+ if (rename_succeeded)
+ *rename_succeeded = true;
+
+ if (command_line_arg && !x->last_file)
+ {
+ /* 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_relname, &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 (rename_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"),
+ quoteaf_n (0, top_level_src_name),
+ quoteaf_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 partition 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 (rename_errno != EXDEV)
+ {
+ /* There are many ways this can happen due to a race condition.
+ When something happens between the initial follow_fstatat 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, rename_errno,
+ _("cannot move %s to %s"),
+ quoteaf_n (0, src_name), quoteaf_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. Note both src and dst must both be directories
+ or not, and this is enforced above. Therefore we check the src_mode
+ and operate on dst_name here as a tighter constraint and also because
+ src_mode is readily available here. */
+ if ((unlinkat (dst_dirfd, dst_relname,
+ S_ISDIR (src_mode) ? AT_REMOVEDIR : 0)
+ != 0)
+ && errno != ENOENT)
+ {
+ error (0, errno,
+ _("inter-device move failed: %s to %s; unable to remove target"),
+ quoteaf_n (0, src_name), quoteaf_n (1, dst_name));
+ forget_created (src_sb.st_ino, src_sb.st_dev);
+ return false;
+ }
+
+ if (x->verbose && !S_ISDIR (src_mode))
+ {
+ printf (_("copied "));
+ emit_verbose (src_name, dst_name, dst_backup);
+ }
+ 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;
+
+ /* If required, set the default security context for new files.
+ Also for existing files this is used as a reference
+ when copying the context with --preserve=context.
+ FIXME: Do we need to consider dst_mode_bits here? */
+ if (! set_process_security_ctx (src_name, dst_name, src_mode, new_dst, x))
+ return false;
+
+ 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 file system. */
+
+ if (is_ancestor (&src_sb, ancestors))
+ {
+ error (0, 0, _("cannot copy cyclic symbolic link %s"),
+ quoteaf (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. */
+ mode_t mode = dst_mode_bits & ~omitted_permissions;
+ if (mkdirat (dst_dirfd, dst_relname, mode) != 0)
+ {
+ error (0, errno, _("cannot create directory %s"),
+ quoteaf (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 (fstatat (dst_dirfd, dst_relname, &dst_sb,
+ AT_SYMLINK_NOFOLLOW) != 0)
+ {
+ error (0, errno, _("cannot stat %s"), quoteaf (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 (lchmodat (dst_dirfd, dst_relname, dst_mode | S_IRWXU) != 0)
+ {
+ error (0, errno, _("setting permissions for %s"),
+ quoteaf (dst_name));
+ goto un_backup;
+ }
+ }
+
+ /* Record the created directory's inode and device numbers into
+ the search structure, so that we can avoid copying it again.
+ Do this only for the first directory that is created for each
+ source command line argument. */
+ if (!*first_dir_created_per_command_line_arg)
+ {
+ remember_copied (dst_relname, dst_sb.st_ino, dst_sb.st_dev);
+ *first_dir_created_per_command_line_arg = true;
+ }
+
+ if (x->verbose)
+ {
+ if (x->move_mode)
+ printf (_("created directory %s\n"), quoteaf (dst_name));
+ else
+ emit_verbose (src_name, dst_name, NULL);
+ }
+ }
+ else
+ {
+ omitted_permissions = 0;
+
+ /* For directories, the process global context could be reset for
+ descendents, so use it to set the context for existing dirs here.
+ This will also give earlier indication of failure to set ctx. */
+ if (x->set_security_context || x->preserve_security_context)
+ if (! set_file_security_ctx (dst_name, false, x))
+ {
+ if (x->require_preserve_context)
+ goto un_backup;
+ }
+ }
+
+ /* Decide whether to copy the contents of the directory. */
+ if (x->one_file_system && parent && parent->st_dev != 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, dst_dirfd, dst_relname,
+ new_dst, &src_sb, dir, x,
+ first_dir_created_per_command_line_arg,
+ copy_into_self);
+ }
+ }
+ else if (x->symbolic_link)
+ {
+ dest_is_symlink = true;
+ 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_relname);
+
+ in_current_dir = ((dst_dirfd == AT_FDCWD && 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
+ || (fstatat (dst_dirfd, dst_parent, &dst_parent_sb,
+ 0) != 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"),
+ quotef (dst_name));
+ goto un_backup;
+ }
+ }
+
+ int err = force_symlinkat (src_name, dst_dirfd, dst_relname,
+ x->unlink_dest_after_failed_open, -1);
+ if (0 < err)
+ {
+ error (0, err, _("cannot create symbolic link %s to %s"),
+ quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
+ goto un_backup;
+ }
+ }
+
+ /* POSIX 2008 states that it is implementation-defined whether
+ link() on a symlink creates a hard-link to the symlink, or only
+ to the referent (effectively dereferencing the symlink) (POSIX
+ 2001 required the latter behavior, although many systems provided
+ the former). Yet cp, invoked with '--link --no-dereference',
+ should not follow the link. 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.
+
+ Note gnulib's linkat module, guarantees that the symlink is not
+ dereferenced. However its emulation currently doesn't maintain
+ timestamps or ownership so we only call it when we know the
+ emulation will not be needed. */
+ else if (x->hard_link
+ && !(! CAN_HARDLINK_SYMLINKS && S_ISLNK (src_mode)
+ && x->dereference == DEREF_NEVER))
+ {
+ bool replace = (x->unlink_dest_after_failed_open
+ || x->interactive == I_ASK_USER);
+ if (! create_hard_link (src_name, AT_FDCWD, src_name,
+ dst_name, dst_dirfd, dst_relname,
+ replace, false, dereference))
+ 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.
+
+ This call uses DST_MODE_BITS, not SRC_MODE. These are
+ normally the same, and the exception (where x->set_mode) is
+ used only by 'install', which POSIX does not specify and
+ where DST_MODE_BITS is what's wanted. */
+ if (! copy_reg (src_name, dst_name, dst_dirfd, dst_relname,
+ x, dst_mode_bits & S_IRWXUGO,
+ omitted_permissions, &new_dst, &src_sb))
+ goto un_backup;
+ }
+ else if (S_ISFIFO (src_mode))
+ {
+ /* Use mknodat, rather than mkfifoat, because the former preserves
+ the special mode bits of a fifo on Solaris 10, while mkfifoat
+ does not. But fall back on mkfifoat, because on some BSD systems,
+ mknodat always fails when asked to create a FIFO. */
+ mode_t mode = src_mode & ~omitted_permissions;
+ if (mknodat (dst_dirfd, dst_relname, mode, 0) != 0)
+ if (mkfifoat (dst_dirfd, dst_relname, mode & ~S_IFIFO) != 0)
+ {
+ error (0, errno, _("cannot create fifo %s"), quoteaf (dst_name));
+ goto un_backup;
+ }
+ }
+ else if (S_ISBLK (src_mode) || S_ISCHR (src_mode) || S_ISSOCK (src_mode))
+ {
+ mode_t mode = src_mode & ~omitted_permissions;
+ if (mknodat (dst_dirfd, dst_relname, mode, src_sb.st_rdev) != 0)
+ {
+ error (0, errno, _("cannot create special file %s"),
+ quoteaf (dst_name));
+ goto un_backup;
+ }
+ }
+ else if (S_ISLNK (src_mode))
+ {
+ char *src_link_val = areadlink_with_size (src_name, src_sb.st_size);
+ dest_is_symlink = true;
+ if (src_link_val == NULL)
+ {
+ error (0, errno, _("cannot read symbolic link %s"),
+ quoteaf (src_name));
+ goto un_backup;
+ }
+
+ int symlink_err = force_symlinkat (src_link_val, dst_dirfd, dst_relname,
+ x->unlink_dest_after_failed_open, -1);
+ if (0 < symlink_err && 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 timestamps. */
+ char *dest_link_val =
+ areadlinkat_with_size (dst_dirfd, dst_relname, dst_sb.st_size);
+ if (dest_link_val)
+ {
+ if (STREQ (dest_link_val, src_link_val))
+ symlink_err = 0;
+ free (dest_link_val);
+ }
+ }
+ free (src_link_val);
+ if (0 < symlink_err)
+ {
+ error (0, symlink_err, _("cannot create symbolic link %s"),
+ quoteaf (dst_name));
+ goto un_backup;
+ }
+
+ if (x->preserve_security_context)
+ restore_default_fscreatecon_or_die ();
+
+ if (x->preserve_ownership)
+ {
+ /* Preserve the owner and group of the just-'copied'
+ symbolic link, if possible. */
+ if (HAVE_LCHOWN
+ && (lchownat (dst_dirfd, dst_relname,
+ src_sb.st_uid, src_sb.st_gid)
+ != 0)
+ && ! chown_failure_ok (x))
+ {
+ error (0, errno, _("failed to preserve ownership for %s"),
+ dst_name);
+ if (x->require_preserve)
+ 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. */
+ }
+ }
+ }
+ else
+ {
+ error (0, 0, _("%s has unknown file type"), quoteaf (src_name));
+ goto un_backup;
+ }
+
+ /* With -Z or --preserve=context, set the context for existing files.
+ Note this is done already for copy_reg() for reasons described therein. */
+ if (!new_dst && !x->copy_as_regular && !S_ISDIR (src_mode)
+ && (x->set_security_context || x->preserve_security_context))
+ {
+ if (! set_file_security_ctx (dst_name, false, x))
+ {
+ if (x->require_preserve_context)
+ goto un_backup;
+ }
+ }
+
+ if (command_line_arg && x->dest_info)
+ {
+ /* Now that the destination file is very likely to exist,
+ add its info to the set. */
+ struct stat sb;
+ if (fstatat (dst_dirfd, dst_relname, &sb, AT_SYMLINK_NOFOLLOW) == 0)
+ record_file (x->dest_info, dst_relname, &sb);
+ }
+
+ /* If we've just created a hard-link due to cp's --link option,
+ we're done. */
+ if (x->hard_link && ! S_ISDIR (src_mode)
+ && !(! CAN_HARDLINK_SYMLINKS && S_ISLNK (src_mode)
+ && x->dereference == DEREF_NEVER))
+ return delayed_ok;
+
+ 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);
+
+ int utimensat_flags = dest_is_symlink ? AT_SYMLINK_NOFOLLOW : 0;
+ if (utimensat (dst_dirfd, dst_relname, timespec, utimensat_flags) != 0)
+ {
+ error (0, errno, _("preserving times for %s"), quoteaf (dst_name));
+ if (x->require_preserve)
+ return false;
+ }
+ }
+
+ /* Avoid calling chown if we know it's not necessary. */
+ if (!dest_is_symlink && x->preserve_ownership
+ && (new_dst || !SAME_OWNER_AND_GROUP (src_sb, dst_sb)))
+ {
+ switch (set_owner (x, dst_name, dst_dirfd, dst_relname, -1,
+ &src_sb, new_dst, &dst_sb))
+ {
+ case -1:
+ return false;
+
+ case 0:
+ src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX);
+ break;
+ }
+ }
+
+ /* Set xattrs after ownership as changing owners will clear capabilities. */
+ if (x->preserve_xattr && ! copy_attr (src_name, -1, dst_name, -1, x)
+ && x->require_preserve_xattr)
+ return false;
+
+ /* The operations beyond this point may dereference a symlink. */
+ if (dest_is_symlink)
+ return delayed_ok;
+
+ 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 (x->explicit_no_preserve_mode && new_dst)
+ {
+ int default_permissions = S_ISDIR (src_mode) || S_ISSOCK (src_mode)
+ ? S_IRWXUGO : MODE_RW_UGO;
+ if (set_acl (dst_name, -1, default_permissions & ~cached_umask ()) != 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 && fstatat (dst_dirfd, dst_relname, &dst_sb,
+ AT_SYMLINK_NOFOLLOW) != 0)
+ {
+ error (0, errno, _("cannot stat %s"), quoteaf (dst_name));
+ return false;
+ }
+ dst_mode = dst_sb.st_mode;
+ if (omitted_permissions & ~dst_mode)
+ restore_dst_mode = true;
+ }
+ }
+
+ if (restore_dst_mode)
+ {
+ if (lchmodat (dst_dirfd, dst_relname, dst_mode | omitted_permissions)
+ != 0)
+ {
+ error (0, errno, _("preserving permissions for %s"),
+ quoteaf (dst_name));
+ if (x->require_preserve)
+ return false;
+ }
+ }
+ }
+
+ return delayed_ok;
+
+un_backup:
+
+ if (x->preserve_security_context)
+ restore_default_fscreatecon_or_die ();
+
+ /* 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)
+ {
+ char const *dst_relbackup = &dst_backup[dst_relname - dst_name];
+ if (renameat (dst_dirfd, dst_relbackup, dst_dirfd, dst_relname) != 0)
+ error (0, errno, _("cannot un-backup %s"), quoteaf (dst_name));
+ else
+ {
+ if (x->verbose)
+ printf (_("%s -> %s (unbackup)\n"),
+ quoteaf_n (0, dst_backup), quoteaf_n (1, dst_name));
+ }
+ }
+ return false;
+}
+
+ATTRIBUTE_PURE
+static bool
+valid_options (const struct cp_options *co)
+{
+ assert (VALID_BACKUP_TYPE (co->backup_type));
+ assert (VALID_SPARSE_MODE (co->sparse_mode));
+ assert (VALID_REFLINK_MODE (co->reflink_mode));
+ assert (!(co->hard_link && co->symbolic_link));
+ assert (!
+ (co->reflink_mode == REFLINK_ALWAYS
+ && co->sparse_mode != SPARSE_AUTO));
+ return true;
+}
+
+/* Copy the file SRC_NAME to the file DST_NAME aka DST_DIRFD+DST_RELNAME.
+ If NONEXISTENT_DST is positive, DST_NAME does not exist even as a
+ dangling symlink; if negative, it does not exist except possibly
+ as a dangling symlink; if zero, its existence status is unknown.
+ OPTIONS summarizes the command-line options.
+ Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the
+ same as) DST_NAME; otherwise, set clear it.
+ If X->move_mode, set *RENAME_SUCCEEDED according to whether
+ the source was simply renamed to the destination.
+ Return true if successful. */
+
+extern bool
+copy (char const *src_name, char const *dst_name,
+ int dst_dirfd, char const *dst_relname,
+ int 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;
+
+ bool first_dir_created_per_command_line_arg = false;
+ return copy_internal (src_name, dst_name, dst_dirfd, dst_relname,
+ nonexistent_dst, NULL, NULL,
+ options, true,
+ &first_dir_created_per_command_line_arg,
+ copy_into_self, rename_succeeded);
+}
+
+/* Set *X to the default options for a value of type struct cp_options. */
+
+extern void
+cp_options_default (struct cp_options *x)
+{
+ memset (x, 0, sizeof *x);
+#ifdef PRIV_FILE_CHOWN
+ {
+ priv_set_t *pset = priv_allocset ();
+ if (!pset)
+ xalloc_die ();
+ if (getppriv (PRIV_EFFECTIVE, pset) == 0)
+ {
+ x->chown_privileges = priv_ismember (pset, PRIV_FILE_CHOWN);
+ x->owner_privileges = priv_ismember (pset, PRIV_FILE_OWNER);
+ }
+ priv_freeset (pset);
+ }
+#else
+ x->chown_privileges = x->owner_privileges = (geteuid () == ROOT_UID);
+#endif
+ x->rename_errno = -1;
+}
+
+/* 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);
+}
+
+/* Similarly, return true if it's OK for chmod and similar operations
+ to fail, where errno is the error number that chmod failed with and
+ X is the copying option set. */
+
+static bool
+owner_failure_ok (struct cp_options const *x)
+{
+ return ((errno == EPERM || errno == EINVAL) && !x->owner_privileges);
+}
+
+/* Return the user's umask, caching the result.
+
+ FIXME: If the destination's parent directory has has a default ACL,
+ some operating systems (e.g., GNU/Linux's "POSIX" ACLs) use that
+ ACL's mask rather than the process umask. Currently, the callers
+ of cached_umask incorrectly assume that this situation cannot occur. */
+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;
+}