diff --git a/src/basic/chattr-util.c b/src/basic/chattr-util.c index 49831a26ce..13d46f26aa 100644 --- a/src/basic/chattr-util.c +++ b/src/basic/chattr-util.c @@ -149,14 +149,23 @@ int read_attr_fd(int fd, unsigned *ret) { } int read_attr_at(int dir_fd, const char *path, unsigned *ret) { - _cleanup_close_ int fd = -EBADF; + _cleanup_close_ int fd_close = -EBADF; + int fd; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(ret); - fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return fd; + if (isempty(path)) { + fd = fd_reopen_condition(dir_fd, O_RDONLY|O_CLOEXEC, O_PATH, &fd_close); /* drop O_PATH if it is set */ + if (fd < 0) + return fd; + } else { + fd_close = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd_close < 0) + return fd_close; + + fd = fd_close; + } return read_attr_fd(fd, ret); } diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index af5552379e..1a2595c8bf 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2881,13 +2881,8 @@ static int setup_ephemeral( if (*root_image) { log_debug("Making ephemeral copy of %s to %s", *root_image, new_root); - fd = copy_file(*root_image, - new_root, - O_EXCL, - 0600, - COPY_LOCK_BSD| - COPY_REFLINK| - COPY_CRTIME); + fd = copy_file(*root_image, new_root, O_EXCL, 0600, + COPY_LOCK_BSD|COPY_REFLINK|COPY_CRTIME|COPY_NOCOW_AFTER); if (fd < 0) return log_debug_errno(fd, "Failed to copy image %s to %s: %m", *root_image, new_root); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 2694b11f14..92077866a2 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -36,6 +36,7 @@ #include "capability-util.h" #include "cgroup-util.h" #include "chase.h" +#include "chattr-util.h" #include "common-signal.h" #include "copy.h" #include "cpu-set-util.h" @@ -6073,7 +6074,7 @@ static int run(int argc, char *argv[]) { { BLOCK_SIGNALS(SIGINT); r = copy_file(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600, - COPY_REFLINK|COPY_CRTIME|COPY_SIGINT); + COPY_REFLINK|COPY_CRTIME|COPY_SIGINT|COPY_NOCOW_AFTER); } if (r == -EINTR) { log_error_errno(r, "Interrupted while copying image file to %s, removed again.", np); diff --git a/src/shared/copy.c b/src/shared/copy.c index 8e240dc434..836753cc75 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -765,6 +765,35 @@ static int memorize_hardlink( return 1; } +static int prepare_nocow(int fdf, const char *from, int fdt, unsigned *chattr_mask, unsigned *chattr_flags) { + unsigned attrs = 0; + int r; + + assert(fdf >= 0 || fdf == AT_FDCWD); + assert(fdt >= 0); + assert(!!chattr_mask == !!chattr_flags); + + /* If caller explicitly requested NOCOW to be set or unset, let's not interfere. */ + if (chattr_mask && FLAGS_SET(*chattr_mask, FS_NOCOW_FL)) + return 0; + + r = read_attr_at(fdf, from, &attrs); + if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r) && r != -ELOOP) /* If the source is a symlink we get ELOOP */ + return r; + + if (FLAGS_SET(attrs, FS_NOCOW_FL)) { + if (chattr_mask && chattr_flags) { + *chattr_mask |= FS_NOCOW_FL; + *chattr_flags |= FS_NOCOW_FL; + } else + /* If the NOCOW flag is set on the source, make the copy NOCOW as well. If the source + * is not NOCOW, don't do anything in particular with the copy. */ + (void) chattr_fd(fdt, FS_NOCOW_FL, FS_NOCOW_FL, /*previous=*/ NULL); + } + + return 0; +} + static int fd_copy_tree_generic( int df, const char *from, @@ -798,7 +827,6 @@ static int fd_copy_regular( void *userdata) { _cleanup_close_ int fdf = -EBADF, fdt = -EBADF; - unsigned attrs = 0; int r, q; assert(st); @@ -814,10 +842,6 @@ static int fd_copy_regular( if (fdf < 0) return fdf; - r = read_attr_fd(fdf, &attrs); - if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r) && r != -ELOOP) - return r; - if (copy_flags & COPY_MAC_CREATE) { r = mac_selinux_create_file_prepare_at(dt, to, S_IFREG); if (r < 0) @@ -829,8 +853,9 @@ static int fd_copy_regular( if (fdt < 0) return -errno; - if (attrs != 0) - (void) chattr_full(fdt, NULL, attrs, CHATTR_EARLY_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); + r = prepare_nocow(fdf, /*from=*/ NULL, fdt, /*chattr_mask=*/ NULL, /*chattr_flags=*/ NULL); + if (r < 0) + return r; r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress, userdata); if (r < 0) @@ -847,9 +872,6 @@ static int fd_copy_regular( (void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim }); (void) copy_xattr(fdf, NULL, fdt, NULL, copy_flags); - if (attrs != 0) - (void) chattr_full(fdt, NULL, attrs, ~CHATTR_EARLY_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); - if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { r = fd_verify_linked(fdf); if (r < 0) @@ -1430,7 +1452,6 @@ int copy_file_at_full( _cleanup_close_ int fdf = -EBADF, fdt = -EBADF; struct stat st; - unsigned attrs = 0; int r; assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD); @@ -1448,10 +1469,6 @@ int copy_file_at_full( if (r < 0) return r; - r = read_attr_at(dir_fdf, from, &attrs); - if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r) && r != -ELOOP) - return r; - WITH_UMASK(0000) { fdt = xopenat_lock_full(dir_fdt, to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, @@ -1468,10 +1485,12 @@ int copy_file_at_full( goto fail; } - attrs = (attrs & ~chattr_mask) | (chattr_flags & chattr_mask); + r = prepare_nocow(fdf, /*from=*/ NULL, fdt, &chattr_mask, &chattr_flags); + if (r < 0) + return r; - if (attrs != 0) - (void) chattr_full(fdt, NULL, attrs, CHATTR_EARLY_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); + 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 & ~COPY_LOCK_BSD, NULL, NULL, progress_bytes, userdata); if (r < 0) @@ -1486,8 +1505,9 @@ int copy_file_at_full( goto fail; } - if (attrs != 0) - (void) chattr_full(fdt, NULL, attrs, ~CHATTR_EARLY_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); + unsigned nocow = FLAGS_SET(copy_flags, COPY_NOCOW_AFTER) ? FS_NOCOW_FL : 0; + if ((chattr_mask | nocow) != 0) + (void) chattr_fd(fdt, chattr_flags | nocow, (chattr_mask & ~CHATTR_EARLY_FL) | nocow, NULL); if (copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL)) { if (fsync(fdt) < 0) { @@ -1532,7 +1552,6 @@ int copy_file_atomic_at_full( _cleanup_(unlink_and_freep) char *t = NULL; _cleanup_close_ int fdt = -EBADF; - unsigned attrs = 0; int r; assert(to); @@ -1549,14 +1568,12 @@ int copy_file_atomic_at_full( if (fdt < 0) return fdt; - r = read_attr_at(dir_fdf, from, &attrs); - if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r) && r != -ELOOP) + r = prepare_nocow(dir_fdf, from, fdt, &chattr_mask, &chattr_flags); + if (r < 0) return r; - attrs = (attrs & ~chattr_mask) | (chattr_flags & chattr_mask); - - if (attrs != 0) - (void) chattr_full(fdt, NULL, attrs, CHATTR_EARLY_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); + if (chattr_mask != 0) + (void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL); r = copy_file_fd_at_full(dir_fdf, from, fdt, copy_flags, progress_bytes, userdata); if (r < 0) @@ -1577,8 +1594,9 @@ int copy_file_atomic_at_full( t = mfree(t); - if (attrs != 0) - (void) chattr_full(fdt, NULL, attrs, ~CHATTR_EARLY_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); + unsigned nocow = FLAGS_SET(copy_flags, COPY_NOCOW_AFTER) ? FS_NOCOW_FL : 0; + if ((chattr_mask | nocow) != 0) + (void) chattr_fd(fdt, chattr_flags | nocow, (chattr_mask & ~CHATTR_EARLY_FL) | nocow, NULL); r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */ if (r < 0) diff --git a/src/shared/copy.h b/src/shared/copy.h index db95738b80..4eea560bed 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -32,6 +32,14 @@ typedef enum CopyFlags { COPY_LOCK_BSD = 1 << 17, /* Return a BSD exclusively locked file descriptor referring to the copied image/directory. */ COPY_VERIFY_LINKED = 1 << 18, /* Check the source file is still linked after copying. */ COPY_RESTORE_DIRECTORY_TIMESTAMPS = 1 << 19, /* Make sure existing directory timestamps don't change during copying. */ + /* A root image might be subject to lots of random writes so we provide a flag to try to disable COW + * on a copied file which tends to not perform well in combination with lots of random writes. + * + * Note: btrfs actually isn't impressed by us setting the flag after making the copy, but this at + * least makes the intention clear. We don't want to unconditionally set the flag before doing the + * copy because reflinking from COW to NOCOW files is not supported. + */ + COPY_NOCOW_AFTER = 1 << 20, } CopyFlags; typedef enum DenyType { diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 1386452203..10b2405cc3 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -1205,7 +1205,8 @@ int image_clone(Image *i, const char *new_name, bool read_only) { case IMAGE_RAW: new_path = strjoina("/var/lib/machines/", new_name, ".raw"); - r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, COPY_REFLINK|COPY_CRTIME); + r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, + COPY_REFLINK|COPY_CRTIME|COPY_NOCOW_AFTER); break; case IMAGE_BLOCK: