diff options
author | Filipe David Borba Manana <fdmanana@gmail.com> | 2013-08-09 10:41:36 -0400 |
---|---|---|
committer | Chris Mason <chris.mason@fusionio.com> | 2013-09-01 08:16:24 -0400 |
commit | d73068018419c5999f594a52998621947dc1f7d0 (patch) | |
tree | d5de1cca9d49b6af6ddfc21425d65393bb55c97e /fs/btrfs | |
parent | b8d0c69b9469ffd33df30fee3e990f2d4aa68a09 (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.c | 2 | ||||
-rw-r--r-- | fs/btrfs/volumes.c | 8 |
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; |