diff options
Diffstat (limited to 'fs/btrfs/volumes.c')
| -rw-r--r-- | fs/btrfs/volumes.c | 119 | 
1 files changed, 83 insertions, 36 deletions
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index b8fc2fa91fd..c48214ef5c0 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -350,7 +350,7 @@ static noinline int device_list_add(const char *path,  		INIT_LIST_HEAD(&device->dev_alloc_list);  		mutex_lock(&fs_devices->device_list_mutex); -		list_add(&device->dev_list, &fs_devices->devices); +		list_add_rcu(&device->dev_list, &fs_devices->devices);  		mutex_unlock(&fs_devices->device_list_mutex);  		device->fs_devices = fs_devices; @@ -393,7 +393,7 @@ static struct btrfs_fs_devices *clone_fs_devices(struct btrfs_fs_devices *orig)  	fs_devices->latest_trans = orig->latest_trans;  	memcpy(fs_devices->fsid, orig->fsid, sizeof(fs_devices->fsid)); -	mutex_lock(&orig->device_list_mutex); +	/* We have held the volume lock, it is safe to get the devices. */  	list_for_each_entry(orig_dev, &orig->devices, dev_list) {  		device = kzalloc(sizeof(*device), GFP_NOFS);  		if (!device) @@ -416,10 +416,8 @@ static struct btrfs_fs_devices *clone_fs_devices(struct btrfs_fs_devices *orig)  		device->fs_devices = fs_devices;  		fs_devices->num_devices++;  	} -	mutex_unlock(&orig->device_list_mutex);  	return fs_devices;  error: -	mutex_unlock(&orig->device_list_mutex);  	free_fs_devices(fs_devices);  	return ERR_PTR(-ENOMEM);  } @@ -430,7 +428,7 @@ int btrfs_close_extra_devices(struct btrfs_fs_devices *fs_devices)  	mutex_lock(&uuid_mutex);  again: -	mutex_lock(&fs_devices->device_list_mutex); +	/* This is the initialized path, it is safe to release the devices. */  	list_for_each_entry_safe(device, next, &fs_devices->devices, dev_list) {  		if (device->in_fs_metadata)  			continue; @@ -450,7 +448,6 @@ again:  		kfree(device->name);  		kfree(device);  	} -	mutex_unlock(&fs_devices->device_list_mutex);  	if (fs_devices->seed) {  		fs_devices = fs_devices->seed; @@ -461,6 +458,29 @@ again:  	return 0;  } +static void __free_device(struct work_struct *work) +{ +	struct btrfs_device *device; + +	device = container_of(work, struct btrfs_device, rcu_work); + +	if (device->bdev) +		blkdev_put(device->bdev, device->mode); + +	kfree(device->name); +	kfree(device); +} + +static void free_device(struct rcu_head *head) +{ +	struct btrfs_device *device; + +	device = container_of(head, struct btrfs_device, rcu); + +	INIT_WORK(&device->rcu_work, __free_device); +	schedule_work(&device->rcu_work); +} +  static int __btrfs_close_devices(struct btrfs_fs_devices *fs_devices)  {  	struct btrfs_device *device; @@ -468,20 +488,32 @@ static int __btrfs_close_devices(struct btrfs_fs_devices *fs_devices)  	if (--fs_devices->opened > 0)  		return 0; +	mutex_lock(&fs_devices->device_list_mutex);  	list_for_each_entry(device, &fs_devices->devices, dev_list) { -		if (device->bdev) { -			blkdev_put(device->bdev, device->mode); +		struct btrfs_device *new_device; + +		if (device->bdev)  			fs_devices->open_devices--; -		} +  		if (device->writeable) {  			list_del_init(&device->dev_alloc_list);  			fs_devices->rw_devices--;  		} -		device->bdev = NULL; -		device->writeable = 0; -		device->in_fs_metadata = 0; +		new_device = kmalloc(sizeof(*new_device), GFP_NOFS); +		BUG_ON(!new_device); +		memcpy(new_device, device, sizeof(*new_device)); +		new_device->name = kstrdup(device->name, GFP_NOFS); +		BUG_ON(!new_device->name); +		new_device->bdev = NULL; +		new_device->writeable = 0; +		new_device->in_fs_metadata = 0; +		list_replace_rcu(&device->dev_list, &new_device->dev_list); + +		call_rcu(&device->rcu, free_device);  	} +	mutex_unlock(&fs_devices->device_list_mutex); +  	WARN_ON(fs_devices->open_devices);  	WARN_ON(fs_devices->rw_devices);  	fs_devices->opened = 0; @@ -584,6 +616,7 @@ static int __btrfs_open_devices(struct btrfs_fs_devices *fs_devices,  			list_add(&device->dev_alloc_list,  				 &fs_devices->alloc_list);  		} +		brelse(bh);  		continue;  error_brelse: @@ -933,14 +966,14 @@ static int btrfs_free_dev_extent(struct btrfs_trans_handle *trans,  	if (ret > 0) {  		ret = btrfs_previous_item(root, path, key.objectid,  					  BTRFS_DEV_EXTENT_KEY); -		BUG_ON(ret); +		if (ret) +			goto out;  		leaf = path->nodes[0];  		btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);  		extent = btrfs_item_ptr(leaf, path->slots[0],  					struct btrfs_dev_extent);  		BUG_ON(found_key.offset > start || found_key.offset +  		       btrfs_dev_extent_length(leaf, extent) < start); -		ret = 0;  	} else if (ret == 0) {  		leaf = path->nodes[0];  		extent = btrfs_item_ptr(leaf, path->slots[0], @@ -951,8 +984,8 @@ static int btrfs_free_dev_extent(struct btrfs_trans_handle *trans,  	if (device->bytes_used > 0)  		device->bytes_used -= btrfs_dev_extent_length(leaf, extent);  	ret = btrfs_del_item(trans, root, path); -	BUG_ON(ret); +out:  	btrfs_free_path(path);  	return ret;  } @@ -1187,11 +1220,13 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)  	struct block_device *bdev;  	struct buffer_head *bh = NULL;  	struct btrfs_super_block *disk_super; +	struct btrfs_fs_devices *cur_devices;  	u64 all_avail;  	u64 devid;  	u64 num_devices;  	u8 *dev_uuid;  	int ret = 0; +	bool clear_super = false;  	mutex_lock(&uuid_mutex);  	mutex_lock(&root->fs_info->volume_mutex); @@ -1222,14 +1257,16 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)  		device = NULL;  		devices = &root->fs_info->fs_devices->devices; -		mutex_lock(&root->fs_info->fs_devices->device_list_mutex); +		/* +		 * It is safe to read the devices since the volume_mutex +		 * is held. +		 */  		list_for_each_entry(tmp, devices, dev_list) {  			if (tmp->in_fs_metadata && !tmp->bdev) {  				device = tmp;  				break;  			}  		} -		mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);  		bdev = NULL;  		bh = NULL;  		disk_super = NULL; @@ -1271,8 +1308,11 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)  	}  	if (device->writeable) { +		lock_chunks(root);  		list_del_init(&device->dev_alloc_list); +		unlock_chunks(root);  		root->fs_info->fs_devices->rw_devices--; +		clear_super = true;  	}  	ret = btrfs_shrink_device(device, 0); @@ -1291,9 +1331,10 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)  	 * the device list while someone else is writing out all  	 * the device supers.  	 */ + +	cur_devices = device->fs_devices;  	mutex_lock(&root->fs_info->fs_devices->device_list_mutex); -	list_del_init(&device->dev_list); -	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex); +	list_del_rcu(&device->dev_list);  	device->fs_devices->num_devices--; @@ -1307,34 +1348,36 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)  	if (device->bdev == root->fs_info->fs_devices->latest_bdev)  		root->fs_info->fs_devices->latest_bdev = next_device->bdev; -	if (device->bdev) { -		blkdev_put(device->bdev, device->mode); -		device->bdev = NULL; +	if (device->bdev)  		device->fs_devices->open_devices--; -	} + +	call_rcu(&device->rcu, free_device); +	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);  	num_devices = btrfs_super_num_devices(&root->fs_info->super_copy) - 1;  	btrfs_set_super_num_devices(&root->fs_info->super_copy, num_devices); -	if (device->fs_devices->open_devices == 0) { +	if (cur_devices->open_devices == 0) {  		struct btrfs_fs_devices *fs_devices;  		fs_devices = root->fs_info->fs_devices;  		while (fs_devices) { -			if (fs_devices->seed == device->fs_devices) +			if (fs_devices->seed == cur_devices)  				break;  			fs_devices = fs_devices->seed;  		} -		fs_devices->seed = device->fs_devices->seed; -		device->fs_devices->seed = NULL; -		__btrfs_close_devices(device->fs_devices); -		free_fs_devices(device->fs_devices); +		fs_devices->seed = cur_devices->seed; +		cur_devices->seed = NULL; +		lock_chunks(root); +		__btrfs_close_devices(cur_devices); +		unlock_chunks(root); +		free_fs_devices(cur_devices);  	}  	/*  	 * at this point, the device is zero sized.  We want to  	 * remove it from the devices list and zero out the old super  	 */ -	if (device->writeable) { +	if (clear_super) {  		/* make sure this device isn't detected as part of  		 * the FS anymore  		 */ @@ -1343,8 +1386,6 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)  		sync_dirty_buffer(bh);  	} -	kfree(device->name); -	kfree(device);  	ret = 0;  error_brelse: @@ -1358,8 +1399,10 @@ out:  	return ret;  error_undo:  	if (device->writeable) { +		lock_chunks(root);  		list_add(&device->dev_alloc_list,  			 &root->fs_info->fs_devices->alloc_list); +		unlock_chunks(root);  		root->fs_info->fs_devices->rw_devices++;  	}  	goto error_brelse; @@ -1399,7 +1442,12 @@ static int btrfs_prepare_sprout(struct btrfs_trans_handle *trans,  	INIT_LIST_HEAD(&seed_devices->devices);  	INIT_LIST_HEAD(&seed_devices->alloc_list);  	mutex_init(&seed_devices->device_list_mutex); -	list_splice_init(&fs_devices->devices, &seed_devices->devices); + +	mutex_lock(&root->fs_info->fs_devices->device_list_mutex); +	list_splice_init_rcu(&fs_devices->devices, &seed_devices->devices, +			      synchronize_rcu); +	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex); +  	list_splice_init(&fs_devices->alloc_list, &seed_devices->alloc_list);  	list_for_each_entry(device, &seed_devices->devices, dev_list) {  		device->fs_devices = seed_devices; @@ -1596,7 +1644,7 @@ int btrfs_init_new_device(struct btrfs_root *root, char *device_path)  	 * half setup  	 */  	mutex_lock(&root->fs_info->fs_devices->device_list_mutex); -	list_add(&device->dev_list, &root->fs_info->fs_devices->devices); +	list_add_rcu(&device->dev_list, &root->fs_info->fs_devices->devices);  	list_add(&device->dev_alloc_list,  		 &root->fs_info->fs_devices->alloc_list);  	root->fs_info->fs_devices->num_devices++; @@ -1754,10 +1802,9 @@ static int btrfs_free_chunk(struct btrfs_trans_handle *trans,  	BUG_ON(ret);  	ret = btrfs_del_item(trans, root, path); -	BUG_ON(ret);  	btrfs_free_path(path); -	return 0; +	return ret;  }  static int btrfs_del_sys_chunk(struct btrfs_root *root, u64 chunk_objectid, u64  |