diff options
| author | Christian Brauner <brauner@kernel.org> | 2026-06-16 13:58:14 +0200 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-06-29 10:31:49 +0200 |
| commit | ea4e4cc263011910eb7c62f3bb4fa094a1573c61 (patch) | |
| tree | 9e99938b22316fcc6a58850aa15ebb2a25908843 /block/bdev.c | |
| parent | dc59e4fea9d83f03bad6bddf3fa2e52491777482 (diff) | |
block: allow making a block device unfreezable
Add bdev_deny_freeze() and bdev_allow_freeze(), modeled on
deny_write_access()/allow_write_access(). bd_fsfreeze_count becomes a
signed counter: > 0 counts active freezes, < 0 counts deniers, and the
two regimes are mutually exclusive. bdev_freeze() refuses with -EBUSY
while a deny is held, and bdev_deny_freeze() refuses while the device is
frozen.
A filesystem that mutates a device's membership (a btrfs device add,
remove or replace) denies freezing on the device for the duration, so a
claim a freeze walk might act on is never added or torn down behind the
freezer's back.
The deny/allow helpers are a single atomic on bd_fsfreeze_count and take
no lock, so they can be called while holding s_umount without inverting
against bdev_freeze()'s bd_fsfreeze_mutex -> s_umount order.
Reviewed-by: Jan Kara <jack@suse.cz>
Link: https://patch.msgid.link/20260616-work-super-freeze_deny_upstream-v2-1-b3567c7f994b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Diffstat (limited to 'block/bdev.c')
| -rw-r--r-- | block/bdev.c | 63 |
1 files changed, 53 insertions, 10 deletions
diff --git a/block/bdev.c b/block/bdev.c index 85ce57bd2ae4..9b73487a91ca 100644 --- a/block/bdev.c +++ b/block/bdev.c @@ -304,7 +304,12 @@ int bdev_freeze(struct block_device *bdev) mutex_lock(&bdev->bd_fsfreeze_mutex); - if (atomic_inc_return(&bdev->bd_fsfreeze_count) > 1) { + /* A device being removed from its filesystem refuses freezes. */ + if (!atomic_inc_unless_negative(&bdev->bd_fsfreeze_count)) { + mutex_unlock(&bdev->bd_fsfreeze_mutex); + return -EBUSY; + } + if (atomic_read(&bdev->bd_fsfreeze_count) > 1) { mutex_unlock(&bdev->bd_fsfreeze_mutex); return 0; } @@ -340,18 +345,18 @@ int bdev_thaw(struct block_device *bdev) mutex_lock(&bdev->bd_fsfreeze_mutex); - /* - * If this returns < 0 it means that @bd_fsfreeze_count was - * already 0 and no decrement was performed. - */ - nr_freeze = atomic_dec_if_positive(&bdev->bd_fsfreeze_count); - if (nr_freeze < 0) + /* <= 0: not frozen (0) or a freeze deny is held (< 0); leave it. */ + nr_freeze = atomic_read(&bdev->bd_fsfreeze_count); + if (nr_freeze <= 0) goto out; error = 0; - if (nr_freeze > 0) + if (nr_freeze > 1) { + atomic_dec(&bdev->bd_fsfreeze_count); goto out; + } + /* Keep the count positive across the thaw so a deny is refused. */ mutex_lock(&bdev->bd_holder_lock); if (bdev->bd_holder_ops && bdev->bd_holder_ops->thaw) { error = bdev->bd_holder_ops->thaw(bdev); @@ -360,14 +365,52 @@ int bdev_thaw(struct block_device *bdev) mutex_unlock(&bdev->bd_holder_lock); } - if (error) - atomic_inc(&bdev->bd_fsfreeze_count); + if (!error) + atomic_dec(&bdev->bd_fsfreeze_count); out: mutex_unlock(&bdev->bd_fsfreeze_mutex); return error; } EXPORT_SYMBOL(bdev_thaw); +/** + * bdev_deny_freeze - make a block device unfreezable + * @bdev: block device + * + * Reserve @bdev against bdev_freeze() the way deny_write_access() reserves a + * file against writers. bd_fsfreeze_count is sign-encoded: > 0 counts active + * freezes, < 0 counts deniers, so a deny succeeds only while no freeze is in + * progress. While held, bdev_freeze() returns -EBUSY. Pair with + * bdev_allow_freeze(). + * + * A filesystem removing, adding or replacing a member device denies freezes on + * it for the duration, so a claim a freeze walk might act on is never torn down + * behind the freezer's back. The deny is device-scoped, not (device, + * superblock)-scoped: a device shared by several superblocks is refused for all + * of them. No in-tree filesystem removes a shared claim from a live superblock. + * + * Return: 0, or -EBUSY if the device is currently frozen. + */ +int bdev_deny_freeze(struct block_device *bdev) +{ + return atomic_dec_unless_positive(&bdev->bd_fsfreeze_count) ? 0 : -EBUSY; +} +EXPORT_SYMBOL_GPL(bdev_deny_freeze); + +/** + * bdev_allow_freeze - allow freezing a block device again + * @bdev: block device + * + * Undo one bdev_deny_freeze(). + */ +void bdev_allow_freeze(struct block_device *bdev) +{ + /* A deny must be held, i.e. the count must be negative. */ + WARN_ON_ONCE(atomic_read(&bdev->bd_fsfreeze_count) >= 0); + atomic_inc(&bdev->bd_fsfreeze_count); +} +EXPORT_SYMBOL_GPL(bdev_allow_freeze); + /* * pseudo-fs */ |
