diff options
Diffstat (limited to 'fs/btrfs/dev-replace.c')
| -rw-r--r-- | fs/btrfs/dev-replace.c | 118 | 
1 files changed, 77 insertions, 41 deletions
| diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index db93909b25e0..4a0243cb9d97 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -64,10 +64,6 @@  static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,  				       int scrub_ret); -static void btrfs_dev_replace_update_device_in_mapping_tree( -						struct btrfs_fs_info *fs_info, -						struct btrfs_device *srcdev, -						struct btrfs_device *tgtdev);  static int btrfs_dev_replace_kthread(void *data);  int btrfs_init_dev_replace(struct btrfs_fs_info *fs_info) @@ -224,13 +220,12 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,  {  	struct btrfs_device *device;  	struct block_device *bdev; -	struct list_head *devices;  	struct rcu_string *name;  	u64 devid = BTRFS_DEV_REPLACE_DEVID;  	int ret = 0;  	*device_out = NULL; -	if (fs_info->fs_devices->seeding) { +	if (srcdev->fs_devices->seeding) {  		btrfs_err(fs_info, "the filesystem is a seed filesystem!");  		return -EINVAL;  	} @@ -244,8 +239,7 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,  	sync_blockdev(bdev); -	devices = &fs_info->fs_devices->devices; -	list_for_each_entry(device, devices, dev_list) { +	list_for_each_entry(device, &fs_info->fs_devices->devices, dev_list) {  		if (device->bdev == bdev) {  			btrfs_err(fs_info,  				  "target device is in the filesystem!"); @@ -512,7 +506,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,  	atomic64_set(&dev_replace->num_uncorrectable_read_errors, 0);  	up_write(&dev_replace->rwsem); -	ret = btrfs_sysfs_add_devices_dir(tgt_device->fs_devices, tgt_device); +	ret = btrfs_sysfs_add_device(tgt_device);  	if (ret)  		btrfs_err(fs_info, "kobj add dev failed %d", ret); @@ -599,6 +593,63 @@ static void btrfs_rm_dev_replace_unblocked(struct btrfs_fs_info *fs_info)  	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. + */ +static int 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; + +	lockdep_assert_held(&srcdev->fs_info->chunk_mutex); + +	while (!find_first_extent_bit(&srcdev->alloc_state, start, +				      &found_start, &found_end, +				      CHUNK_ALLOCATED, &cached_state)) { +		ret = set_extent_bits(&tgtdev->alloc_state, found_start, +				      found_end, CHUNK_ALLOCATED); +		if (ret) +			break; +		start = found_end + 1; +	} + +	free_extent_state(cached_state); +	return ret; +} + +static void btrfs_dev_replace_update_device_in_mapping_tree( +						struct btrfs_fs_info *fs_info, +						struct btrfs_device *srcdev, +						struct btrfs_device *tgtdev) +{ +	struct extent_map_tree *em_tree = &fs_info->mapping_tree; +	struct extent_map *em; +	struct map_lookup *map; +	u64 start = 0; +	int i; + +	write_lock(&em_tree->lock); +	do { +		em = lookup_extent_mapping(em_tree, start, (u64)-1); +		if (!em) +			break; +		map = em->map_lookup; +		for (i = 0; i < map->num_stripes; i++) +			if (srcdev == map->stripes[i].dev) +				map->stripes[i].dev = tgtdev; +		start = em->start + em->len; +		free_extent_map(em); +	} while (start); +	write_unlock(&em_tree->lock); +} +  static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,  				       int scrub_ret)  { @@ -630,7 +681,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,  	 * 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, -1); +	ret = btrfs_start_delalloc_roots(fs_info, U64_MAX);  	if (ret) {  		mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);  		return ret; @@ -673,8 +724,14 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,  	dev_replace->time_stopped = ktime_get_real_seconds();  	dev_replace->item_needs_writeback = 1; -	/* replace old device with new one in mapping tree */ +	/* +	 * 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); @@ -685,6 +742,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,  				 btrfs_dev_name(src_device),  				 src_device->devid,  				 rcu_str_deref(tgt_device->name), scrub_ret); +error:  		up_write(&dev_replace->rwsem);  		mutex_unlock(&fs_info->chunk_mutex);  		mutex_unlock(&fs_info->fs_devices->device_list_mutex); @@ -743,9 +801,11 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,  	mutex_unlock(&fs_info->fs_devices->device_list_mutex);  	/* replace the sysfs entry */ -	btrfs_sysfs_remove_devices_dir(fs_info->fs_devices, src_device); +	btrfs_sysfs_remove_device(src_device);  	btrfs_sysfs_update_devid(tgt_device); -	btrfs_rm_dev_replace_free_srcdev(src_device); +	if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &src_device->dev_state)) +		btrfs_scratch_superblocks(fs_info, src_device->bdev, +					  src_device->name->str);  	/* write back the superblocks */  	trans = btrfs_start_transaction(root, 0); @@ -754,33 +814,9 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,  	mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); -	return 0; -} - -static void btrfs_dev_replace_update_device_in_mapping_tree( -						struct btrfs_fs_info *fs_info, -						struct btrfs_device *srcdev, -						struct btrfs_device *tgtdev) -{ -	struct extent_map_tree *em_tree = &fs_info->mapping_tree; -	struct extent_map *em; -	struct map_lookup *map; -	u64 start = 0; -	int i; +	btrfs_rm_dev_replace_free_srcdev(src_device); -	write_lock(&em_tree->lock); -	do { -		em = lookup_extent_mapping(em_tree, start, (u64)-1); -		if (!em) -			break; -		map = em->map_lookup; -		for (i = 0; i < map->num_stripes; i++) -			if (srcdev == map->stripes[i].dev) -				map->stripes[i].dev = tgtdev; -		start = em->start + em->len; -		free_extent_map(em); -	} while (start); -	write_unlock(&em_tree->lock); +	return 0;  }  /* @@ -983,7 +1019,7 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info)  	 * should never allow both to start and pause. We don't want to allow  	 * dev-replace to start anyway.  	 */ -	if (test_and_set_bit(BTRFS_FS_EXCL_OP, &fs_info->flags)) { +	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; @@ -1020,7 +1056,7 @@ static int btrfs_dev_replace_kthread(void *data)  	ret = btrfs_dev_replace_finishing(fs_info, ret);  	WARN_ON(ret && ret != -ECANCELED); -	clear_bit(BTRFS_FS_EXCL_OP, &fs_info->flags); +	btrfs_exclop_finish(fs_info);  	return 0;  } |