mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 22:14:03 +02:00
Merge patch series "block,btrfs: fix frozen-superblock strand on device add/remove/replace"
Christian Brauner <brauner@kernel.org> says:
block,btrfs: fix frozen-superblock strand on device add/remove/replace
This is another series of fixes that fell out of the device to
superblock hashtable work. These are all pre-existing bugs.
A block-device freeze that races a btrfs device membership change can leave
the whole filesystem stuck frozen, recoverable only with a manual FITHAW.
btrfs holds each of its devices open with the superblock as the block-device
holder. bdev_freeze() - issued by "dmsetup suspend" or an LVM snapshot -
resolves that holder to freeze the filesystem, and bdev_thaw() ("dmsetup
resume") resolves it again to thaw. If a freeze lands while btrfs is adding,
removing or replacing a device, it rides in on the device's holder link and
freezes the filesystem; the membership change then drops that link, so the
matching thaw can no longer find the superblock. The filesystem stays frozen
with no way back short of FITHAW.
To reproduce on the remove path: build a two-device btrfs with one member
behind a dm-linear target, write enough data that removing that member
relocates for a few seconds, start "btrfs device remove" on it, and
"dmsetup suspend" the dm device while the removal is underway. The suspend's
freeze blocks on the remove ioctl's write access and rides in as the ioctl
drops it; the removal then clears the device's holder link, so the matching
"dmsetup resume" can no longer reach the superblock. On an unpatched kernel
the filesystem is left frozen and the next write hangs in D state until a
manual FITHAW (fsfreeze -u).
The fix lets a filesystem forbid freezing a device for the duration of a
membership change, modelled on deny_write_access()/allow_write_access().
bd_fsfreeze_count becomes signed: > 0 counts active freezes, < 0 counts deny
holders, and the two are mutually exclusive. bdev_deny_freeze() reserves the
device (bdev_freeze() then returns -EBUSY) and bdev_allow_freeze() releases
it; both are a single lockless atomic, so a filesystem can deny under
s_umount without inverting against bdev_freeze()'s bd_fsfreeze_mutex. btrfs
denies the device across each add, remove and replace, so a racing freeze is
refused instead of riding in, while a normal freeze of a settled member
still works.
To re-allow freezing safely on release, bdev_yield_claim() is split out of
bdev_fput(): the caller yields the holder while the device file is still
open, re-allows freezing on the now-holderless device, and only then closes
it. Re-allowing after the holder is gone avoids re-stranding on a racing
freeze; doing it while the file is still open keeps the block device alive
without referencing it after the final fput.
With the fix the racing suspend is refused with -EBUSY mid-removal and the
filesystem stays writable.
* patches from https://patch.msgid.link/20260616-work-super-freeze_deny_upstream-v2-0-b3567c7f994b@kernel.org:
btrfs: deny freezing devices undergoing a replace
btrfs: deny freezing a device while it is being added
btrfs: deny freezing a device while it is being removed
block: split bdev_yield_claim() out of bdev_fput()
block: allow making a block device unfreezable
Link: https://patch.msgid.link/20260616-work-super-freeze_deny_upstream-v2-0-b3567c7f994b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
commit
d152447614
113
block/bdev.c
113
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
|
||||
*/
|
||||
|
|
@ -1152,6 +1195,39 @@ void bdev_release(struct file *bdev_file)
|
|||
blkdev_put_no_open(bdev);
|
||||
}
|
||||
|
||||
/**
|
||||
* bdev_yield_claim - give up the holder claim on an open block device
|
||||
* @bdev_file: open block device
|
||||
*
|
||||
* Yield the holder and any write access for @bdev_file without closing it, so
|
||||
* the caller can still act on the device - e.g. bdev_allow_freeze() it - before
|
||||
* the final bdev_fput(). bdev_fput() yields too, so calling it afterwards is
|
||||
* safe.
|
||||
*/
|
||||
void bdev_yield_claim(struct file *bdev_file)
|
||||
{
|
||||
struct block_device *bdev;
|
||||
struct gendisk *disk;
|
||||
|
||||
if (!bdev_file->private_data)
|
||||
return;
|
||||
|
||||
bdev = file_bdev(bdev_file);
|
||||
disk = bdev->bd_disk;
|
||||
|
||||
mutex_lock(&disk->open_mutex);
|
||||
bdev_yield_write_access(bdev_file);
|
||||
bd_yield_claim(bdev_file);
|
||||
/*
|
||||
* Tell release we already gave up our hold on the
|
||||
* device and if write restrictions are available that
|
||||
* we already gave up write access to the device.
|
||||
*/
|
||||
bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host);
|
||||
mutex_unlock(&disk->open_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bdev_yield_claim);
|
||||
|
||||
/**
|
||||
* bdev_fput - yield claim to the block device and put the file
|
||||
* @bdev_file: open block device
|
||||
|
|
@ -1165,22 +1241,7 @@ void bdev_fput(struct file *bdev_file)
|
|||
if (WARN_ON_ONCE(bdev_file->f_op != &def_blk_fops))
|
||||
return;
|
||||
|
||||
if (bdev_file->private_data) {
|
||||
struct block_device *bdev = file_bdev(bdev_file);
|
||||
struct gendisk *disk = bdev->bd_disk;
|
||||
|
||||
mutex_lock(&disk->open_mutex);
|
||||
bdev_yield_write_access(bdev_file);
|
||||
bd_yield_claim(bdev_file);
|
||||
/*
|
||||
* Tell release we already gave up our hold on the
|
||||
* device and if write restrictions are available that
|
||||
* we already gave up write access to the device.
|
||||
*/
|
||||
bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host);
|
||||
mutex_unlock(&disk->open_mutex);
|
||||
}
|
||||
|
||||
bdev_yield_claim(bdev_file);
|
||||
fput(bdev_file);
|
||||
}
|
||||
EXPORT_SYMBOL(bdev_fput);
|
||||
|
|
|
|||
|
|
@ -247,8 +247,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE,
|
||||
fs_info->sb, &fs_holder_ops);
|
||||
/* Unfreezable for the whole replace; see btrfs_dev_replace_start(). */
|
||||
bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb);
|
||||
if (IS_ERR(bdev_file)) {
|
||||
btrfs_err(fs_info, "target device %s is invalid!", device_path);
|
||||
return PTR_ERR(bdev_file);
|
||||
|
|
@ -327,7 +327,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,
|
|||
return 0;
|
||||
|
||||
error:
|
||||
bdev_fput(bdev_file);
|
||||
/* Undo the open-time freeze deny. */
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -624,6 +625,15 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Deny the source before mark, so every 'leave' unwinds both denied. */
|
||||
if (src_device->bdev) {
|
||||
ret = bdev_deny_freeze(src_device->bdev);
|
||||
if (ret) {
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, true);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = mark_block_group_to_copy(fs_info, src_device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -708,7 +718,9 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
|
|||
return ret;
|
||||
|
||||
leave:
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device);
|
||||
if (src_device->bdev)
|
||||
bdev_allow_freeze(src_device->bdev);
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -889,6 +901,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
|
|||
*/
|
||||
ret = btrfs_start_delalloc_roots(fs_info, LONG_MAX, false);
|
||||
if (ret) {
|
||||
/* Stays started/resumable; keep both denied. */
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -902,6 +915,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
|
|||
while (1) {
|
||||
trans = btrfs_start_transaction(root, 0);
|
||||
if (IS_ERR(trans)) {
|
||||
/* Stays started/resumable; keep both denied. */
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
return PTR_ERR(trans);
|
||||
}
|
||||
|
|
@ -954,7 +968,10 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
|
|||
mutex_unlock(&fs_devices->device_list_mutex);
|
||||
btrfs_rm_dev_replace_blocked(fs_info);
|
||||
if (tgt_device)
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device);
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, true);
|
||||
/* The source stays a member; re-allow freezing it. */
|
||||
if (src_device->bdev)
|
||||
bdev_allow_freeze(src_device->bdev);
|
||||
btrfs_rm_dev_replace_unblocked(fs_info);
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
|
||||
|
|
@ -1027,6 +1044,8 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
|
|||
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
|
||||
/* The target is now a member; the source is freed (allow + release). */
|
||||
bdev_allow_freeze(tgt_device->bdev);
|
||||
btrfs_rm_dev_replace_free_srcdev(src_device);
|
||||
|
||||
return 0;
|
||||
|
|
@ -1155,8 +1174,9 @@ int btrfs_dev_replace_cancel(struct btrfs_fs_info *fs_info)
|
|||
btrfs_dev_name(src_device), src_device->devid,
|
||||
btrfs_dev_name(tgt_device));
|
||||
|
||||
/* A suspended replace never re-denied freezing; do not allow. */
|
||||
if (tgt_device)
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device);
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, false);
|
||||
break;
|
||||
default:
|
||||
up_write(&dev_replace->rwsem);
|
||||
|
|
@ -1186,6 +1206,11 @@ void btrfs_dev_replace_suspend_for_unmount(struct btrfs_fs_info *fs_info)
|
|||
dev_replace->time_stopped = ktime_get_real_seconds();
|
||||
dev_replace->item_needs_writeback = 1;
|
||||
btrfs_info(fs_info, "suspending dev_replace for unmount");
|
||||
/* Reopened freezable next mount; resume re-denies. */
|
||||
if (dev_replace->srcdev && dev_replace->srcdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->srcdev->bdev);
|
||||
if (dev_replace->tgtdev && dev_replace->tgtdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->tgtdev->bdev);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1198,6 +1223,7 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info)
|
|||
{
|
||||
struct task_struct *task;
|
||||
struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace;
|
||||
int ret = 0;
|
||||
|
||||
down_write(&dev_replace->rwsem);
|
||||
|
||||
|
|
@ -1241,8 +1267,33 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Re-deny for the resumed replace; stay suspended if frozen now. */
|
||||
if (dev_replace->srcdev->bdev &&
|
||||
bdev_deny_freeze(dev_replace->srcdev->bdev))
|
||||
goto suspend;
|
||||
if (bdev_deny_freeze(dev_replace->tgtdev->bdev)) {
|
||||
if (dev_replace->srcdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->srcdev->bdev);
|
||||
goto suspend;
|
||||
}
|
||||
|
||||
task = kthread_run(btrfs_dev_replace_kthread, fs_info, "btrfs-devrepl");
|
||||
return PTR_ERR_OR_ZERO(task);
|
||||
if (IS_ERR(task)) {
|
||||
bdev_allow_freeze(dev_replace->tgtdev->bdev);
|
||||
if (dev_replace->srcdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->srcdev->bdev);
|
||||
/* Undo the deny and suspend, but still fail the mount. */
|
||||
ret = PTR_ERR(task);
|
||||
goto suspend;
|
||||
}
|
||||
return 0;
|
||||
|
||||
suspend:
|
||||
btrfs_exclop_finish(fs_info);
|
||||
down_write(&dev_replace->rwsem);
|
||||
dev_replace->replace_state = BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED;
|
||||
up_write(&dev_replace->rwsem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int btrfs_dev_replace_kthread(void *data)
|
||||
|
|
|
|||
|
|
@ -2622,7 +2622,7 @@ static long btrfs_ioctl_rm_dev_v2(struct file *file, void __user *arg)
|
|||
err_drop:
|
||||
mnt_drop_write_file(file);
|
||||
if (bdev_file)
|
||||
bdev_fput(bdev_file);
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
out:
|
||||
btrfs_put_dev_args_from_path(&args);
|
||||
return ret;
|
||||
|
|
@ -2672,7 +2672,7 @@ static long btrfs_ioctl_rm_dev(struct file *file, void __user *arg)
|
|||
|
||||
mnt_drop_write_file(file);
|
||||
if (bdev_file)
|
||||
bdev_fput(bdev_file);
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
out:
|
||||
btrfs_put_dev_args_from_path(&args);
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -1124,7 +1124,16 @@ void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices)
|
|||
mutex_unlock(&uuid_mutex);
|
||||
}
|
||||
|
||||
static void btrfs_close_bdev(struct btrfs_device *device)
|
||||
/* Release a device that was made unfreezable for a membership change. */
|
||||
void btrfs_release_device_allow_freeze(struct file *bdev_file)
|
||||
{
|
||||
/* Yield before allow (strand-safe); file still open for the allow (UAF-safe). */
|
||||
bdev_yield_claim(bdev_file);
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
bdev_fput(bdev_file);
|
||||
}
|
||||
|
||||
static void btrfs_close_bdev(struct btrfs_device *device, bool allow_freeze)
|
||||
{
|
||||
if (!device->bdev)
|
||||
return;
|
||||
|
|
@ -1134,7 +1143,11 @@ static void btrfs_close_bdev(struct btrfs_device *device)
|
|||
invalidate_bdev(device->bdev);
|
||||
}
|
||||
|
||||
bdev_fput(device->bdev_file);
|
||||
/* @allow_freeze undoes a replace-time deny; unmount-close was never denied. */
|
||||
if (allow_freeze)
|
||||
btrfs_release_device_allow_freeze(device->bdev_file);
|
||||
else
|
||||
bdev_fput(device->bdev_file);
|
||||
}
|
||||
|
||||
static void btrfs_close_one_device(struct btrfs_device *device)
|
||||
|
|
@ -1155,7 +1168,7 @@ static void btrfs_close_one_device(struct btrfs_device *device)
|
|||
fs_devices->missing_devices--;
|
||||
}
|
||||
|
||||
btrfs_close_bdev(device);
|
||||
btrfs_close_bdev(device, false);
|
||||
if (device->bdev) {
|
||||
fs_devices->open_devices--;
|
||||
device->bdev = NULL;
|
||||
|
|
@ -2373,6 +2386,13 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
|
|||
fs_info->fs_devices->rw_devices == 1)
|
||||
return BTRFS_ERROR_DEV_ONLY_WRITABLE;
|
||||
|
||||
/* Removal and freezing are mutually exclusive; refuse if frozen now. */
|
||||
if (device->bdev) {
|
||||
ret = bdev_deny_freeze(device->bdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
|
||||
mutex_lock(&fs_info->chunk_mutex);
|
||||
list_del_init(&device->dev_alloc_list);
|
||||
|
|
@ -2399,6 +2419,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
|
|||
device->devid, ret);
|
||||
btrfs_abort_transaction(trans, ret);
|
||||
btrfs_end_transaction(trans);
|
||||
if (device->bdev)
|
||||
bdev_allow_freeze(device->bdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2490,6 +2512,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
|
|||
return btrfs_commit_transaction(trans);
|
||||
|
||||
error_undo:
|
||||
if (device->bdev)
|
||||
bdev_allow_freeze(device->bdev);
|
||||
if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
|
||||
mutex_lock(&fs_info->chunk_mutex);
|
||||
list_add(&device->dev_alloc_list,
|
||||
|
|
@ -2534,7 +2558,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev)
|
|||
|
||||
mutex_lock(&uuid_mutex);
|
||||
|
||||
btrfs_close_bdev(srcdev);
|
||||
/* The source was made unfreezable for the replace; undo it. */
|
||||
btrfs_close_bdev(srcdev, true);
|
||||
synchronize_rcu();
|
||||
btrfs_free_device(srcdev);
|
||||
|
||||
|
|
@ -2555,7 +2580,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev)
|
|||
mutex_unlock(&uuid_mutex);
|
||||
}
|
||||
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev)
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev,
|
||||
bool allow_freeze)
|
||||
{
|
||||
struct btrfs_fs_devices *fs_devices = tgtdev->fs_info->fs_devices;
|
||||
|
||||
|
|
@ -2576,7 +2602,7 @@ void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev)
|
|||
|
||||
btrfs_scratch_superblocks(tgtdev->fs_info, tgtdev);
|
||||
|
||||
btrfs_close_bdev(tgtdev);
|
||||
btrfs_close_bdev(tgtdev, allow_freeze);
|
||||
synchronize_rcu();
|
||||
btrfs_free_device(tgtdev);
|
||||
}
|
||||
|
|
@ -2845,6 +2871,37 @@ static int btrfs_finish_sprout(struct btrfs_trans_handle *trans)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open @path for @sb with freezing denied before the holder claim is published,
|
||||
* so a racing bdev_freeze() can never reach a claim a device add or replace may
|
||||
* still abort. The deny is taken on a throwaway non-holder probe open, then the
|
||||
* holder is opened by the probe's dev_t. Balanced by the caller.
|
||||
*/
|
||||
struct file *btrfs_open_device_deny_freeze(const char *path,
|
||||
struct super_block *sb)
|
||||
{
|
||||
struct file *probe_file, *bdev_file;
|
||||
int ret;
|
||||
|
||||
/* WRITE so bdev_file_open_by_path() rejects a read-only device. */
|
||||
probe_file = bdev_file_open_by_path(path, BLK_OPEN_WRITE, NULL, NULL);
|
||||
if (IS_ERR(probe_file))
|
||||
return probe_file;
|
||||
|
||||
ret = bdev_deny_freeze(file_bdev(probe_file));
|
||||
if (ret) {
|
||||
bdev_fput(probe_file);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
bdev_file = bdev_file_open_by_dev(file_bdev(probe_file)->bd_dev,
|
||||
BLK_OPEN_WRITE, sb, &fs_holder_ops);
|
||||
if (IS_ERR(bdev_file))
|
||||
bdev_allow_freeze(file_bdev(probe_file));
|
||||
bdev_fput(probe_file);
|
||||
return bdev_file;
|
||||
}
|
||||
|
||||
int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path)
|
||||
{
|
||||
struct btrfs_root *root = fs_info->dev_root;
|
||||
|
|
@ -2863,8 +2920,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
|||
if (sb_rdonly(sb) && !fs_devices->seeding)
|
||||
return -EROFS;
|
||||
|
||||
bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE,
|
||||
fs_info->sb, &fs_holder_ops);
|
||||
/* Forbid freezing until the device is a committed member (or unwound). */
|
||||
bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb);
|
||||
if (IS_ERR(bdev_file))
|
||||
return PTR_ERR(bdev_file);
|
||||
|
||||
|
|
@ -3035,8 +3092,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
|||
up_write(&sb->s_umount);
|
||||
locked = false;
|
||||
|
||||
if (ret) /* transaction commit */
|
||||
if (ret) { /* transaction commit */
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = btrfs_relocate_sys_chunks(fs_info);
|
||||
if (ret < 0)
|
||||
|
|
@ -3044,8 +3103,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
|||
"Failed to relocate sys chunks after device initialization. This can be fixed using the \"btrfs balance\" command.");
|
||||
trans = btrfs_attach_transaction(root);
|
||||
if (IS_ERR(trans)) {
|
||||
if (PTR_ERR(trans) == -ENOENT)
|
||||
if (PTR_ERR(trans) == -ENOENT) {
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
return 0;
|
||||
}
|
||||
ret = PTR_ERR(trans);
|
||||
trans = NULL;
|
||||
goto error_sysfs;
|
||||
|
|
@ -3065,6 +3126,7 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
|||
/* Update ctime/mtime for blkid or udev */
|
||||
update_dev_time(device_path);
|
||||
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
return ret;
|
||||
|
||||
error_sysfs:
|
||||
|
|
@ -3094,7 +3156,7 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
|||
error_free_device:
|
||||
btrfs_free_device(device);
|
||||
error:
|
||||
bdev_fput(bdev_file);
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
if (locked) {
|
||||
mutex_unlock(&uuid_mutex);
|
||||
up_write(&sb->s_umount);
|
||||
|
|
|
|||
|
|
@ -744,6 +744,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices,
|
|||
struct btrfs_device *btrfs_scan_one_device(const char *path, bool mount_arg_dev);
|
||||
int btrfs_forget_devices(dev_t devt);
|
||||
void btrfs_close_devices(struct btrfs_fs_devices *fs_devices);
|
||||
void btrfs_release_device_allow_freeze(struct file *bdev_file);
|
||||
void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices);
|
||||
void btrfs_assign_next_active_device(struct btrfs_device *device,
|
||||
struct btrfs_device *this_dev);
|
||||
|
|
@ -768,6 +769,8 @@ struct btrfs_device *btrfs_find_device(const struct btrfs_fs_devices *fs_devices
|
|||
const struct btrfs_dev_lookup_args *args);
|
||||
int btrfs_shrink_device(struct btrfs_device *device, u64 new_size);
|
||||
int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *path);
|
||||
struct file *btrfs_open_device_deny_freeze(const char *path,
|
||||
struct super_block *sb);
|
||||
int btrfs_balance(struct btrfs_fs_info *fs_info,
|
||||
struct btrfs_balance_control *bctl,
|
||||
struct btrfs_ioctl_balance_args *bargs);
|
||||
|
|
@ -788,7 +791,8 @@ int btrfs_init_writeback_bio_size(struct btrfs_fs_info *fs_info);
|
|||
int btrfs_run_dev_stats(struct btrfs_trans_handle *trans);
|
||||
void btrfs_rm_dev_replace_remove_srcdev(struct btrfs_device *srcdev);
|
||||
void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev);
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev);
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev,
|
||||
bool allow_freeze);
|
||||
unsigned long btrfs_full_stripe_len(struct btrfs_fs_info *fs_info,
|
||||
u64 logical);
|
||||
u64 btrfs_calc_stripe_length(const struct btrfs_chunk_map *map);
|
||||
|
|
|
|||
|
|
@ -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,7 +1837,10 @@ 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);
|
||||
void bdev_yield_claim(struct file *bdev_file);
|
||||
|
||||
struct io_comp_batch {
|
||||
struct rq_list req_list;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user