mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 06:23:42 -04:00
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:
63
block/bdev.c
63
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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user