Merge pull request #20352 from poettering/copy-tweaks

various tweaks to copy.c
This commit is contained in:
Luca Boccassi 2021-08-02 20:31:42 +01:00 committed by GitHub
commit f121bd7818
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 229 additions and 59 deletions

View file

@ -1613,7 +1613,7 @@ int btrfs_subvol_snapshot_fd_full(
return -EISDIR;
r = btrfs_subvol_make(new_path);
if (r == -ENOTTY && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) {
if (ERRNO_IS_NOT_SUPPORTED(r) && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) {
/* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */
if (mkdir(new_path, 0755) < 0)
return -errno;
@ -1624,8 +1624,15 @@ int btrfs_subvol_snapshot_fd_full(
r = copy_directory_fd_full(
old_fd, new_path,
COPY_MERGE|COPY_REFLINK|COPY_SAME_MOUNT|COPY_HARDLINKS|(FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0),
progress_path, progress_bytes, userdata);
COPY_MERGE_EMPTY|
COPY_REFLINK|
COPY_SAME_MOUNT|
COPY_HARDLINKS|
(FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0)|
(FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGTERM) ? COPY_SIGTERM : 0),
progress_path,
progress_bytes,
userdata);
if (r < 0)
goto fallback_fail;

View file

@ -35,6 +35,7 @@ typedef enum BtrfsSnapshotFlags {
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY = 1 << 4, /* If the destination doesn't support subvolumes, reflink/copy instead */
BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE = 1 << 5, /* When we can't create a subvolume, use the FS_IMMUTABLE attribute for indicating read-only */
BTRFS_SNAPSHOT_SIGINT = 1 << 6, /* Check for SIGINT regularly, and return EINTR if seen */
BTRFS_SNAPSHOT_SIGTERM = 1 << 7, /* Ditto, but for SIGTERM */
} BtrfsSnapshotFlags;
typedef enum BtrfsRemoveFlags {

View file

@ -88,6 +88,23 @@ static int fd_is_nonblock_pipe(int fd) {
return FLAGS_SET(flags, O_NONBLOCK) ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE;
}
static int look_for_signals(CopyFlags copy_flags) {
int r;
if ((copy_flags & (COPY_SIGINT|COPY_SIGTERM)) == 0)
return 0;
r = pop_pending_signal(copy_flags & COPY_SIGINT ? SIGINT : 0,
copy_flags & COPY_SIGTERM ? SIGTERM : 0);
if (r < 0)
return r;
if (r != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINTR),
"Got %s, cancelling copy operation.", signal_to_string(r));
return 0;
}
int copy_bytes_full(
int fdf, int fdt,
uint64_t max_bytes,
@ -176,13 +193,9 @@ int copy_bytes_full(
if (max_bytes <= 0)
return 1; /* return > 0 if we hit the max_bytes limit */
if (FLAGS_SET(copy_flags, COPY_SIGINT)) {
r = pop_pending_signal(SIGINT);
if (r < 0)
return r;
if (r > 0)
return -EINTR;
}
r = look_for_signals(copy_flags);
if (r < 0)
return r;
if (max_bytes != UINT64_MAX && m > max_bytes)
m = max_bytes;
@ -627,10 +640,8 @@ static int fd_copy_regular(
return -errno;
r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress, userdata);
if (r < 0) {
(void) unlinkat(dt, to, 0);
return r;
}
if (r < 0)
goto fail;
if (fchown(fdt,
uid_is_valid(override_uid) ? override_uid : st->st_uid,
@ -643,16 +654,25 @@ static int fd_copy_regular(
(void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim });
(void) copy_xattr(fdf, fdt);
q = close(fdt);
fdt = -1;
if (copy_flags & COPY_FSYNC) {
if (fsync(fdt) < 0) {
r = -errno;
goto fail;
}
}
q = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
if (q < 0) {
r = -errno;
(void) unlinkat(dt, to, 0);
r = q;
goto fail;
}
(void) memorize_hardlink(hardlink_context, st, dt, to);
return r;
fail:
(void) unlinkat(dt, to, 0);
return r;
}
static int fd_copy_fifo(
@ -845,13 +865,9 @@ static int fd_copy_directory(
if (dot_or_dot_dot(de->d_name))
continue;
if (FLAGS_SET(copy_flags, COPY_SIGINT)) {
r = pop_pending_signal(SIGINT);
if (r < 0)
return r;
if (r > 0)
return -EINTR;
}
r = look_for_signals(copy_flags);
if (r < 0)
return r;
if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) {
r = -errno;
@ -912,7 +928,7 @@ static int fd_copy_directory(
else
q = -EOPNOTSUPP;
if (q == -EINTR) /* Propagate SIGINT up instantly */
if (q == -EINTR) /* Propagate SIGINT/SIGTERM up instantly */
return q;
if (q == -EEXIST && (copy_flags & COPY_MERGE))
q = 0;
@ -933,6 +949,11 @@ static int fd_copy_directory(
(void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim });
}
if (copy_flags & COPY_FSYNC_FULL) {
if (fsync(fdt) < 0)
return -errno;
}
return r;
}
@ -949,6 +970,7 @@ int copy_tree_at_full(
void *userdata) {
struct stat st;
int r;
assert(from);
assert(to);
@ -957,17 +979,47 @@ int copy_tree_at_full(
return -errno;
if (S_ISREG(st.st_mode))
return fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata);
r = fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata);
else if (S_ISDIR(st.st_mode))
return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
r = fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
else if (S_ISLNK(st.st_mode))
return fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags);
r = fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags);
else if (S_ISFIFO(st.st_mode))
return fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
r = fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode))
return fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
r = fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
else
return -EOPNOTSUPP;
if (r < 0)
return r;
if (S_ISDIR(st.st_mode) && (copy_flags & COPY_SYNCFS)) {
/* If the top-level inode is a directory run syncfs() now. */
r = syncfs_path(fdt, to);
if (r < 0)
return r;
} else if ((copy_flags & (COPY_FSYNC_FULL|COPY_SYNCFS)) != 0) {
/* fsync() the parent dir of what we just copied if COPY_FSYNC_FULL is set. Also do this in
* case COPY_SYNCFS is set but the top-level inode wasn't actually a directory. We do this so that
* COPY_SYNCFS provides reasonable synchronization semantics on any kind of inode: when the
* copy operation is done the whole inode regardless of its type and all its children
* will be synchronized to disk. */
r = fsync_parent_at(fdt, to);
if (r < 0)
return r;
}
return 0;
}
static int sync_dir_by_flags(const char *path, CopyFlags copy_flags) {
if (copy_flags & COPY_SYNCFS)
return syncfs_path(AT_FDCWD, path);
if (copy_flags & COPY_FSYNC_FULL)
return fsync_parent_at(AT_FDCWD, path);
return 0;
}
int copy_directory_fd_full(
@ -991,7 +1043,26 @@ int copy_directory_fd_full(
if (r < 0)
return r;
return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
r = fd_copy_directory(
dirfd, NULL,
&st,
AT_FDCWD, to,
st.st_dev,
COPY_DEPTH_MAX,
UID_INVALID, GID_INVALID,
copy_flags,
NULL, NULL,
progress_path,
progress_bytes,
userdata);
if (r < 0)
return r;
r = sync_dir_by_flags(to, copy_flags);
if (r < 0)
return r;
return 0;
}
int copy_directory_full(
@ -1015,7 +1086,26 @@ int copy_directory_full(
if (r < 0)
return r;
return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
r = fd_copy_directory(
AT_FDCWD, from,
&st,
AT_FDCWD, to,
st.st_dev,
COPY_DEPTH_MAX,
UID_INVALID, GID_INVALID,
copy_flags,
NULL, NULL,
progress_path,
progress_bytes,
userdata);
if (r < 0)
return r;
r = sync_dir_by_flags(to, copy_flags);
if (r < 0)
return r;
return 0;
}
int copy_file_fd_full(
@ -1026,6 +1116,7 @@ int copy_file_fd_full(
void *userdata) {
_cleanup_close_ int fdf = -1;
struct stat st;
int r;
assert(from);
@ -1035,12 +1126,32 @@ int copy_file_fd_full(
if (fdf < 0)
return -errno;
r = fd_verify_regular(fdf);
if (r < 0)
return r;
if (fstat(fdt, &st) < 0)
return -errno;
r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata);
if (r < 0)
return r;
(void) copy_times(fdf, fdt, copy_flags);
(void) copy_xattr(fdf, fdt);
if (S_ISREG(fdt)) {
(void) copy_times(fdf, fdt, copy_flags);
(void) copy_xattr(fdf, fdt);
}
return r;
if (copy_flags & COPY_FSYNC_FULL) {
r = fsync_full(fdt);
if (r < 0)
return r;
} else if (copy_flags & COPY_FSYNC) {
if (fsync(fdt) < 0)
return -errno;
}
return 0;
}
int copy_file_full(
@ -1054,9 +1165,9 @@ int copy_file_full(
copy_progress_bytes_t progress_bytes,
void *userdata) {
_cleanup_close_ int fdf = -1;
_cleanup_close_ int fdf = -1, fdt = -1;
struct stat st;
int r, fdt = -1; /* avoid false maybe-uninitialized warning */
int r;
assert(from);
assert(to);
@ -1065,9 +1176,12 @@ int copy_file_full(
if (fdf < 0)
return -errno;
if (mode == MODE_INVALID)
if (fstat(fdf, &st) < 0)
return -errno;
if (fstat(fdf, &st) < 0)
return -errno;
r = stat_verify_regular(&st);
if (r < 0)
return r;
RUN_WITH_UMASK(0000) {
if (copy_flags & COPY_MAC_CREATE) {
@ -1083,15 +1197,18 @@ int copy_file_full(
return -errno;
}
if (!FLAGS_SET(flags, O_EXCL)) { /* if O_EXCL was used we created the thing as regular file, no need to check again */
r = fd_verify_regular(fdt);
if (r < 0)
goto fail;
}
if (chattr_mask != 0)
(void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL);
r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata);
if (r < 0) {
close(fdt);
(void) unlink(to);
return r;
}
if (r < 0)
goto fail;
(void) copy_times(fdf, fdt, copy_flags);
(void) copy_xattr(fdf, fdt);
@ -1099,12 +1216,31 @@ int copy_file_full(
if (chattr_mask != 0)
(void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL);
if (close(fdt) < 0) {
unlink_noerrno(to);
return -errno;
if (copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL)) {
if (fsync(fdt) < 0) {
r = -errno;
goto fail;
}
}
r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
if (r < 0)
goto fail;
if (copy_flags & COPY_FSYNC_FULL) {
r = fsync_parent_at(AT_FDCWD, to);
if (r < 0)
goto fail;
}
return 0;
fail:
/* Only unlink if we definitely are the ones who created the file */
if (FLAGS_SET(flags, O_EXCL))
(void) unlink(to);
return r;
}
int copy_file_atomic_full(
@ -1172,6 +1308,12 @@ int copy_file_atomic_full(
if (fchmod(fdt, mode) < 0)
return -errno;
if ((copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL))) {
/* Sync the file */
if (fsync(fdt) < 0)
return -errno;
}
if (copy_flags & COPY_REPLACE) {
if (renameat(AT_FDCWD, t, AT_FDCWD, to) < 0)
return -errno;
@ -1181,11 +1323,27 @@ int copy_file_atomic_full(
return r;
}
t = mfree(t);
if (chattr_mask != 0)
(void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL);
t = mfree(t);
r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
if (r < 0)
goto fail;
if (copy_flags & COPY_FSYNC_FULL) {
/* Sync the parent directory */
r = fsync_parent_at(AT_FDCWD, to);
if (r < 0)
goto fail;
}
return 0;
fail:
(void) unlink(to);
return r;
}
int copy_times(int fdf, int fdt, CopyFlags flags) {

View file

@ -10,15 +10,19 @@
#include <sys/types.h>
typedef enum CopyFlags {
COPY_REFLINK = 1 << 0, /* Try to reflink */
COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */
COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */
COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */
COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */
COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */
COPY_MAC_CREATE = 1 << 7, /* Create files with the correct MAC label (currently SELinux only) */
COPY_HARDLINKS = 1 << 8, /* Try to reproduce hard links */
COPY_REFLINK = 1 << 0, /* Try to reflink */
COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */
COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */
COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */
COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */
COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */
COPY_SIGTERM = 1 << 7, /* ditto, but for SIGTERM */
COPY_MAC_CREATE = 1 << 8, /* Create files with the correct MAC label (currently SELinux only) */
COPY_HARDLINKS = 1 << 9, /* Try to reproduce hard links */
COPY_FSYNC = 1 << 10, /* fsync() after we are done */
COPY_FSYNC_FULL = 1 << 11, /* fsync_full() after we are done */
COPY_SYNCFS = 1 << 12, /* syncfs() the *top-level* dir after we are done */
} CopyFlags;
typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);