Merge branch 'stable-3.16' of git://git.infradead.org/users/pcmoore/selinux into...
[firefly-linux-kernel-4.4.55.git] / fs / btrfs / dev-replace.c
index 564c92638b20a8d4929a920eb843c4f4fe71745b..eea26e1b2fda1d21230dd5f1e01e25f8fb23a3e8 100644 (file)
@@ -36,6 +36,7 @@
 #include "check-integrity.h"
 #include "rcu-string.h"
 #include "dev-replace.h"
+#include "sysfs.h"
 
 static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
                                       int scrub_ret);
@@ -313,7 +314,7 @@ int btrfs_dev_replace_start(struct btrfs_root *root,
 
        if (btrfs_fs_incompat(fs_info, RAID56)) {
                btrfs_warn(fs_info, "dev_replace cannot yet handle RAID5/RAID6");
-               return -EINVAL;
+               return -EOPNOTSUPP;
        }
 
        switch (args->start.cont_reading_from_srcdev_mode) {
@@ -431,6 +432,35 @@ leave_no_lock:
        return ret;
 }
 
+/*
+ * blocked until all flighting bios are finished.
+ */
+static void btrfs_rm_dev_replace_blocked(struct btrfs_fs_info *fs_info)
+{
+       s64 writers;
+       DEFINE_WAIT(wait);
+
+       set_bit(BTRFS_FS_STATE_DEV_REPLACING, &fs_info->fs_state);
+       do {
+               prepare_to_wait(&fs_info->replace_wait, &wait,
+                               TASK_UNINTERRUPTIBLE);
+               writers = percpu_counter_sum(&fs_info->bio_counter);
+               if (writers)
+                       schedule();
+               finish_wait(&fs_info->replace_wait, &wait);
+       } while (writers);
+}
+
+/*
+ * we have removed target device, it is safe to allow new bios request.
+ */
+static void btrfs_rm_dev_replace_unblocked(struct btrfs_fs_info *fs_info)
+{
+       clear_bit(BTRFS_FS_STATE_DEV_REPLACING, &fs_info->fs_state);
+       if (waitqueue_active(&fs_info->replace_wait))
+               wake_up(&fs_info->replace_wait);
+}
+
 static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
                                       int scrub_ret)
 {
@@ -458,17 +488,11 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
        src_device = dev_replace->srcdev;
        btrfs_dev_replace_unlock(dev_replace);
 
-       /* replace old device with new one in mapping tree */
-       if (!scrub_ret)
-               btrfs_dev_replace_update_device_in_mapping_tree(fs_info,
-                                                               src_device,
-                                                               tgt_device);
-
        /*
         * flush all outstanding I/O and inode extent mappings before the
         * copy operation is declared as being finished
         */
-       ret = btrfs_start_delalloc_roots(root->fs_info, 0);
+       ret = btrfs_start_delalloc_roots(root->fs_info, 0, -1);
        if (ret) {
                mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
                return ret;
@@ -484,6 +508,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
        WARN_ON(ret);
 
        /* keep away write_all_supers() during the finishing procedure */
+       mutex_lock(&root->fs_info->chunk_mutex);
        mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
        btrfs_dev_replace_lock(dev_replace);
        dev_replace->replace_state =
@@ -494,7 +519,12 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
        dev_replace->time_stopped = get_seconds();
        dev_replace->item_needs_writeback = 1;
 
-       if (scrub_ret) {
+       /* replace old device with new one in mapping tree */
+       if (!scrub_ret) {
+               btrfs_dev_replace_update_device_in_mapping_tree(fs_info,
+                                                               src_device,
+                                                               tgt_device);
+       } else {
                printk_in_rcu(KERN_ERR
                              "BTRFS: btrfs_scrub_dev(%s, %llu, %s) failed %d\n",
                              src_device->missing ? "<missing disk>" :
@@ -503,6 +533,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
                              rcu_str_deref(tgt_device->name), scrub_ret);
                btrfs_dev_replace_unlock(dev_replace);
                mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
+               mutex_unlock(&root->fs_info->chunk_mutex);
                if (tgt_device)
                        btrfs_destroy_dev_replace_tgtdev(fs_info, tgt_device);
                mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
@@ -532,8 +563,16 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
                fs_info->fs_devices->latest_bdev = tgt_device->bdev;
        list_add(&tgt_device->dev_alloc_list, &fs_info->fs_devices->alloc_list);
 
+       /* replace the sysfs entry */
+       btrfs_kobj_rm_device(fs_info, src_device);
+       btrfs_kobj_add_device(fs_info, tgt_device);
+
+       btrfs_rm_dev_replace_blocked(fs_info);
+
        btrfs_rm_dev_replace_srcdev(fs_info, src_device);
 
+       btrfs_rm_dev_replace_unblocked(fs_info);
+
        /*
         * this is again a consistent state where no dev_replace procedure
         * is running, the target device is part of the filesystem, the
@@ -543,6 +582,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
         */
        btrfs_dev_replace_unlock(dev_replace);
        mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
+       mutex_unlock(&root->fs_info->chunk_mutex);
 
        /* write back the superblocks */
        trans = btrfs_start_transaction(root, 0);
@@ -862,3 +902,31 @@ void btrfs_dev_replace_unlock(struct btrfs_dev_replace *dev_replace)
                mutex_unlock(&dev_replace->lock_management_lock);
        }
 }
+
+void btrfs_bio_counter_inc_noblocked(struct btrfs_fs_info *fs_info)
+{
+       percpu_counter_inc(&fs_info->bio_counter);
+}
+
+void btrfs_bio_counter_dec(struct btrfs_fs_info *fs_info)
+{
+       percpu_counter_dec(&fs_info->bio_counter);
+
+       if (waitqueue_active(&fs_info->replace_wait))
+               wake_up(&fs_info->replace_wait);
+}
+
+void btrfs_bio_counter_inc_blocked(struct btrfs_fs_info *fs_info)
+{
+       DEFINE_WAIT(wait);
+again:
+       percpu_counter_inc(&fs_info->bio_counter);
+       if (test_bit(BTRFS_FS_STATE_DEV_REPLACING, &fs_info->fs_state)) {
+               btrfs_bio_counter_dec(fs_info);
+               wait_event(fs_info->replace_wait,
+                          !test_bit(BTRFS_FS_STATE_DEV_REPLACING,
+                                    &fs_info->fs_state));
+               goto again;
+       }
+
+}