aboutsummaryrefslogtreecommitdiffstats
path: root/fs/btrfs
diff options
context:
space:
mode:
authorFilipe David Borba Manana <fdmanana@gmail.com>2013-08-09 10:41:36 -0400
committerChris Mason <chris.mason@fusionio.com>2013-09-01 08:16:24 -0400
commitd73068018419c5999f594a52998621947dc1f7d0 (patch)
treed5de1cca9d49b6af6ddfc21425d65393bb55c97e /fs/btrfs
parentb8d0c69b9469ffd33df30fee3e990f2d4aa68a09 (diff)
Btrfs: fix race between removing a dev and writing sbs
This change fixes an issue when removing a device and writing all super blocks run simultaneously. Here's the steps necessary for the issue to happen: 1) disk-io.c:write_all_supers() gets a number of N devices from the super_copy, so it will not panic if it fails to write super blocks for N - 1 devices; 2) Then it tries to acquire the device_list_mutex, but blocks because volumes.c:btrfs_rm_device() got it first; 3) btrfs_rm_device() removes the device from the list, then unlocks the mutex and after the unlock it updates the number of devices in super_copy to N - 1. 4) write_all_supers() finally acquires the mutex, iterates over all the devices in the list and gets N - 1 errors, that is, it failed to write super blocks to all the devices; 5) Because write_all_supers() thinks there are a total of N devices, it considers N - 1 errors to be ok, and therefore won't panic. So this change just makes sure that write_all_supers() reads the number of devices from super_copy after it acquires the device_list_mutex. Conversely, it changes btrfs_rm_device() to update the number of devices in super_copy before it releases the device list mutex. The code path to add a new device (volumes.c:btrfs_init_new_device), already has the right behaviour: it updates the number of devices in super_copy while holding the device_list_mutex. The only code path that doesn't lock the device list mutex before updating the number of devices in the super copy is disk-io.c:next_root_backup(), called by open_ctree() during mount time where concurrency issues can't happen. Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com> Signed-off-by: Josef Bacik <jbacik@fusionio.com> Signed-off-by: Chris Mason <chris.mason@fusionio.com>
Diffstat (limited to 'fs/btrfs')
-rw-r--r--fs/btrfs/disk-io.c2
-rw-r--r--fs/btrfs/volumes.c8
2 files changed, 7 insertions, 3 deletions
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 940bc486a0f8..6d642e487229 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3365,7 +3365,6 @@ static int write_all_supers(struct btrfs_root *root, int max_mirrors)
3365 int total_errors = 0; 3365 int total_errors = 0;
3366 u64 flags; 3366 u64 flags;
3367 3367
3368 max_errors = btrfs_super_num_devices(root->fs_info->super_copy) - 1;
3369 do_barriers = !btrfs_test_opt(root, NOBARRIER); 3368 do_barriers = !btrfs_test_opt(root, NOBARRIER);
3370 backup_super_roots(root->fs_info); 3369 backup_super_roots(root->fs_info);
3371 3370
@@ -3374,6 +3373,7 @@ static int write_all_supers(struct btrfs_root *root, int max_mirrors)
3374 3373
3375 mutex_lock(&root->fs_info->fs_devices->device_list_mutex); 3374 mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
3376 head = &root->fs_info->fs_devices->devices; 3375 head = &root->fs_info->fs_devices->devices;
3376 max_errors = btrfs_super_num_devices(root->fs_info->super_copy) - 1;
3377 3377
3378 if (do_barriers) { 3378 if (do_barriers) {
3379 ret = barrier_all_devices(root->fs_info); 3379 ret = barrier_all_devices(root->fs_info);
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 34068b887f14..3f1c2c200691 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -1620,7 +1620,11 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
1620 /* 1620 /*
1621 * the device list mutex makes sure that we don't change 1621 * the device list mutex makes sure that we don't change
1622 * the device list while someone else is writing out all 1622 * the device list while someone else is writing out all
1623 * the device supers. 1623 * the device supers. Whoever is writing all supers, should
1624 * lock the device list mutex before getting the number of
1625 * devices in the super block (super_copy). Conversely,
1626 * whoever updates the number of devices in the super block
1627 * (super_copy) should hold the device list mutex.
1624 */ 1628 */
1625 1629
1626 cur_devices = device->fs_devices; 1630 cur_devices = device->fs_devices;
@@ -1644,10 +1648,10 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
1644 device->fs_devices->open_devices--; 1648 device->fs_devices->open_devices--;
1645 1649
1646 call_rcu(&device->rcu, free_device); 1650 call_rcu(&device->rcu, free_device);
1647 mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
1648 1651
1649 num_devices = btrfs_super_num_devices(root->fs_info->super_copy) - 1; 1652 num_devices = btrfs_super_num_devices(root->fs_info->super_copy) - 1;
1650 btrfs_set_super_num_devices(root->fs_info->super_copy, num_devices); 1653 btrfs_set_super_num_devices(root->fs_info->super_copy, num_devices);
1654 mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
1651 1655
1652 if (cur_devices->open_devices == 0) { 1656 if (cur_devices->open_devices == 0) {
1653 struct btrfs_fs_devices *fs_devices; 1657 struct btrfs_fs_devices *fs_devices;