| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| #include <config.h> |
| #include <stdio.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 "assure.h" |
| #include "backupfile.h" |
| #include "canonicalize.h" |
| #include "copy.h" |
| #include "cp-hash.h" |
| #include "fcntl--.h" |
| #include "file-set.h" |
| #include "filemode.h" |
| #include "filenamecat.h" |
| #include "force-link.h" |
| #include "hash.h" |
| #include "hashcode-file.h" |
| #include "ignore-value.h" |
| #include "issymlink.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> |
| #endif |
|
|
| #if HAVE_LINUX_FALLOC_H |
| # include <linux/falloc.h> |
| #endif |
|
|
| |
| #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 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)) |
|
|
| |
| |
| #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 st_ino; |
| dev_t st_dev; |
| }; |
|
|
| |
| #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); |
|
|
| |
| |
| static char const *top_level_src_name; |
| static char const *top_level_dst_name; |
|
|
| |
| static struct copy_debug copy_debug; |
|
|
| static const char* |
| copy_debug_string (enum copy_debug_val debug_val) |
| { |
| switch (debug_val) |
| { |
| case COPY_DEBUG_NO: return "no"; |
| case COPY_DEBUG_YES: return "yes"; |
| case COPY_DEBUG_AVOIDED: return "avoided"; |
| case COPY_DEBUG_UNSUPPORTED: return "unsupported"; |
| case COPY_DEBUG_UNKNOWN: return "unknown"; |
|
|
| case COPY_DEBUG_EXTERNAL: |
| case COPY_DEBUG_EXTERNAL_INTERNAL: |
| default: unreachable (); |
| } |
| } |
|
|
| static const char* |
| copy_debug_sparse_string (enum copy_debug_val debug_val) |
| { |
| switch (debug_val) |
| { |
| case COPY_DEBUG_NO: return "no"; |
| case COPY_DEBUG_YES: return "zeros"; |
| case COPY_DEBUG_EXTERNAL: return "SEEK_HOLE"; |
| case COPY_DEBUG_EXTERNAL_INTERNAL: return "SEEK_HOLE + zeros"; |
| case COPY_DEBUG_UNKNOWN: return "unknown"; |
|
|
| case COPY_DEBUG_AVOIDED: |
| case COPY_DEBUG_UNSUPPORTED: |
| default: unreachable (); |
| } |
| } |
|
|
| |
| static void |
| emit_debug (const struct cp_options *x) |
| { |
| if (! x->hard_link && ! x->symbolic_link && x->data_copy_required) |
| printf ("copy offload: %s, reflink: %s, sparse detection: %s\n", |
| copy_debug_string (copy_debug.offload), |
| copy_debug_string (copy_debug.reflink), |
| copy_debug_sparse_string (copy_debug.sparse_detection)); |
| } |
|
|
| #ifndef DEV_FD_MIGHT_BE_CHR |
| # define DEV_FD_MIGHT_BE_CHR false |
| #endif |
|
|
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
|
|
| static bool |
| is_terminal_error (int err) |
| { |
| return err == EIO || err == ENOMEM || err == ENOSPC || err == EDQUOT; |
| } |
|
|
| |
| |
| 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 |
| } |
|
|
| |
| |
| |
| |
|
|
| ATTRIBUTE_PURE |
| static bool |
| is_ancestor (const struct stat *sb, const struct dir_list *ancestors) |
| { |
| while (ancestors != 0) |
| { |
| if (PSAME_INODE (ancestors, sb)) |
| 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; |
|
|
| |
| 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; |
|
|
| |
| 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) |
| { |
| } |
|
|
| |
| |
| |
| |
| |
| static int |
| check_selinux_attr (char const *name, struct error_context *ctx) |
| { |
| return STRNCMP_LIT (name, "security.selinux") |
| && attr_copy_check_permissions (name, ctx); |
| } |
|
|
| |
| |
|
|
| 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 : nullptr); |
|
|
| # if 4 < __GNUC__ + (8 <= __GNUC_MINOR__) |
| |
| # 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 |
| }) |
| : nullptr); |
| # 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 |
|
|
| 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 |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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 == nullptr) |
| { |
| |
| |
| error (0, errno, _("cannot access %s"), quoteaf (src_name_in)); |
| return false; |
| } |
|
|
| |
| |
| 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, nullptr); |
| char *dst_name = file_name_concat (dst_name_in, namep, nullptr); |
| 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 (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; |
| } |
|
|
| |
| |
| |
|
|
| 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); |
| } |
|
|
| |
| |
| |
|
|
| static int |
| fchown_or_lchown (int desc, int dirfd, char const *name, uid_t uid, gid_t gid) |
| { |
| #if HAVE_FCHOWN |
| if (0 <= desc) |
| return fchown (desc, uid, gid); |
| #endif |
| return lchownat (dirfd, name, uid, gid); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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; |
|
|
| |
| |
| |
| |
| |
|
|
| 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 (fchown_or_lchown (dest_desc, dst_dirfd, dst_relname, uid, gid) == 0) |
| return 1; |
|
|
| |
| |
| |
| if (chown_failure_ok (x)) |
| ignore_value (fchown_or_lchown (dest_desc, dst_dirfd, dst_relname, |
| -1, gid)); |
| else |
| { |
| error (0, errno, _("failed to preserve ownership for %s"), |
| quoteaf (dst_name)); |
| if (x->require_preserve) |
| return -1; |
| } |
|
|
| return 0; |
| } |
|
|
| |
| |
| |
| |
|
|
| static void |
| set_author (char const *dst_name, int dest_desc, const struct stat *src_sb) |
| { |
| #if HAVE_STRUCT_STAT_ST_AUTHOR |
| |
| |
|
|
| |
| 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 |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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) |
| { |
| |
| bool all_errors = !x->data_copy_required || x->require_preserve_context; |
| bool some_errors = !all_errors && !x->reduce_diagnostics; |
| char *con_raw; |
|
|
| if (0 <= lgetfilecon_raw (src_name, &con_raw)) |
| { |
| if (setfscreatecon_raw (con_raw) < 0) |
| { |
| if (all_errors || (some_errors && !errno_unsupported (errno))) |
| error (0, errno, |
| _("failed to set default file creation context to %s"), |
| quote (con_raw)); |
| if (x->require_preserve_context) |
| { |
| freecon (con_raw); |
| return false; |
| } |
| } |
| freecon (con_raw); |
| } |
| 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) |
| { |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| 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; |
| } |
|
|
| #if HAVE_FCLONEFILEAT && !USE_XATTR |
| # include <sys/acl.h> |
| |
| static bool |
| fd_has_acl (int fd) |
| { |
| |
| |
| bool has_acl = false; |
| acl_t acl = acl_get_fd_np (fd, ACL_TYPE_EXTENDED); |
| if (acl) |
| { |
| acl_entry_t ace; |
| has_acl = 0 <= acl_get_entry (acl, ACL_FIRST_ENTRY, &ace); |
| acl_free (acl); |
| } |
| return has_acl; |
| } |
| #endif |
|
|
| |
| |
|
|
| static bool |
| handle_clone_fail (int dst_dirfd, char const *dst_relname, |
| char const *src_name, char const *dst_name, |
| int dest_desc, bool new_dst, enum Reflink_type reflink_mode) |
| { |
| |
| |
| |
| |
| |
| bool report_failure = is_terminal_error (errno); |
|
|
| if (reflink_mode == REFLINK_ALWAYS || report_failure) |
| error (0, errno, _("failed to clone %s from %s"), |
| quoteaf_n (0, dst_name), quoteaf_n (1, src_name)); |
|
|
| |
| |
| if (new_dst |
| && reflink_mode == REFLINK_ALWAYS |
| && ((! report_failure) || lseek (dest_desc, 0, SEEK_END) == 0) |
| && unlinkat (dst_dirfd, dst_relname, 0) != 0 && errno != ENOENT) |
| error (0, errno, _("cannot remove %s"), quoteaf (dst_name)); |
|
|
| if (! report_failure) |
| copy_debug.reflink = COPY_DEBUG_UNSUPPORTED; |
|
|
| if (reflink_mode == REFLINK_ALWAYS || report_failure) |
| return false; |
|
|
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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 *src_sb) |
| { |
| int dest_desc; |
| int dest_errno; |
| int source_desc; |
| mode_t extra_permissions; |
| struct stat sb; |
| struct stat src_open_sb; |
| bool return_val = true; |
| bool data_copy_required = x->data_copy_required; |
| bool preserve_xattr = USE_XATTR & x->preserve_xattr; |
|
|
| copy_debug.offload = COPY_DEBUG_UNKNOWN; |
| copy_debug.reflink = x->reflink_mode ? COPY_DEBUG_UNKNOWN : COPY_DEBUG_NO; |
| copy_debug.sparse_detection = COPY_DEBUG_UNKNOWN; |
|
|
| 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; |
| } |
|
|
| |
| |
| if (! psame_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; |
| } |
|
|
| |
| |
| *src_sb = src_open_sb; |
| mode_t src_mode = src_sb->st_mode; |
|
|
| |
| |
| 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; |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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) |
| { |
| |
| |
| 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; |
| } |
| } |
|
|
| |
| *new_dst = true; |
| } |
| } |
|
|
| if (*new_dst) |
| { |
| #if HAVE_FCLONEFILEAT && !USE_XATTR |
| # ifndef CLONE_ACL |
| # define CLONE_ACL 0 |
| # endif |
| # ifndef CLONE_NOOWNERCOPY |
| # define CLONE_NOOWNERCOPY 0 |
| # endif |
| |
| |
| |
| |
| |
| if (data_copy_required && x->reflink_mode |
| && (CLONE_NOOWNERCOPY || x->preserve_ownership)) |
| { |
| |
| |
| |
| mode_t cloned_mode_bits = S_ISVTX | S_IRWXUGO; |
| mode_t cloned_mode = src_mode & cloned_mode_bits; |
| mode_t desired_mode |
| = (x->preserve_mode ? src_mode & CHMOD_MODE_BITS |
| : x->set_mode ? x->mode |
| : ((x->explicit_no_preserve_mode ? MODE_RW_UGO : dst_mode) |
| & ~ cached_umask ())); |
| if (! (cloned_mode & ~desired_mode)) |
| { |
| int fc_flags |
| = (CLONE_NOFOLLOW |
| | (x->preserve_mode ? CLONE_ACL : 0) |
| | (x->preserve_ownership ? 0 : CLONE_NOOWNERCOPY)); |
| int s = fclonefileat (source_desc, dst_dirfd, dst_relname, |
| fc_flags); |
| if (s != 0 && (fc_flags & CLONE_ACL) && errno == EINVAL) |
| { |
| fc_flags &= ~CLONE_ACL; |
| s = fclonefileat (source_desc, dst_dirfd, dst_relname, |
| fc_flags); |
| } |
| if (s == 0) |
| { |
| copy_debug.reflink = COPY_DEBUG_YES; |
|
|
| |
| |
|
|
| if (!x->preserve_timestamps) |
| { |
| struct timespec timespec[2]; |
| timespec[0].tv_nsec = timespec[1].tv_nsec = UTIME_NOW; |
| if (utimensat (dst_dirfd, dst_relname, timespec, |
| AT_SYMLINK_NOFOLLOW) |
| != 0) |
| { |
| error (0, errno, _("updating times for %s"), |
| quoteaf (dst_name)); |
| return_val = false; |
| goto close_src_desc; |
| } |
| } |
|
|
| extra_permissions = desired_mode & ~cloned_mode; |
| if (!extra_permissions |
| && (!x->preserve_mode || (fc_flags & CLONE_ACL) |
| || !fd_has_acl (source_desc))) |
| { |
| goto close_src_desc; |
| } |
|
|
| |
| |
| omitted_permissions = 0; |
| dest_desc = -1; |
| goto set_dest_mode; |
| } |
| if (! handle_clone_fail (dst_dirfd, dst_relname, src_name, |
| dst_name, |
| -1, false , |
| x->reflink_mode)) |
| { |
| return_val = false; |
| goto close_src_desc; |
| } |
| } |
| else |
| copy_debug.reflink = COPY_DEBUG_AVOIDED; |
| } |
| else if (data_copy_required && x->reflink_mode) |
| { |
| if (! CLONE_NOOWNERCOPY) |
| copy_debug.reflink = COPY_DEBUG_AVOIDED; |
| } |
| #endif |
|
|
| |
| |
| |
| mode_t open_mode = |
| ((dst_mode & ~omitted_permissions) |
| | (preserve_xattr && !x->owner_privileges ? S_IWUSR : 0)); |
| extra_permissions = open_mode & ~dst_mode; |
|
|
| 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; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (dest_desc < 0 && dest_errno == EEXIST && ! x->move_mode) |
| { |
| if (issymlinkat (dst_dirfd, dst_relname) == 1) |
| { |
| 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; |
| } |
| } |
| } |
|
|
| |
| |
| 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; |
| } |
|
|
| |
| if (data_copy_required && x->reflink_mode) |
| { |
| if (clone_file (dest_desc, source_desc) == 0) |
| { |
| data_copy_required = false; |
| copy_debug.reflink = COPY_DEBUG_YES; |
| } |
| else |
| { |
| if (! handle_clone_fail (dst_dirfd, dst_relname, src_name, dst_name, |
| dest_desc, *new_dst, x->reflink_mode)) |
| { |
| 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; |
| } |
|
|
| |
| |
| |
| 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 |
| && (copy_file_data (source_desc, &src_open_sb, 0, src_name, |
| dest_desc, &sb, 0, dst_name, |
| COUNT_MAX, x, ©_debug) |
| < 0)) |
| { |
| 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; |
| } |
| } |
| } |
|
|
| |
| |
| 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 HAVE_FCLONEFILEAT && !USE_XATTR |
| set_dest_mode: |
| #endif |
| if (x->preserve_mode || x->move_mode) |
| { |
| if (xcopy_acl (src_name, source_desc, dst_name, dest_desc, src_mode) != 0 |
| && x->require_preserve) |
| return_val = false; |
| } |
| else if (x->set_mode) |
| { |
| if (xset_acl (dst_name, dest_desc, x->mode) != 0) |
| return_val = false; |
| } |
| else if (x->explicit_no_preserve_mode && *new_dst) |
| { |
| if (xset_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; |
| } |
| } |
|
|
| if (dest_desc < 0) |
| goto close_src_desc; |
|
|
| 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; |
| } |
|
|
| |
| if (x->debug) |
| emit_debug (x); |
|
|
| return return_val; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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 = psame_inode (src_sb, dst_sb); |
|
|
| *return_now = false; |
|
|
| |
| |
| |
| |
| |
| if (same && x->hard_link) |
| { |
| *return_now = true; |
| return true; |
| } |
|
|
| if (x->dereference == DEREF_NEVER) |
| { |
| same_link = same; |
|
|
| |
| |
| |
| 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) |
| { |
| |
| if (x->backup_type != no_backups) |
| return true; |
|
|
| |
| |
| |
| |
| 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 = psame_inode (src_sb_link, dst_sb_link); |
|
|
| |
| |
| |
| |
| |
| if (S_ISLNK (src_sb_link->st_mode) && S_ISLNK (dst_sb_link->st_mode) |
| && x->unlink_dest_before_opening) |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| if (x->backup_type != no_backups) |
| { |
| if (!same_link) |
| { |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| if ( ! x->move_mode |
| && x->dereference != DEREF_NEVER |
| && S_ISLNK (src_sb_link->st_mode) |
| && ! S_ISLNK (dst_sb_link->st_mode)) |
| return false; |
|
|
| return true; |
| } |
|
|
| |
| return ! same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname); |
| } |
|
|
| #if 0 |
| |
|
|
| |
| |
| |
| |
| |
| 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) |
| { |
| |
| |
| |
| |
| if (S_ISLNK (dst_sb_link->st_mode)) |
| return true; |
|
|
| |
| |
| if (same_link |
| && 1 < dst_sb_link->st_nlink |
| && ! same_nameat (AT_FDCWD, src_name, dst_dirfd, dst_relname)) |
| return ! x->move_mode; |
| } |
|
|
| |
| |
| if (!S_ISLNK (src_sb_link->st_mode) && !S_ISLNK (dst_sb_link->st_mode)) |
| { |
| if (!psame_inode (src_sb_link, dst_sb_link)) |
| return true; |
|
|
| |
| if (x->hard_link) |
| { |
| *return_now = true; |
| return true; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| } |
|
|
| |
| 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 (!psame_inode (&tmp_src_sb, &tmp_dst_sb)) |
| return true; |
|
|
| if (x->hard_link) |
| { |
| |
| |
| |
| |
| *return_now = ! S_ISLNK (dst_sb_link->st_mode); |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| 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]; |
| 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 (); |
| } |
|
|
| |
| |
| extern void |
| dest_info_init (struct cp_options *x) |
| { |
| x->dest_info |
| = hash_initialize (DEST_INFO_INITIAL_CAPACITY, |
| nullptr, |
| triple_hash, |
| triple_compare, |
| triple_free); |
| if (! x->dest_info) |
| xalloc_die (); |
| } |
|
|
| |
| |
| extern void |
| src_info_init (struct cp_options *x) |
| { |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| x->src_info |
| = hash_initialize (DEST_INFO_INITIAL_CAPACITY, |
| nullptr, |
| triple_hash_no_name, |
| triple_compare, |
| triple_free); |
| if (! x->src_info) |
| xalloc_die (); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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) |
| { |
| affirm (x->move_mode); |
| return (x->update == UPDATE_NONE |
| || x->update == UPDATE_NONE_FAIL |
| || ((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))); |
| } |
|
|
| |
| |
| |
| static void |
| emit_verbose (char const *format, char const *src, char const *dst, |
| char const *backup_dst_name) |
| { |
| printf (format, quoteaf_n (0, src), quoteaf_n (1, dst)); |
| if (backup_dst_name) |
| printf (_(" (backup: %s)"), quoteaf (backup_dst_name)); |
| putchar ('\n'); |
| } |
|
|
| |
| static void |
| restore_default_fscreatecon_or_die (void) |
| { |
| if (setfscreatecon (nullptr) != 0) |
| error (EXIT_FAILURE, errno, |
| _("failed to restore the default file creation context")); |
| } |
|
|
| |
| |
| 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); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 = nullptr; |
| 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; |
| } |
|
|
| |
| |
| |
| 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); |
| } |
|
|
| |
| |
| 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 |
| && memeq (srcbase, dstbase, dstbaselen) |
| && 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 && psame_inode (src_st, &dst_back_sb); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 = nullptr; |
| char *dst_backup = nullptr; |
| char const *drelname = *dst_relname ? dst_relname : "."; |
| bool delayed_ok; |
| bool copied_as_regular = false; |
| bool dest_is_symlink = false; |
| bool have_dst_lstat = false; |
|
|
| *copy_into_self = false; |
|
|
| int rename_errno = x->rename_errno; |
| if (x->move_mode && !x->exchange) |
| { |
| if (rename_errno < 0) |
| rename_errno = (renameatu (AT_FDCWD, src_name, dst_dirfd, drelname, |
| RENAME_NOREPLACE) |
| ? errno : 0); |
| nonexistent_dst = *rename_succeeded = rename_errno == 0; |
| } |
|
|
| if (rename_errno == 0 |
| ? !x->last_file |
| : rename_errno != EEXIST |
| || (x->update != UPDATE_NONE && x->update != UPDATE_NONE_FAIL)) |
| { |
| 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 ? drelname : 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 |
| ? _("-r not specified; omitting directory %s") |
| : _("omitting directory %s"), |
| quoteaf (src_name)); |
| return false; |
| } |
| } |
| else |
| { |
| #if defined lint && (defined __clang__ || defined __COVERITY__) |
| affirm (x->move_mode); |
| memset (&src_sb, 0, sizeof src_sb); |
| #endif |
| } |
|
|
| |
| |
| |
| |
| 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); |
|
|
| |
| |
| |
| |
| bool new_dst = 0 < nonexistent_dst; |
|
|
| if (! new_dst) |
| { |
| |
| |
| |
| |
| |
| if (! (rename_errno == EEXIST |
| && (x->update == UPDATE_NONE |
| || x->update == UPDATE_NONE_FAIL))) |
| { |
| |
| |
| |
| |
| |
| |
| bool use_lstat |
| = ((! S_ISREG (src_mode) |
| && (! x->copy_as_regular |
| || (S_ISDIR (src_mode) && !x->keep_directory_symlink) |
| || S_ISLNK (src_mode))) |
| || x->move_mode || x->symbolic_link || x->hard_link |
| || x->backup_type != no_backups |
| || x->unlink_dest_before_opening); |
| if (!use_lstat && nonexistent_dst < 0) |
| new_dst = true; |
| else if (0 <= follow_fstatat (dst_dirfd, drelname, &dst_sb, |
| use_lstat ? AT_SYMLINK_NOFOLLOW : 0)) |
| { |
| have_dst_lstat = use_lstat; |
| rename_errno = EEXIST; |
| } |
| else if (errno == ENOENT) |
| new_dst = true; |
| else if (errno == ELOOP && !use_lstat |
| && x->unlink_dest_after_failed_open) |
| { |
| |
| |
| } |
| else |
| { |
| error (0, errno, _("cannot stat %s"), quoteaf (dst_name)); |
| return false; |
| } |
| } |
|
|
| if (rename_errno == EEXIST) |
| { |
| bool return_now = false; |
| bool return_val = true; |
| bool skipped = false; |
|
|
| if ((x->update != UPDATE_NONE && x->update != UPDATE_NONE_FAIL) |
| && ! same_file_ok (src_name, &src_sb, dst_dirfd, drelname, |
| &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 == UPDATE_OLDER && !S_ISDIR (src_mode)) |
| { |
| |
| |
| |
| |
| 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)) |
| { |
| |
| |
| |
| |
| if (rename_succeeded) |
| *rename_succeeded = true; |
|
|
| |
| |
| |
| |
| |
| earlier_file = remember_copied (dst_relname, src_sb.st_ino, |
| src_sb.st_dev); |
| if (earlier_file) |
| { |
| |
| |
| if (! create_hard_link (nullptr, dst_dirfd, earlier_file, |
| dst_name, dst_dirfd, dst_relname, |
| true, |
| x->verbose, dereference)) |
| { |
| goto un_backup; |
| } |
| } |
|
|
| skipped = true; |
| goto skip; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| if (x->move_mode) |
| { |
| if (abandon_move (x, dst_name, dst_dirfd, drelname, &dst_sb)) |
| { |
| |
| |
| if (rename_succeeded) |
| *rename_succeeded = true; |
|
|
| skipped = true; |
| return_val = x->update == UPDATE_NONE; |
| } |
| } |
| else |
| { |
| if (! S_ISDIR (src_mode) |
| && (x->update == UPDATE_NONE |
| || x->update == UPDATE_NONE_FAIL |
| || (x->interactive == I_ASK_USER |
| && ! overwrite_ok (x, dst_name, dst_dirfd, |
| dst_relname, &dst_sb)))) |
| { |
| skipped = true; |
| return_val = x->update == UPDATE_NONE; |
| } |
| } |
|
|
| skip: |
| if (skipped) |
| { |
| if (x->update == UPDATE_NONE_FAIL) |
| error (0, 0, _("not replacing %s"), quoteaf (dst_name)); |
| else if (x->debug) |
| printf (_("skipped %s\n"), quoteaf (dst_name)); |
|
|
| return_now = true; |
| } |
|
|
| if (return_now) |
| return return_val; |
|
|
| |
| |
| if (!S_ISDIR (src_mode) != !S_ISDIR (dst_sb.st_mode) |
| && x->backup_type == no_backups && !x->exchange) |
| { |
| error (0, 0, |
| _(S_ISDIR (src_mode) |
| ? ("cannot overwrite non-directory %s " |
| "with directory %s") |
| : ("cannot overwrite directory %s " |
| "with non-directory %s")), |
| quoteaf_n (0, dst_name), quoteaf_n (1, src_name)); |
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| if (!S_ISDIR (dst_sb.st_mode) && command_line_arg |
| && x->backup_type != numbered_backups && !x->exchange |
| && 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; |
| } |
|
|
| char const *srcbase; |
| if (x->backup_type != no_backups |
| |
| |
| && ! dot_or_dotdot (srcbase = last_component (src_name)) |
| |
| |
| |
| |
| |
| && (x->move_mode || ! S_ISDIR (dst_sb.st_mode))) |
| { |
| |
| |
| |
| |
| 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); |
|
|
| |
| |
| |
| |
| |
| 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) |
| |
| && ! 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)); |
| } |
| } |
| } |
|
|
| |
| |
| if (command_line_arg |
| && x->dest_info |
| && ! x->move_mode |
| && x->backup_type == no_backups) |
| { |
| |
| |
| struct stat tmp_buf; |
| struct stat *dst_lstat_sb |
| = (have_dst_lstat ? &dst_sb |
| : fstatat (dst_dirfd, drelname, &tmp_buf, AT_SYMLINK_NOFOLLOW) < 0 |
| ? nullptr : &tmp_buf); |
|
|
| |
| if (dst_lstat_sb |
| && 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 (x->verbose && !x->move_mode && !S_ISDIR (src_mode)) |
| emit_verbose ("%s -> %s", src_name, dst_name, dst_backup); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| if (rename_errno == 0 || x->exchange) |
| earlier_file = nullptr; |
| 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); |
| } |
|
|
| |
| |
|
|
| if (earlier_file) |
| { |
| |
| |
| |
| if (S_ISDIR (src_mode)) |
| { |
| |
| |
| 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)); |
| |
| |
| |
| |
| if (x->move_mode && rename_succeeded) |
| *rename_succeeded = true; |
| |
| |
| return true; |
| } |
| else if (x->dereference == DEREF_ALWAYS |
| || (command_line_arg |
| && x->dereference == DEREF_COMMAND_LINE_ARGUMENTS)) |
| { |
| |
| |
| |
| |
| |
| |
| } |
| 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 (nullptr, 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 = ((renameatu (AT_FDCWD, src_name, dst_dirfd, drelname, |
| x->exchange ? RENAME_EXCHANGE : 0) |
| == 0) |
| ? 0 : errno); |
|
|
| if (rename_errno == 0) |
| { |
| if (x->verbose) |
| emit_verbose (x->exchange |
| ? _("exchanged %s <-> %s") |
| : _("renamed %s -> %s"), |
| src_name, dst_name, dst_backup); |
|
|
| if (x->set_security_context) |
| { |
| |
| (void) set_file_security_ctx (dst_name, true, x); |
| } |
|
|
| if (rename_succeeded) |
| *rename_succeeded = true; |
|
|
| if (command_line_arg && !x->last_file) |
| { |
| |
| |
| |
| |
| |
| |
| |
| record_file (x->dest_info, dst_relname, &src_sb); |
| } |
|
|
| return true; |
| } |
|
|
| |
| |
|
|
| |
| |
| if (rename_errno == EINVAL) |
| { |
| |
| |
| |
| 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)); |
|
|
| |
| |
| |
|
|
| *copy_into_self = true; |
| |
| |
| |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (rename_errno != EXDEV || x->no_copy || x->exchange) |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| char const *quoted_dst_name = quoteaf_n (1, dst_name); |
| if (x->exchange) |
| error (0, rename_errno, _("cannot exchange %s and %s"), |
| quoteaf_n (0, src_name), quoted_dst_name); |
| else |
| switch (rename_errno) |
| { |
| case EDQUOT: case EEXIST: case EISDIR: case EMLINK: |
| case ENOSPC: case ETXTBSY: |
| #if ENOTEMPTY != EEXIST |
| case ENOTEMPTY: |
| #endif |
| |
| |
| |
| error (0, rename_errno, _("cannot overwrite %s"), |
| quoted_dst_name); |
| break; |
|
|
| default: |
| error (0, rename_errno, _("cannot move %s to %s"), |
| quoteaf_n (0, src_name), quoted_dst_name); |
| break; |
| } |
| forget_created (src_sb.st_ino, src_sb.st_dev); |
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| if ((unlinkat (dst_dirfd, drelname, |
| 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)) |
| emit_verbose (_("copied %s -> %s"), src_name, dst_name, dst_backup); |
| new_dst = true; |
| } |
|
|
| |
| |
| |
| |
| 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 (! 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 (is_ancestor (&src_sb, ancestors)) |
| { |
| error (0, 0, _("cannot copy cyclic symbolic link %s"), |
| quoteaf (src_name)); |
| goto un_backup; |
| } |
|
|
| |
|
|
| dir = alloca (sizeof *dir); |
| dir->parent = ancestors; |
| dir->st_ino = src_sb.st_ino; |
| dir->st_dev = src_sb.st_dev; |
|
|
| if (new_dst || !S_ISDIR (dst_sb.st_mode)) |
| { |
| |
| |
| |
| |
| mode_t mode = dst_mode_bits & ~omitted_permissions; |
| if (mkdirat (dst_dirfd, drelname, mode) != 0) |
| { |
| error (0, errno, _("cannot create directory %s"), |
| quoteaf (dst_name)); |
| goto un_backup; |
| } |
|
|
| |
| |
| |
|
|
| if (fstatat (dst_dirfd, drelname, &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) |
| { |
| |
|
|
| dst_mode = dst_sb.st_mode; |
| restore_dst_mode = true; |
|
|
| if (lchmodat (dst_dirfd, drelname, dst_mode | S_IRWXU) != 0) |
| { |
| error (0, errno, _("setting permissions for %s"), |
| quoteaf (dst_name)); |
| goto un_backup; |
| } |
| } |
|
|
| |
| |
| |
| |
| 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 ("%s -> %s", src_name, dst_name, nullptr); |
| } |
| } |
| else |
| { |
| omitted_permissions = 0; |
|
|
| |
| |
| |
| 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; |
| } |
| } |
|
|
| |
| if (x->one_file_system && parent && parent->st_dev != src_sb.st_dev) |
| { |
| |
| |
| } |
| else |
| { |
| |
| |
| |
| |
| 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 != '/') |
| { |
| |
| 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)) |
| |
| |
| |
| || stat (".", &dot_sb) != 0 |
| || (fstatat (dst_dirfd, dst_parent, &dst_parent_sb, |
| 0) != 0) |
| || psame_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; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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)) |
| { |
| |
| |
| |
| |
| 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 == nullptr) |
| { |
| 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 == UPDATE_OLDER |
| && !new_dst && S_ISLNK (dst_sb.st_mode) |
| && dst_sb.st_size == strlen (src_link_val)) |
| { |
| |
| |
| |
| |
| 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) |
| { |
| |
| |
| 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 |
| { |
| |
| |
| |
| |
| } |
| } |
| } |
| else |
| { |
| error (0, 0, _("%s has unknown file type"), quoteaf (src_name)); |
| goto un_backup; |
| } |
|
|
| |
| |
| 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) |
| { |
| |
| |
| struct stat sb; |
| if (fstatat (dst_dirfd, drelname, &sb, AT_SYMLINK_NOFOLLOW) == 0) |
| record_file (x->dest_info, dst_relname, &sb); |
| } |
|
|
| |
| |
| 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; |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| 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, drelname, timespec, utimensat_flags) != 0) |
| { |
| error (0, errno, _("preserving times for %s"), quoteaf (dst_name)); |
| if (x->require_preserve) |
| return false; |
| } |
| } |
|
|
| |
| 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, drelname, -1, |
| &src_sb, new_dst, &dst_sb)) |
| { |
| case -1: |
| return false; |
|
|
| case 0: |
| src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX); |
| break; |
| } |
| } |
|
|
| |
| if (x->preserve_xattr && ! copy_attr (src_name, -1, dst_name, -1, x) |
| && x->require_preserve_xattr) |
| return false; |
|
|
| |
| if (dest_is_symlink) |
| return delayed_ok; |
|
|
| set_author (dst_name, -1, &src_sb); |
|
|
| if (x->preserve_mode || x->move_mode) |
| { |
| if (xcopy_acl (src_name, -1, dst_name, -1, src_mode) != 0 |
| && x->require_preserve) |
| return false; |
| } |
| else if (x->set_mode) |
| { |
| if (xset_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; |
| dst_mode = dst_sb.st_mode; |
| if (S_ISDIR (src_mode)) |
| default_permissions |= (dst_mode & S_ISGID); |
| if (xset_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) |
| { |
| |
| |
| |
| |
| |
| |
| if (new_dst && (fstatat (dst_dirfd, drelname, &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, drelname, 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 (); |
|
|
| |
| |
| |
| |
| |
| |
| if (earlier_file == nullptr) |
| 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, drelname) != 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; |
| } |
|
|
| static void |
| valid_options (const struct cp_options *co) |
| { |
| affirm (VALID_BACKUP_TYPE (co->backup_type)); |
| affirm (VALID_SPARSE_MODE (co->sparse_mode)); |
| affirm (VALID_REFLINK_MODE (co->reflink_mode)); |
| affirm (!(co->hard_link && co->symbolic_link)); |
| affirm (! |
| (co->reflink_mode == REFLINK_ALWAYS |
| && co->sparse_mode != SPARSE_AUTO)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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) |
| { |
| valid_options (options); |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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, nullptr, nullptr, |
| options, true, |
| &first_dir_created_per_command_line_arg, |
| copy_into_self, rename_succeeded); |
| } |
|
|
| |
|
|
| 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; |
| } |
|
|
| |
| |
| |
|
|
| extern bool |
| chown_failure_ok (struct cp_options const *x) |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| return ((errno == EPERM || errno == EINVAL || errno == EACCES) |
| && !x->chown_privileges); |
| } |
|
|
| |
| |
| |
|
|
| static bool |
| owner_failure_ok (struct cp_options const *x) |
| { |
| return ((errno == EPERM || errno == EINVAL || errno == EACCES) |
| && !x->owner_privileges); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| extern mode_t |
| cached_umask (void) |
| { |
| static mode_t mask; |
| static bool cached; |
| if (!cached) |
| { |
| cached = true; |
| mask = umask (0); |
| umask (mask); |
| } |
| return mask; |
| } |
|
|