btrfs: clean the old superblocks before freeing the device
authorJeff Mahoney <jeffm@suse.com>
Tue, 20 Sep 2016 12:50:21 +0000 (08:50 -0400)
committerDavid Sterba <dsterba@suse.com>
Mon, 26 Sep 2016 16:08:44 +0000 (18:08 +0200)
btrfs_rm_device frees the block device but then re-opens it using
the saved device name.  A race exists between the close and the
re-open that allows the block size to be changed.  The result
is getting stuck forever in the reclaim loop in __getblk_slow.

This patch moves the superblock cleanup before closing the block
device, which is also consistent with other callers.  We also don't
need a private copy of dev_name as the whole routine operates under
the uuid_mutex.

Signed-off-by: Jeff Mahoney <jeffm@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/volumes.c

index c356ce332229fbf81d82ca877321647b90789c33..be2c8b35c3aef239e3996f778bf665d930893417 100644 (file)
@@ -1846,7 +1846,6 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path, u64 devid)
        u64 num_devices;
        int ret = 0;
        bool clear_super = false;
-       char *dev_name = NULL;
 
        mutex_lock(&uuid_mutex);
 
@@ -1882,11 +1881,6 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path, u64 devid)
                list_del_init(&device->dev_alloc_list);
                device->fs_devices->rw_devices--;
                unlock_chunks(root);
-               dev_name = kstrdup(device->name->str, GFP_KERNEL);
-               if (!dev_name) {
-                       ret = -ENOMEM;
-                       goto error_undo;
-               }
                clear_super = true;
        }
 
@@ -1936,14 +1930,21 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path, u64 devid)
                btrfs_sysfs_rm_device_link(root->fs_info->fs_devices, device);
        }
 
-       btrfs_close_bdev(device);
-
-       call_rcu(&device->rcu, free_device);
-
        num_devices = btrfs_super_num_devices(root->fs_info->super_copy) - 1;
        btrfs_set_super_num_devices(root->fs_info->super_copy, num_devices);
        mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
 
+       /*
+        * at this point, the device is zero sized and detached from
+        * the devices list.  All that's left is to zero out the old
+        * supers and free the device.
+        */
+       if (device->writeable)
+               btrfs_scratch_superblocks(device->bdev, device->name->str);
+
+       btrfs_close_bdev(device);
+       call_rcu(&device->rcu, free_device);
+
        if (cur_devices->open_devices == 0) {
                struct btrfs_fs_devices *fs_devices;
                fs_devices = root->fs_info->fs_devices;
@@ -1962,24 +1963,7 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path, u64 devid)
        root->fs_info->num_tolerated_disk_barrier_failures =
                btrfs_calc_num_tolerated_disk_barrier_failures(root->fs_info);
 
-       /*
-        * at this point, the device is zero sized.  We want to
-        * remove it from the devices list and zero out the old super
-        */
-       if (clear_super) {
-               struct block_device *bdev;
-
-               bdev = blkdev_get_by_path(dev_name, FMODE_READ | FMODE_EXCL,
-                                               root->fs_info->bdev_holder);
-               if (!IS_ERR(bdev)) {
-                       btrfs_scratch_superblocks(bdev, dev_name);
-                       blkdev_put(bdev, FMODE_READ | FMODE_EXCL);
-               }
-       }
-
 out:
-       kfree(dev_name);
-
        mutex_unlock(&uuid_mutex);
        return ret;