diff options
Diffstat (limited to 'fs/btrfs/dev-replace.c')
| -rw-r--r-- | fs/btrfs/dev-replace.c | 184 | 
1 files changed, 184 insertions, 0 deletions
| diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index bc73f798ce3a..3a9c1e046ebe 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -22,6 +22,7 @@  #include "dev-replace.h"  #include "sysfs.h"  #include "zoned.h" +#include "block-group.h"  /*   * Device replace overview @@ -459,6 +460,185 @@ static char* btrfs_dev_name(struct btrfs_device *device)  		return rcu_str_deref(device->name);  } +static int mark_block_group_to_copy(struct btrfs_fs_info *fs_info, +				    struct btrfs_device *src_dev) +{ +	struct btrfs_path *path; +	struct btrfs_key key; +	struct btrfs_key found_key; +	struct btrfs_root *root = fs_info->dev_root; +	struct btrfs_dev_extent *dev_extent = NULL; +	struct btrfs_block_group *cache; +	struct btrfs_trans_handle *trans; +	int ret = 0; +	u64 chunk_offset; + +	/* 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; + +		spin_lock(&fs_info->trans_lock); +	} +	spin_unlock(&fs_info->trans_lock); + +	path = btrfs_alloc_path(); +	if (!path) { +		ret = -ENOMEM; +		goto unlock; +	} + +	path->reada = READA_FORWARD; +	path->search_commit_root = 1; +	path->skip_locking = 1; + +	key.objectid = src_dev->devid; +	key.type = BTRFS_DEV_EXTENT_KEY; +	key.offset = 0; + +	ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); +	if (ret < 0) +		goto free_path; +	if (ret > 0) { +		if (path->slots[0] >= +		    btrfs_header_nritems(path->nodes[0])) { +			ret = btrfs_next_leaf(root, path); +			if (ret < 0) +				goto free_path; +			if (ret > 0) { +				ret = 0; +				goto free_path; +			} +		} else { +			ret = 0; +		} +	} + +	while (1) { +		struct extent_buffer *leaf = path->nodes[0]; +		int slot = path->slots[0]; + +		btrfs_item_key_to_cpu(leaf, &found_key, slot); + +		if (found_key.objectid != src_dev->devid) +			break; + +		if (found_key.type != BTRFS_DEV_EXTENT_KEY) +			break; + +		if (found_key.offset < key.offset) +			break; + +		dev_extent = btrfs_item_ptr(leaf, slot, struct btrfs_dev_extent); + +		chunk_offset = btrfs_dev_extent_chunk_offset(leaf, dev_extent); + +		cache = btrfs_lookup_block_group(fs_info, chunk_offset); +		if (!cache) +			goto skip; + +		spin_lock(&cache->lock); +		cache->to_copy = 1; +		spin_unlock(&cache->lock); + +		btrfs_put_block_group(cache); + +skip: +		ret = btrfs_next_item(root, path); +		if (ret != 0) { +			if (ret > 0) +				ret = 0; +			break; +		} +	} + +free_path: +	btrfs_free_path(path); +unlock: +	mutex_unlock(&fs_info->chunk_mutex); + +	return ret; +} + +bool btrfs_finish_block_group_to_copy(struct btrfs_device *srcdev, +				      struct btrfs_block_group *cache, +				      u64 physical) +{ +	struct btrfs_fs_info *fs_info = cache->fs_info; +	struct extent_map *em; +	struct map_lookup *map; +	u64 chunk_offset = cache->start; +	int num_extents, cur_extent; +	int i; + +	/* Do not use "to_copy" on non zoned filesystem for now */ +	if (!btrfs_is_zoned(fs_info)) +		return true; + +	spin_lock(&cache->lock); +	if (cache->removed) { +		spin_unlock(&cache->lock); +		return true; +	} +	spin_unlock(&cache->lock); + +	em = btrfs_get_chunk_map(fs_info, chunk_offset, 1); +	ASSERT(!IS_ERR(em)); +	map = em->map_lookup; + +	num_extents = 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; +	} + +	free_extent_map(em); + +	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. +		 */ +		return false; +	} + +	/* Last stripe on this device */ +	spin_lock(&cache->lock); +	cache->to_copy = 0; +	spin_unlock(&cache->lock); + +	return true; +} +  static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,  		const char *tgtdev_name, u64 srcdevid, const char *srcdev_name,  		int read_src) @@ -500,6 +680,10 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,  	if (ret)  		return ret; +	ret = mark_block_group_to_copy(fs_info, src_device); +	if (ret) +		return ret; +  	down_write(&dev_replace->rwsem);  	switch (dev_replace->replace_state) {  	case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED: |