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