btrfs: fix readahead hang and use-after-free after removing a device
[sfrench/cifs-2.6.git] / fs / btrfs / reada.c
index e261c3d0cec783a40f230558df68d67a0e18ff8d..d9a166eb344e6484a6e86b11a4c5e7ca09dc35fa 100644 (file)
@@ -421,6 +421,9 @@ static struct reada_extent *reada_find_extent(struct btrfs_fs_info *fs_info,
                if (!dev->bdev)
                        continue;
 
+               if (test_bit(BTRFS_DEV_STATE_NO_READA, &dev->dev_state))
+                       continue;
+
                if (dev_replace_is_ongoing &&
                    dev == fs_info->dev_replace.tgtdev) {
                        /*
@@ -1022,3 +1025,45 @@ void btrfs_reada_detach(void *handle)
 
        kref_put(&rc->refcnt, reada_control_release);
 }
+
+/*
+ * Before removing a device (device replace or device remove ioctls), call this
+ * function to wait for all existing readahead requests on the device and to
+ * make sure no one queues more readahead requests for the device.
+ *
+ * Must be called without holding neither the device list mutex nor the device
+ * replace semaphore, otherwise it will deadlock.
+ */
+void btrfs_reada_remove_dev(struct btrfs_device *dev)
+{
+       struct btrfs_fs_info *fs_info = dev->fs_info;
+
+       /* Serialize with readahead extent creation at reada_find_extent(). */
+       spin_lock(&fs_info->reada_lock);
+       set_bit(BTRFS_DEV_STATE_NO_READA, &dev->dev_state);
+       spin_unlock(&fs_info->reada_lock);
+
+       /*
+        * There might be readahead requests added to the radix trees which
+        * were not yet added to the readahead work queue. We need to start
+        * them and wait for their completion, otherwise we can end up with
+        * use-after-free problems when dropping the last reference on the
+        * readahead extents and their zones, as they need to access the
+        * device structure.
+        */
+       reada_start_machine(fs_info);
+       btrfs_flush_workqueue(fs_info->readahead_workers);
+}
+
+/*
+ * If when removing a device (device replace or device remove ioctls) an error
+ * happens after calling btrfs_reada_remove_dev(), call this to undo what that
+ * function did. This is safe to call even if btrfs_reada_remove_dev() was not
+ * called before.
+ */
+void btrfs_reada_undo_remove_dev(struct btrfs_device *dev)
+{
+       spin_lock(&dev->fs_info->reada_lock);
+       clear_bit(BTRFS_DEV_STATE_NO_READA, &dev->dev_state);
+       spin_unlock(&dev->fs_info->reada_lock);
+}