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)