/* * Device replace overview * * [Objective] * To copy all extents (both new and on-disk) from source device to target * device, while still keeping the filesystem read-write. * * [Method] * There are two main methods involved: * * - Write duplication * * All new writes will be written to both target and source devices, so even * if replace gets canceled, sources device still contains up-to-date data. * * Location: handle_ops_on_dev_replace() from btrfs_map_block() * Start: btrfs_dev_replace_start() * End: btrfs_dev_replace_finishing() * Content: Latest data/metadata * * - Copy existing extents * * This happens by reusing scrub facility, as scrub also iterates through * existing extents from commit root. * * Location: scrub_write_block_to_dev_replace() from * scrub_block_complete() * Content: Data/meta from commit root. * * Due to the content difference, we need to avoid nocow write when dev-replace * is happening. This is done by marking the block group read-only and waiting * for NOCOW writes. * * After replace is done, the finishing part is done by swapping the target and * source devices. * * Location: btrfs_dev_replace_update_device_in_mapping_tree() from * btrfs_dev_replace_finishing()
*/
staticint btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info, int scrub_ret); staticint btrfs_dev_replace_kthread(void *data);
int btrfs_init_dev_replace(struct btrfs_fs_info *fs_info)
{ struct btrfs_dev_lookup_args args = { .devid = BTRFS_DEV_REPLACE_DEVID }; struct btrfs_key key; struct btrfs_root *dev_root = fs_info->dev_root; struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace; struct extent_buffer *eb; int slot; int ret = 0;
BTRFS_PATH_AUTO_FREE(path); int item_size; struct btrfs_dev_replace_item *ptr;
u64 src_devid;
if (!dev_root) return 0;
path = btrfs_alloc_path(); if (!path) return -ENOMEM;
key.objectid = 0;
key.type = BTRFS_DEV_REPLACE_KEY;
key.offset = 0;
ret = btrfs_search_slot(NULL, dev_root, &key, path, 0, 0); if (ret) {
no_valid_dev_replace_entry_found: /* * We don't have a replace item or it's corrupted. If there is * a replace target, fail the mount.
*/ if (btrfs_find_device(fs_info->fs_devices, &args)) {
btrfs_err(fs_info, "found replace target device without a valid replace item"); return -EUCLEAN;
}
dev_replace->replace_state =
BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED;
dev_replace->cont_reading_from_srcdev_mode =
BTRFS_DEV_REPLACE_ITEM_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
dev_replace->time_started = 0;
dev_replace->time_stopped = 0;
atomic64_set(&dev_replace->num_write_errors, 0);
atomic64_set(&dev_replace->num_uncorrectable_read_errors, 0);
dev_replace->cursor_left = 0;
dev_replace->committed_cursor_left = 0;
dev_replace->cursor_left_last_write_of_item = 0;
dev_replace->cursor_right = 0;
dev_replace->srcdev = NULL;
dev_replace->tgtdev = NULL;
dev_replace->is_valid = 0;
dev_replace->item_needs_writeback = 0; return 0;
}
slot = path->slots[0];
eb = path->nodes[0];
item_size = btrfs_item_size(eb, slot);
ptr = btrfs_item_ptr(eb, slot, struct btrfs_dev_replace_item);
if (item_size != sizeof(struct btrfs_dev_replace_item)) {
btrfs_warn(fs_info, "dev_replace entry found has unexpected size, ignore entry"); goto no_valid_dev_replace_entry_found;
}
dev_replace->item_needs_writeback = 0; switch (dev_replace->replace_state) { case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED: case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED: /* * We don't have an active replace item but if there is a * replace target, fail the mount.
*/ if (btrfs_find_device(fs_info->fs_devices, &args)) {
btrfs_err(fs_info, "replace without active item, run 'device scan --forget' on the target device");
ret = -EUCLEAN;
} else {
dev_replace->srcdev = NULL;
dev_replace->tgtdev = NULL;
} break; case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
dev_replace->tgtdev = btrfs_find_device(fs_info->fs_devices, &args);
args.devid = src_devid;
dev_replace->srcdev = btrfs_find_device(fs_info->fs_devices, &args);
/* * allow 'btrfs dev replace_cancel' if src/tgt device is * missing
*/ if (!dev_replace->srcdev &&
!btrfs_test_opt(fs_info, DEGRADED)) {
ret = -EIO;
btrfs_warn(fs_info, "cannot mount because device replace operation is ongoing and");
btrfs_warn(fs_info, "srcdev (devid %llu) is missing, need to run 'btrfs dev scan'?",
src_devid);
} if (!dev_replace->tgtdev &&
!btrfs_test_opt(fs_info, DEGRADED)) {
ret = -EIO;
btrfs_warn(fs_info, "cannot mount because device replace operation is ongoing and");
btrfs_warn(fs_info, "tgtdev (devid %llu) is missing, need to run 'btrfs dev scan'?",
BTRFS_DEV_REPLACE_DEVID);
} if (dev_replace->tgtdev) { if (dev_replace->srcdev) {
dev_replace->tgtdev->total_bytes =
dev_replace->srcdev->total_bytes;
dev_replace->tgtdev->disk_total_bytes =
dev_replace->srcdev->disk_total_bytes;
dev_replace->tgtdev->commit_total_bytes =
dev_replace->srcdev->commit_total_bytes;
dev_replace->tgtdev->bytes_used =
dev_replace->srcdev->bytes_used;
dev_replace->tgtdev->commit_bytes_used =
dev_replace->srcdev->commit_bytes_used;
}
set_bit(BTRFS_DEV_STATE_REPLACE_TGT,
&dev_replace->tgtdev->dev_state);
if (!btrfs_check_device_zone_type(fs_info, bdev)) {
btrfs_err(fs_info, "dev-replace: zoned type of target device mismatch with filesystem");
ret = -EINVAL; goto error;
}
sync_blockdev(bdev);
list_for_each_entry(device, &fs_devices->devices, dev_list) { if (device->bdev == bdev) {
btrfs_err(fs_info, "target device is in the filesystem!");
ret = -EEXIST; goto error;
}
}
if (bdev_nr_bytes(bdev) < btrfs_device_get_total_bytes(srcdev)) {
btrfs_err(fs_info, "target device is smaller than source device!");
ret = -EINVAL; goto error;
}
device = btrfs_alloc_device(NULL, &devid, NULL, device_path); if (IS_ERR(device)) {
ret = PTR_ERR(device); goto error;
}
ret = lookup_bdev(device_path, &device->devt); if (ret) goto error;
path = btrfs_alloc_path(); if (!path) return -ENOMEM;
ret = btrfs_search_slot(trans, dev_root, &key, path, -1, 1); if (ret < 0) {
btrfs_warn(fs_info, "error %d while searching for dev_replace item!",
ret); return ret;
}
if (ret == 0 &&
btrfs_item_size(path->nodes[0], path->slots[0]) < sizeof(*ptr)) { /* * need to delete old one and insert a new one. * Since no attempt is made to recover any old state, if the * dev_replace state is 'running', the data on the target * drive is lost. * It would be possible to recover the state: just make sure * that the beginning of the item is never changed and always * contains all the essential information. Then read this * minimal set of information and use it as a base for the * new state.
*/
ret = btrfs_del_item(trans, dev_root, path); if (ret != 0) {
btrfs_warn(fs_info, "delete too small dev_replace item failed %d!",
ret); return ret;
}
ret = 1;
}
if (ret == 1) { /* need to insert a new item */
btrfs_release_path(path);
ret = btrfs_insert_empty_item(trans, dev_root, path,
&key, sizeof(*ptr)); if (ret < 0) {
btrfs_warn(fs_info, "insert dev_replace item failed %d!", ret); return ret;
}
}
/* Do not use "to_copy" on non zoned filesystem for now */ if (!btrfs_is_zoned(fs_info)) return 0;
mutex_lock(&fs_info->chunk_mutex);
/* Ensure we don't have pending new block group */
spin_lock(&fs_info->trans_lock); while (fs_info->running_transaction &&
!list_empty(&fs_info->running_transaction->dev_update_list)) {
spin_unlock(&fs_info->trans_lock);
mutex_unlock(&fs_info->chunk_mutex);
trans = btrfs_attach_transaction(root); if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
mutex_lock(&fs_info->chunk_mutex); if (ret == -ENOENT) {
spin_lock(&fs_info->trans_lock); continue;
} else { goto unlock;
}
}
ret = btrfs_commit_transaction(trans);
mutex_lock(&fs_info->chunk_mutex); if (ret) goto unlock;
num_extents = 0;
cur_extent = 0; for (i = 0; i < map->num_stripes; i++) { /* We have more device extent to copy */ if (srcdev != map->stripes[i].dev) continue;
num_extents++; if (physical == map->stripes[i].physical)
cur_extent = i;
}
btrfs_free_chunk_map(map);
if (num_extents > 1 && cur_extent < num_extents - 1) { /* * Has more stripes on this device. Keep this block group * readonly until we finish all the stripes.
*/ returnfalse;
}
/* Last stripe on this device */
clear_bit(BLOCK_GROUP_FLAG_TO_COPY, &cache->runtime_flags);
src_device = btrfs_find_device_by_devspec(fs_info, srcdevid,
srcdev_name); if (IS_ERR(src_device)) return PTR_ERR(src_device);
if (btrfs_pinned_by_swapfile(fs_info, src_device)) {
btrfs_warn(fs_info, "cannot replace device %s (devid %llu) due to active swapfile",
btrfs_dev_name(src_device), src_device->devid); return -ETXTBSY;
}
/* * Here we commit the transaction to make sure commit_total_bytes * of all the devices are updated.
*/
trans = btrfs_attach_transaction(root); if (!IS_ERR(trans)) {
ret = btrfs_commit_transaction(trans); if (ret) return ret;
} elseif (PTR_ERR(trans) != -ENOENT) { return PTR_ERR(trans);
}
ret = btrfs_init_dev_replace_tgtdev(fs_info, tgtdev_name,
src_device, &tgt_device); if (ret) return ret;
ret = mark_block_group_to_copy(fs_info, src_device); if (ret) return ret;
down_write(&dev_replace->rwsem);
dev_replace->replace_task = current; switch (dev_replace->replace_state) { case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED: case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED: break; case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
DEBUG_WARN("unexpected STARTED ot SUSPENDED dev-replace state");
ret = BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED;
up_write(&dev_replace->rwsem); goto leave;
}
btrfs_info(fs_info, "dev_replace from %s (devid %llu) to %s started",
btrfs_dev_name(src_device),
src_device->devid,
btrfs_dev_name(tgt_device));
/* * from now on, the writes to the srcdev are all duplicated to * go to the tgtdev as well (refer to btrfs_map_block()).
*/
dev_replace->replace_state = BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED;
dev_replace->time_started = ktime_get_real_seconds();
dev_replace->cursor_left = 0;
dev_replace->committed_cursor_left = 0;
dev_replace->cursor_left_last_write_of_item = 0;
dev_replace->cursor_right = 0;
dev_replace->is_valid = 1;
dev_replace->item_needs_writeback = 1;
atomic64_set(&dev_replace->num_write_errors, 0);
atomic64_set(&dev_replace->num_uncorrectable_read_errors, 0);
up_write(&dev_replace->rwsem);
ret = btrfs_sysfs_add_device(tgt_device); if (ret)
btrfs_err(fs_info, "kobj add dev failed %d", ret);
btrfs_wait_ordered_roots(fs_info, U64_MAX, NULL);
/* * Commit dev_replace state and reserve 1 item for it. * This is crucial to ensure we won't miss copying extents for new block * groups that are allocated after we started the device replace, and * must be done after setting up the device replace state.
*/
trans = btrfs_start_transaction(root, 1); if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
down_write(&dev_replace->rwsem);
dev_replace->replace_state =
BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED;
dev_replace->srcdev = NULL;
dev_replace->tgtdev = NULL;
up_write(&dev_replace->rwsem); goto leave;
}
ret = btrfs_commit_transaction(trans);
WARN_ON(ret);
/* the disk copy procedure reuses the scrub code */
ret = btrfs_scrub_dev(fs_info, src_device->devid, 0,
btrfs_device_get_total_bytes(src_device),
&dev_replace->scrub_progress, 0, 1);
ret = btrfs_dev_replace_finishing(fs_info, ret); if (ret == -EINPROGRESS)
ret = BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS;
if (memchr(args->start.tgtdev_name, 0, sizeof(args->start.tgtdev_name)) == NULL) return -ENAMETOOLONG;
return 0;
}
int btrfs_dev_replace_by_ioctl(struct btrfs_fs_info *fs_info, struct btrfs_ioctl_dev_replace_args *args)
{ int ret;
switch (args->start.cont_reading_from_srcdev_mode) { case BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS: case BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID: break; default: return -EINVAL;
}
ret = btrfs_check_replace_dev_names(args); if (ret < 0) return ret;
ret = btrfs_dev_replace_start(fs_info, args->start.tgtdev_name,
args->start.srcdevid,
args->start.srcdev_name,
args->start.cont_reading_from_srcdev_mode);
args->result = ret; /* don't warn if EINPROGRESS, someone else might be running scrub */ if (ret == BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS ||
ret == BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) return 0;
return ret;
}
/* * blocked until all in-flight bios operations are finished.
*/ staticvoid btrfs_rm_dev_replace_blocked(struct btrfs_fs_info *fs_info)
{
set_bit(BTRFS_FS_STATE_DEV_REPLACING, &fs_info->fs_state);
wait_event(fs_info->dev_replace.replace_wait, !percpu_counter_sum(
&fs_info->dev_replace.bio_counter));
}
/* * we have removed target device, it is safe to allow new bios request.
*/ staticvoid btrfs_rm_dev_replace_unblocked(struct btrfs_fs_info *fs_info)
{
clear_bit(BTRFS_FS_STATE_DEV_REPLACING, &fs_info->fs_state);
wake_up(&fs_info->dev_replace.replace_wait);
}
/* * When finishing the device replace, before swapping the source device with the * target device we must update the chunk allocation state in the target device, * as it is empty because replace works by directly copying the chunks and not * through the normal chunk allocation path.
*/ staticint btrfs_set_target_alloc_state(struct btrfs_device *srcdev, struct btrfs_device *tgtdev)
{ struct extent_state *cached_state = NULL;
u64 start = 0;
u64 found_start;
u64 found_end; int ret = 0;
/* * The chunk mutex must be held so that no new chunks can be created * while we are updating existing chunks. This guarantees we don't miss * any new chunk that gets created for a range that falls before the * range of the last chunk we processed.
*/
lockdep_assert_held(&fs_info->chunk_mutex);
for (int i = 0; i < map->num_stripes; i++) if (srcdev == map->stripes[i].dev)
map->stripes[i].dev = tgtdev;
if (cond_resched_rwlock_write(&fs_info->mapping_tree_lock)) {
map = btrfs_find_chunk_map_nolock(fs_info, next_start, U64_MAX); if (!map) break;
node = &map->rb_node; /* * Drop the lookup reference since we are holding the * lock in write mode and no one can remove the chunk * map from the tree and drop its tree reference.
*/
btrfs_free_chunk_map(map);
} else {
node = next;
}
}
write_unlock(&fs_info->mapping_tree_lock);
}
/* don't allow cancel or unmount to disturb the finishing procedure */
mutex_lock(&dev_replace->lock_finishing_cancel_unmount);
down_read(&dev_replace->rwsem); /* was the operation canceled, or is it finished? */ if (dev_replace->replace_state !=
BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
up_read(&dev_replace->rwsem);
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return 0;
}
/* * flush all outstanding I/O and inode extent mappings before the * copy operation is declared as being finished
*/
ret = btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); if (ret) {
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return ret;
}
btrfs_wait_ordered_roots(fs_info, U64_MAX, NULL);
/* * We have to use this loop approach because at this point src_device * has to be available for transaction commit to complete, yet new * chunks shouldn't be allocated on the device.
*/ while (1) {
trans = btrfs_start_transaction(root, 0); if (IS_ERR(trans)) {
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return PTR_ERR(trans);
}
ret = btrfs_commit_transaction(trans);
WARN_ON(ret);
/* Prevent write_all_supers() during the finishing procedure */
mutex_lock(&fs_devices->device_list_mutex); /* Prevent new chunks being allocated on the source device */
mutex_lock(&fs_info->chunk_mutex);
/* * Update allocation state in the new device and replace the old device * with the new one in the mapping tree.
*/ if (!scrub_ret) {
scrub_ret = btrfs_set_target_alloc_state(src_device, tgt_device); if (scrub_ret) goto error;
btrfs_dev_replace_update_device_in_mapping_tree(fs_info,
src_device,
tgt_device);
} else { if (scrub_ret != -ECANCELED)
btrfs_err(fs_info, "btrfs_scrub_dev(%s, %llu, %s) failed %d",
btrfs_dev_name(src_device),
src_device->devid,
btrfs_dev_name(tgt_device), scrub_ret);
error:
up_write(&dev_replace->rwsem);
mutex_unlock(&fs_info->chunk_mutex);
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_rm_dev_replace_unblocked(fs_info);
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
/* * Increment dev_stats_ccnt so that btrfs_run_dev_stats() will * update on-disk dev stats value during commit transaction
*/
atomic_inc(&tgt_device->dev_stats_ccnt);
/* * this is again a consistent state where no dev_replace procedure * is running, the target device is part of the filesystem, the * source device is not part of the filesystem anymore and its 1st * superblock is scratched out so that it is no longer marked to * belong to this filesystem.
*/
mutex_unlock(&fs_info->chunk_mutex);
mutex_unlock(&fs_devices->device_list_mutex);
/* replace the sysfs entry */
btrfs_sysfs_remove_device(src_device);
btrfs_sysfs_update_devid(tgt_device); if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &src_device->dev_state))
btrfs_scratch_superblocks(fs_info, src_device);
/* write back the superblocks */
trans = btrfs_start_transaction(root, 0); if (!IS_ERR(trans))
btrfs_commit_transaction(trans);
/* * Read progress of device replace status according to the state and last * stored position. The value format is the same as for * btrfs_dev_replace::progress_1000
*/ static u64 btrfs_dev_replace_progress(struct btrfs_fs_info *fs_info)
{ struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace;
u64 ret = 0;
switch (dev_replace->replace_state) { case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
ret = 0; break; case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
ret = 1000; break; case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
ret = div64_u64(dev_replace->cursor_left,
div_u64(btrfs_device_get_total_bytes(
dev_replace->srcdev), 1000)); break;
}
mutex_lock(&dev_replace->lock_finishing_cancel_unmount);
down_write(&dev_replace->rwsem); switch (dev_replace->replace_state) { case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED: case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED;
up_write(&dev_replace->rwsem); break; case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
tgt_device = dev_replace->tgtdev;
src_device = dev_replace->srcdev;
up_write(&dev_replace->rwsem);
ret = btrfs_scrub_cancel(fs_info); if (ret < 0) {
result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED;
} else {
result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR; /* * btrfs_dev_replace_finishing() will handle the * cleanup part
*/
btrfs_info(fs_info, "dev_replace from %s (devid %llu) to %s canceled",
btrfs_dev_name(src_device), src_device->devid,
btrfs_dev_name(tgt_device));
} break; case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED: /* * Scrub doing the replace isn't running so we need to do the * cleanup step of btrfs_dev_replace_finishing() here
*/
result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR;
tgt_device = dev_replace->tgtdev;
src_device = dev_replace->srcdev;
dev_replace->tgtdev = NULL;
dev_replace->srcdev = NULL;
dev_replace->replace_state =
BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED;
dev_replace->time_stopped = ktime_get_real_seconds();
dev_replace->item_needs_writeback = 1;
up_write(&dev_replace->rwsem);
/* Scrub for replace must not be running in suspended state */
btrfs_scrub_cancel(fs_info);
trans = btrfs_start_transaction(root, 0); if (IS_ERR(trans)) {
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return PTR_ERR(trans);
}
ret = btrfs_commit_transaction(trans);
WARN_ON(ret);
btrfs_info(fs_info, "suspended dev_replace from %s (devid %llu) to %s canceled",
btrfs_dev_name(src_device), src_device->devid,
btrfs_dev_name(tgt_device));
if (tgt_device)
btrfs_destroy_dev_replace_tgtdev(tgt_device); break; default:
up_write(&dev_replace->rwsem);
result = -EINVAL;
}
switch (dev_replace->replace_state) { case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED: case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED: case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED: break; case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
dev_replace->replace_state =
BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED;
dev_replace->time_stopped = ktime_get_real_seconds();
dev_replace->item_needs_writeback = 1;
btrfs_info(fs_info, "suspending dev_replace for unmount"); break;
}
/* resume dev_replace procedure that was interrupted by unmount */ 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;
down_write(&dev_replace->rwsem);
switch (dev_replace->replace_state) { case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED: case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
up_write(&dev_replace->rwsem); return 0; case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED: break; case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
dev_replace->replace_state =
BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED; break;
} if (!dev_replace->tgtdev || !dev_replace->tgtdev->bdev) {
btrfs_info(fs_info, "cannot continue dev_replace, tgtdev is missing");
btrfs_info(fs_info, "you may cancel the operation after 'mount -o degraded'");
dev_replace->replace_state =
BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED;
up_write(&dev_replace->rwsem); return 0;
}
up_write(&dev_replace->rwsem);
/* * This could collide with a paused balance, but the exclusive op logic * should never allow both to start and pause. We don't want to allow * dev-replace to start anyway.
*/ if (!btrfs_exclop_start(fs_info, BTRFS_EXCLOP_DEV_REPLACE)) {
down_write(&dev_replace->rwsem);
dev_replace->replace_state =
BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED;
up_write(&dev_replace->rwsem);
btrfs_info(fs_info, "cannot resume dev-replace, other exclusive operation running"); return 0;
}
ret = btrfs_scrub_dev(fs_info, dev_replace->srcdev->devid,
dev_replace->committed_cursor_left,
btrfs_device_get_total_bytes(dev_replace->srcdev),
&dev_replace->scrub_progress, 0, 1);
ret = btrfs_dev_replace_finishing(fs_info, ret);
WARN_ON(ret && ret != -ECANCELED);
btrfs_exclop_finish(fs_info); return 0;
}
bool __pure btrfs_dev_replace_is_ongoing(struct btrfs_dev_replace *dev_replace)
{ if (!dev_replace->is_valid) returnfalse;
switch (dev_replace->replace_state) { case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED: case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED: returnfalse; case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED: case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED: /* * return true even if tgtdev is missing (this is * something that can happen if the dev_replace * procedure is suspended by an umount and then * the tgtdev is missing (or "btrfs dev scan") was * not called and the filesystem is remounted * in degraded state. This does not stop the * dev_replace procedure. It needs to be canceled * manually if the cancellation is wanted.
*/ break;
} returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.