From d54f60c2af4bb5b0df9861b40f2a2609bacb768b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 28 Mar 2023 12:32:51 +0200 Subject: [PATCH] btrfs-util: Add BTRFS_SNAPSHOT_LOCK_BSD When making ephemeral snapshots of subvolumes whose cleanup depends on whether they're locked or not, it's necessary to have the lock from the very beginning, so let's support that with a new BTRFS_SNAPSHOT_LOCK_BSD flag. --- src/shared/btrfs-util.c | 49 ++++++++++++++++++++++++++++++++++++----- src/shared/btrfs-util.h | 1 + src/test/test-btrfs.c | 19 ++++++++++++++-- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index f86645e4eed..5128b308abf 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -1363,9 +1363,26 @@ static int subvol_snapshot_children( if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &vol_args) < 0) return -errno; + if (FLAGS_SET(flags, BTRFS_SNAPSHOT_LOCK_BSD)) { + subvolume_fd = xopenat_lock(new_fd, subvolume, + O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW, + /* xopen_flags = */ 0, + /* mode = */ 0, + LOCK_BSD, + LOCK_EX); + if (subvolume_fd < 0) + return subvolume_fd; + + r = btrfs_is_subvol_fd(subvolume_fd); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + } + if (!(flags & BTRFS_SNAPSHOT_RECURSIVE) && !(flags & BTRFS_SNAPSHOT_QUOTA)) - return 0; + return flags & BTRFS_SNAPSHOT_LOCK_BSD ? TAKE_FD(subvolume_fd) : 0; if (old_subvol_id == 0) { r = btrfs_subvol_get_id_fd(old_fd, &old_subvol_id); @@ -1385,7 +1402,7 @@ static int subvol_snapshot_children( if (flags & BTRFS_SNAPSHOT_QUOTA) (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); - return 0; + return flags & BTRFS_SNAPSHOT_LOCK_BSD ? TAKE_FD(subvolume_fd) : 0; } args.key.min_offset = args.key.max_offset = old_subvol_id; @@ -1480,7 +1497,8 @@ static int subvol_snapshot_children( return k; } - r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, flags & ~BTRFS_SNAPSHOT_FALLBACK_COPY); + r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, + flags & ~(BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_LOCK_BSD)); /* Restore the readonly flag */ if (flags & BTRFS_SNAPSHOT_READ_ONLY) { @@ -1503,7 +1521,7 @@ static int subvol_snapshot_children( if (flags & BTRFS_SNAPSHOT_QUOTA) (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); - return 0; + return flags & BTRFS_SNAPSHOT_LOCK_BSD ? TAKE_FD(subvolume_fd) : 0; } int btrfs_subvol_snapshot_at_full( @@ -1517,7 +1535,7 @@ int btrfs_subvol_snapshot_at_full( void *userdata) { _cleanup_free_ char *subvolume = NULL; - _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF; + _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF, subvolume_fd = -EBADF; int r; assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD); @@ -1556,6 +1574,25 @@ int btrfs_subvol_snapshot_at_full( } else if (r < 0) return r; + if (FLAGS_SET(flags, BTRFS_SNAPSHOT_LOCK_BSD)) { + subvolume_fd = xopenat_lock(new_fd, subvolume, + O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW, + /* xopen_flags = */ 0, + /* mode = */ 0, + LOCK_BSD, + LOCK_EX); + if (subvolume_fd < 0) + return subvolume_fd; + + if (!plain_directory) { + r = btrfs_is_subvol_fd(subvolume_fd); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + } + } + r = copy_directory_at_full( dir_fdf, from, new_fd, subvolume, @@ -1587,7 +1624,7 @@ int btrfs_subvol_snapshot_at_full( } } - return 0; + return flags & BTRFS_SNAPSHOT_LOCK_BSD ? TAKE_FD(subvolume_fd) : 0; fallback_fail: (void) rm_rf_at(new_fd, subvolume, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index de38f1e45c3..c972cc07446 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -36,6 +36,7 @@ typedef enum BtrfsSnapshotFlags { 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 */ + BTRFS_SNAPSHOT_LOCK_BSD = 1 << 8, /* Return a BSD exclusively locked file descriptor referring to snapshot subvolume/directory. */ } BtrfsSnapshotFlags; typedef enum BtrfsRemoveFlags { diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index ccb1661a914..ee4772b983c 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -4,6 +4,7 @@ #include "btrfs-util.h" #include "fd-util.h" +#include "fs-util.h" #include "fileio.h" #include "format-util.h" #include "log.h" @@ -62,6 +63,14 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to make snapshot: %m"); + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest4", BTRFS_SNAPSHOT_LOCK_BSD); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + if (r >= 0) + assert_se(xopenat_lock(AT_FDCWD, "/xxxtest4", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + + safe_close(r); + r = btrfs_subvol_remove("/xxxtest", BTRFS_REMOVE_QUOTA); if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); @@ -74,6 +83,10 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); + r = btrfs_subvol_remove("/xxxtest4", BTRFS_REMOVE_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/etc", AT_FDCWD, "/etc2", BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY); if (r < 0) @@ -161,13 +174,15 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to query quota: %m"); - assert_se(quota.referenced_max == 4ULL * 1024 * 1024 * 1024); + if (r >= 0) + assert_se(quota.referenced_max == 4ULL * 1024 * 1024 * 1024); r = btrfs_subvol_get_subtree_quota("/xxxquotatest2", 0, "a); if (r < 0) log_error_errno(r, "Failed to query quota: %m"); - assert_se(quota.referenced_max == 5ULL * 1024 * 1024 * 1024); + if (r >= 0) + assert_se(quota.referenced_max == 5ULL * 1024 * 1024 * 1024); r = btrfs_subvol_remove("/xxxquotatest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); if (r < 0) -- 2.47.3