| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | #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; |
| | } |
| |
|