btrfs: remove wrong use of volume_mutex from btrfs_dev_replace_start
[sfrench/cifs-2.6.git] / fs / btrfs / dev-replace.c
index 0d203633bb96493dbc8abda9bb4fe4bf9bb2f3cd..d097701d494da4fb440cb632be97cc1164750f15 100644 (file)
@@ -1,20 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (C) STRATO AG 2012.  All rights reserved.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public
- * License v2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public
- * License along with this program; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 021110-1307, USA.
  */
+
 #include <linux/sched.h>
 #include <linux/bio.h>
 #include <linux/slab.h>
@@ -45,8 +33,6 @@ static void btrfs_dev_replace_update_device_in_mapping_tree(
                                                struct btrfs_device *srcdev,
                                                struct btrfs_device *tgtdev);
 static int btrfs_dev_replace_kthread(void *data);
-static int btrfs_dev_replace_continue_on_mount(struct btrfs_fs_info *fs_info);
-
 
 int btrfs_init_dev_replace(struct btrfs_fs_info *fs_info)
 {
@@ -190,6 +176,105 @@ out:
        return ret;
 }
 
+/*
+ * Initialize a new device for device replace target from a given source dev
+ * and path.
+ *
+ * Return 0 and new device in @device_out, otherwise return < 0
+ */
+static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,
+                                 const char *device_path,
+                                 struct btrfs_device *srcdev,
+                                 struct btrfs_device **device_out)
+{
+       struct btrfs_device *device;
+       struct block_device *bdev;
+       struct list_head *devices;
+       struct rcu_string *name;
+       u64 devid = BTRFS_DEV_REPLACE_DEVID;
+       int ret = 0;
+
+       *device_out = NULL;
+       if (fs_info->fs_devices->seeding) {
+               btrfs_err(fs_info, "the filesystem is a seed filesystem!");
+               return -EINVAL;
+       }
+
+       bdev = blkdev_get_by_path(device_path, FMODE_WRITE | FMODE_EXCL,
+                                 fs_info->bdev_holder);
+       if (IS_ERR(bdev)) {
+               btrfs_err(fs_info, "target device %s is invalid!", device_path);
+               return PTR_ERR(bdev);
+       }
+
+       filemap_write_and_wait(bdev->bd_inode->i_mapping);
+
+       devices = &fs_info->fs_devices->devices;
+       list_for_each_entry(device, devices, dev_list) {
+               if (device->bdev == bdev) {
+                       btrfs_err(fs_info,
+                                 "target device is in the filesystem!");
+                       ret = -EEXIST;
+                       goto error;
+               }
+       }
+
+
+       if (i_size_read(bdev->bd_inode) <
+           btrfs_device_get_total_bytes(srcdev)) {
+               btrfs_err(fs_info,
+                         "target device is smaller than source device!");
+               ret = -EINVAL;
+               goto error;
+       }
+
+
+       device = btrfs_alloc_device(NULL, &devid, NULL);
+       if (IS_ERR(device)) {
+               ret = PTR_ERR(device);
+               goto error;
+       }
+
+       name = rcu_string_strdup(device_path, GFP_KERNEL);
+       if (!name) {
+               btrfs_free_device(device);
+               ret = -ENOMEM;
+               goto error;
+       }
+       rcu_assign_pointer(device->name, name);
+
+       mutex_lock(&fs_info->fs_devices->device_list_mutex);
+       set_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
+       device->generation = 0;
+       device->io_width = fs_info->sectorsize;
+       device->io_align = fs_info->sectorsize;
+       device->sector_size = fs_info->sectorsize;
+       device->total_bytes = btrfs_device_get_total_bytes(srcdev);
+       device->disk_total_bytes = btrfs_device_get_disk_total_bytes(srcdev);
+       device->bytes_used = btrfs_device_get_bytes_used(srcdev);
+       device->commit_total_bytes = srcdev->commit_total_bytes;
+       device->commit_bytes_used = device->bytes_used;
+       device->fs_info = fs_info;
+       device->bdev = bdev;
+       set_bit(BTRFS_DEV_STATE_IN_FS_METADATA, &device->dev_state);
+       set_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state);
+       device->mode = FMODE_EXCL;
+       device->dev_stats_valid = 1;
+       set_blocksize(device->bdev, BTRFS_BDEV_BLOCKSIZE);
+       device->fs_devices = fs_info->fs_devices;
+       list_add(&device->dev_list, &fs_info->fs_devices->devices);
+       fs_info->fs_devices->num_devices++;
+       fs_info->fs_devices->open_devices++;
+       mutex_unlock(&fs_info->fs_devices->device_list_mutex);
+
+       *device_out = device;
+       return 0;
+
+error:
+       blkdev_put(bdev, FMODE_EXCL);
+       return ret;
+}
+
 /*
  * called from commit_transaction. Writes changed device replace state to
  * disk.
@@ -329,18 +414,13 @@ int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
        struct btrfs_device *tgt_device = NULL;
        struct btrfs_device *src_device = NULL;
 
-       /* the disk copy procedure reuses the scrub code */
-       mutex_lock(&fs_info->volume_mutex);
        ret = btrfs_find_device_by_devspec(fs_info, srcdevid,
                                            srcdev_name, &src_device);
-       if (ret) {
-               mutex_unlock(&fs_info->volume_mutex);
+       if (ret)
                return ret;
-       }
 
        ret = btrfs_init_dev_replace_tgtdev(fs_info, tgtdev_name,
                                            src_device, &tgt_device);
-       mutex_unlock(&fs_info->volume_mutex);
        if (ret)
                return ret;
 
@@ -372,7 +452,6 @@ int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
        dev_replace->cont_reading_from_srcdev_mode = read_src;
        WARN_ON(!src_device);
        dev_replace->srcdev = src_device;
-       WARN_ON(!tgt_device);
        dev_replace->tgtdev = tgt_device;
 
        btrfs_info_in_rcu(fs_info,
@@ -812,7 +891,17 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info)
        }
        btrfs_dev_replace_write_unlock(dev_replace);
 
-       WARN_ON(test_and_set_bit(BTRFS_FS_EXCL_OP, &fs_info->flags));
+       /*
+        * This could collide with a paused balance, but the exclusive op logic
+        * should never allow both to start and pause. We don't want to allow
+        * dev-replace to start anyway.
+        */
+       if (test_and_set_bit(BTRFS_FS_EXCL_OP, &fs_info->flags)) {
+               btrfs_info(fs_info,
+               "cannot resume dev-replace, other exclusive operation running");
+               return 0;
+       }
+
        task = kthread_run(btrfs_dev_replace_kthread, fs_info, "btrfs-devrepl");
        return PTR_ERR_OR_ZERO(task);
 }
@@ -822,6 +911,7 @@ static int btrfs_dev_replace_kthread(void *data)
        struct btrfs_fs_info *fs_info = data;
        struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace;
        u64 progress;
+       int ret;
 
        progress = btrfs_dev_replace_progress(fs_info);
        progress = div_u64(progress, 10);
@@ -832,23 +922,14 @@ static int btrfs_dev_replace_kthread(void *data)
                btrfs_dev_name(dev_replace->tgtdev),
                (unsigned int)progress);
 
-       btrfs_dev_replace_continue_on_mount(fs_info);
-       clear_bit(BTRFS_FS_EXCL_OP, &fs_info->flags);
-
-       return 0;
-}
-
-static int btrfs_dev_replace_continue_on_mount(struct btrfs_fs_info *fs_info)
-{
-       struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace;
-       int ret;
-
        ret = btrfs_scrub_dev(fs_info, dev_replace->srcdev->devid,
                              dev_replace->committed_cursor_left,
                              btrfs_device_get_total_bytes(dev_replace->srcdev),
                              &dev_replace->scrub_progress, 0, 1);
        ret = btrfs_dev_replace_finishing(fs_info, ret);
        WARN_ON(ret);
+
+       clear_bit(BTRFS_FS_EXCL_OP, &fs_info->flags);
        return 0;
 }