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>
This commit is contained in:
Christian Brauner
2026-06-16 13:58:14 +02:00
parent dc59e4fea9
commit ea4e4cc263
3 changed files with 56 additions and 11 deletions

View File

@@ -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
*/

View File

@@ -66,7 +66,7 @@ struct block_device {
int bd_holders;
struct kobject *bd_holder_dir;
atomic_t bd_fsfreeze_count; /* number of freeze requests */
atomic_t bd_fsfreeze_count; /* >0 freeze requests, <0 freeze deniers */
struct mutex bd_fsfreeze_mutex; /* serialize freeze/thaw */
struct partition_meta_info *bd_meta_info;

View File

@@ -1837,6 +1837,8 @@ static inline int early_lookup_bdev(const char *pathname, dev_t *dev)
int bdev_freeze(struct block_device *bdev);
int bdev_thaw(struct block_device *bdev);
int bdev_deny_freeze(struct block_device *bdev);
void bdev_allow_freeze(struct block_device *bdev);
void bdev_fput(struct file *bdev_file);
struct io_comp_batch {