Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 64 additions & 9 deletions block/bdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -368,6 +373,42 @@ int bdev_thaw(struct block_device *bdev)
}
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)
{
atomic_inc(&bdev->bd_fsfreeze_count);
}
EXPORT_SYMBOL_GPL(bdev_allow_freeze);

/*
* pseudo-fs
*/
Expand Down Expand Up @@ -1158,18 +1199,16 @@ void bdev_release(struct file *bdev_file)
}

/**
* bdev_fput - yield claim to the block device and put the file
* bdev_yield_claim - give up the holder claim on an open block device
* @bdev_file: open block device
*
* Yield claim on the block device and put the file. Ensure that the
* block device can be reclaimed before the file is closed which is a
* deferred operation.
* 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_fput(struct file *bdev_file)
void bdev_yield_claim(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;
Expand All @@ -1185,7 +1224,23 @@ void bdev_fput(struct file *bdev_file)
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
*
* Yield claim on the block device and put the file. Ensure that the
* block device can be reclaimed before the file is closed which is a
* deferred operation.
*/
void bdev_fput(struct file *bdev_file)
{
if (WARN_ON_ONCE(bdev_file->f_op != &def_blk_fops))
return;

bdev_yield_claim(bdev_file);
fput(bdev_file);
}
EXPORT_SYMBOL(bdev_fput);
Expand Down
67 changes: 59 additions & 8 deletions fs/btrfs/dev-replace.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -325,7 +325,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;
}

Expand Down Expand Up @@ -622,9 +623,18 @@ 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;
goto leave;

down_write(&dev_replace->rwsem);
dev_replace->replace_task = current;
Expand Down Expand Up @@ -706,7 +716,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;
}

Expand Down Expand Up @@ -887,6 +899,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;
}
Expand All @@ -900,6 +913,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);
}
Expand Down Expand Up @@ -952,7 +966,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);

Expand Down Expand Up @@ -1018,6 +1035,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;
Expand Down Expand Up @@ -1146,8 +1165,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);
Expand Down Expand Up @@ -1177,6 +1197,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;
}

Expand All @@ -1189,6 +1214,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);

Expand Down Expand Up @@ -1232,8 +1258,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)
Expand Down
4 changes: 2 additions & 2 deletions fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2579,7 +2579,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);
kfree(vol_args);
Expand Down Expand Up @@ -2630,7 +2630,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);
out_free:
Expand Down
Loading
Loading