btrfs: fix potential oops in device_list_add
[sfrench/cifs-2.6.git] / fs / btrfs / volumes.c
index ce14ce068a847211f41b9190e12e9a402db1e498..15561926ab32cbc8c0ba8a4006fcb8a45e87e6d5 100644 (file)
@@ -130,6 +130,60 @@ const char *get_raid_name(enum btrfs_raid_types type)
        return btrfs_raid_array[type].raid_name;
 }
 
+/*
+ * Fill @buf with textual description of @bg_flags, no more than @size_buf
+ * bytes including terminating null byte.
+ */
+void btrfs_describe_block_groups(u64 bg_flags, char *buf, u32 size_buf)
+{
+       int i;
+       int ret;
+       char *bp = buf;
+       u64 flags = bg_flags;
+       u32 size_bp = size_buf;
+
+       if (!flags) {
+               strcpy(bp, "NONE");
+               return;
+       }
+
+#define DESCRIBE_FLAG(flag, desc)                                              \
+       do {                                                            \
+               if (flags & (flag)) {                                   \
+                       ret = snprintf(bp, size_bp, "%s|", (desc));     \
+                       if (ret < 0 || ret >= size_bp)                  \
+                               goto out_overflow;                      \
+                       size_bp -= ret;                                 \
+                       bp += ret;                                      \
+                       flags &= ~(flag);                               \
+               }                                                       \
+       } while (0)
+
+       DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_DATA, "data");
+       DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_SYSTEM, "system");
+       DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_METADATA, "metadata");
+
+       DESCRIBE_FLAG(BTRFS_AVAIL_ALLOC_BIT_SINGLE, "single");
+       for (i = 0; i < BTRFS_NR_RAID_TYPES; i++)
+               DESCRIBE_FLAG(btrfs_raid_array[i].bg_flag,
+                             btrfs_raid_array[i].raid_name);
+#undef DESCRIBE_FLAG
+
+       if (flags) {
+               ret = snprintf(bp, size_bp, "0x%llx|", flags);
+               size_bp -= ret;
+       }
+
+       if (size_bp < size_buf)
+               buf[size_buf - size_bp - 1] = '\0'; /* remove last | */
+
+       /*
+        * The text is trimmed, it's up to the caller to provide sufficiently
+        * large buffer
+        */
+out_overflow:;
+}
+
 static int init_first_rw_device(struct btrfs_trans_handle *trans,
                                struct btrfs_fs_info *fs_info);
 static int btrfs_relocate_sys_chunks(struct btrfs_fs_info *fs_info);
@@ -158,7 +212,7 @@ static int __btrfs_map_block(struct btrfs_fs_info *fs_info,
  * the mutex can be very coarse and can cover long-running operations
  *
  * protects: updates to fs_devices counters like missing devices, rw devices,
- * seeding, structure cloning, openning/closing devices at mount/umount time
+ * seeding, structure cloning, opening/closing devices at mount/umount time
  *
  * global::fs_devs - add, remove, updates to the global list
  *
@@ -405,6 +459,21 @@ static noinline struct btrfs_fs_devices *find_fsid(
                                return fs_devices;
                        }
                }
+               /*
+                * Handle scanned device having completed its fsid change but
+                * belonging to a fs_devices that was created by a device that
+                * has an outdated pair of fsid/metadata_uuid and
+                * CHANGING_FSID_V2 flag set.
+                */
+               list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
+                       if (fs_devices->fsid_change &&
+                           memcmp(fs_devices->metadata_uuid,
+                                  fs_devices->fsid, BTRFS_FSID_SIZE) != 0 &&
+                           memcmp(metadata_fsid, fs_devices->metadata_uuid,
+                                  BTRFS_FSID_SIZE) == 0) {
+                               return fs_devices;
+                       }
+               }
        }
 
        /* Handle non-split brain cases */
@@ -814,6 +883,30 @@ static struct btrfs_fs_devices *find_fsid_inprogress(
        return NULL;
 }
 
+
+static struct btrfs_fs_devices *find_fsid_changed(
+                                       struct btrfs_super_block *disk_super)
+{
+       struct btrfs_fs_devices *fs_devices;
+
+       /*
+        * Handles the case where scanned device is part of an fs that had
+        * multiple successful changes of FSID but curently device didn't
+        * observe it. Meaning our fsid will be different than theirs.
+        */
+       list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
+               if (memcmp(fs_devices->metadata_uuid, fs_devices->fsid,
+                          BTRFS_FSID_SIZE) != 0 &&
+                   memcmp(fs_devices->metadata_uuid, disk_super->metadata_uuid,
+                          BTRFS_FSID_SIZE) == 0 &&
+                   memcmp(fs_devices->fsid, disk_super->fsid,
+                          BTRFS_FSID_SIZE) != 0) {
+                       return fs_devices;
+               }
+       }
+
+       return NULL;
+}
 /*
  * Add new device to list of registered devices
  *
@@ -835,17 +928,20 @@ static noinline struct btrfs_device *device_list_add(const char *path,
        bool fsid_change_in_progress = (btrfs_super_flags(disk_super) &
                                        BTRFS_SUPER_FLAG_CHANGING_FSID_V2);
 
-       if (fsid_change_in_progress && !has_metadata_uuid) {
-               /*
-                * When we have an image which has CHANGING_FSID_V2 set it might
-                * belong to either a filesystem which has disks with completed
-                * fsid change or it might belong to fs with no UUID changes in
-                * effect, handle both.
-                */
-               fs_devices = find_fsid_inprogress(disk_super);
-               if (!fs_devices)
-                       fs_devices = find_fsid(disk_super->fsid, NULL);
-
+       if (fsid_change_in_progress) {
+               if (!has_metadata_uuid) {
+                       /*
+                        * When we have an image which has CHANGING_FSID_V2 set
+                        * it might belong to either a filesystem which has
+                        * disks with completed fsid change or it might belong
+                        * to fs with no UUID changes in effect, handle both.
+                        */
+                       fs_devices = find_fsid_inprogress(disk_super);
+                       if (!fs_devices)
+                               fs_devices = find_fsid(disk_super->fsid, NULL);
+               } else {
+                       fs_devices = find_fsid_changed(disk_super);
+               }
        } else if (has_metadata_uuid) {
                fs_devices = find_fsid(disk_super->fsid,
                                       disk_super->metadata_uuid);
@@ -861,11 +957,11 @@ static noinline struct btrfs_device *device_list_add(const char *path,
                else
                        fs_devices = alloc_fs_devices(disk_super->fsid, NULL);
 
-               fs_devices->fsid_change = fsid_change_in_progress;
-
                if (IS_ERR(fs_devices))
                        return ERR_CAST(fs_devices);
 
+               fs_devices->fsid_change = fsid_change_in_progress;
+
                mutex_lock(&fs_devices->device_list_mutex);
                list_add(&fs_devices->fs_list, &fs_uuids);
 
@@ -1340,7 +1436,7 @@ static int btrfs_read_disk_super(struct block_device *bdev, u64 bytenr,
        p = kmap(*page);
 
        /* align our pointer to the offset of the super block */
-       *disk_super = p + (bytenr & ~PAGE_MASK);
+       *disk_super = p + offset_in_page(bytenr);
 
        if (btrfs_super_bytenr(*disk_super) != bytenr ||
            btrfs_super_magic(*disk_super) != BTRFS_MAGIC) {
@@ -2010,12 +2106,12 @@ static u64 btrfs_num_devices(struct btrfs_fs_info *fs_info)
 {
        u64 num_devices = fs_info->fs_devices->num_devices;
 
-       btrfs_dev_replace_read_lock(&fs_info->dev_replace);
+       down_read(&fs_info->dev_replace.rwsem);
        if (btrfs_dev_replace_is_ongoing(&fs_info->dev_replace)) {
                ASSERT(num_devices > 1);
                num_devices--;
        }
-       btrfs_dev_replace_read_unlock(&fs_info->dev_replace);
+       up_read(&fs_info->dev_replace.rwsem);
 
        return num_devices;
 }
@@ -3604,17 +3700,11 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info)
 {
        struct btrfs_balance_control *bctl = fs_info->balance_ctl;
        struct btrfs_root *chunk_root = fs_info->chunk_root;
-       struct btrfs_root *dev_root = fs_info->dev_root;
-       struct list_head *devices;
-       struct btrfs_device *device;
-       u64 old_size;
-       u64 size_to_free;
        u64 chunk_type;
        struct btrfs_chunk *chunk;
        struct btrfs_path *path = NULL;
        struct btrfs_key key;
        struct btrfs_key found_key;
-       struct btrfs_trans_handle *trans;
        struct extent_buffer *leaf;
        int slot;
        int ret;
@@ -3629,53 +3719,6 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info)
        u32 count_sys = 0;
        int chunk_reserved = 0;
 
-       /* step one make some room on all the devices */
-       devices = &fs_info->fs_devices->devices;
-       list_for_each_entry(device, devices, dev_list) {
-               old_size = btrfs_device_get_total_bytes(device);
-               size_to_free = div_factor(old_size, 1);
-               size_to_free = min_t(u64, size_to_free, SZ_1M);
-               if (!test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state) ||
-                   btrfs_device_get_total_bytes(device) -
-                   btrfs_device_get_bytes_used(device) > size_to_free ||
-                   test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state))
-                       continue;
-
-               ret = btrfs_shrink_device(device, old_size - size_to_free);
-               if (ret == -ENOSPC)
-                       break;
-               if (ret) {
-                       /* btrfs_shrink_device never returns ret > 0 */
-                       WARN_ON(ret > 0);
-                       goto error;
-               }
-
-               trans = btrfs_start_transaction(dev_root, 0);
-               if (IS_ERR(trans)) {
-                       ret = PTR_ERR(trans);
-                       btrfs_info_in_rcu(fs_info,
-                "resize: unable to start transaction after shrinking device %s (error %d), old size %llu, new size %llu",
-                                         rcu_str_deref(device->name), ret,
-                                         old_size, old_size - size_to_free);
-                       goto error;
-               }
-
-               ret = btrfs_grow_device(trans, device, old_size);
-               if (ret) {
-                       btrfs_end_transaction(trans);
-                       /* btrfs_grow_device never returns ret > 0 */
-                       WARN_ON(ret > 0);
-                       btrfs_info_in_rcu(fs_info,
-                "resize: unable to grow device after shrinking device %s (error %d), old size %llu, new size %llu",
-                                         rcu_str_deref(device->name), ret,
-                                         old_size, old_size - size_to_free);
-                       goto error;
-               }
-
-               btrfs_end_transaction(trans);
-       }
-
-       /* step two, relocate all the chunks */
        path = btrfs_alloc_path();
        if (!path) {
                ret = -ENOMEM;
@@ -3883,6 +3926,162 @@ static inline int validate_convert_profile(struct btrfs_balance_args *bctl_arg,
                 (bctl_arg->target & ~allowed)));
 }
 
+/*
+ * Fill @buf with textual description of balance filter flags @bargs, up to
+ * @size_buf including the terminating null. The output may be trimmed if it
+ * does not fit into the provided buffer.
+ */
+static void describe_balance_args(struct btrfs_balance_args *bargs, char *buf,
+                                u32 size_buf)
+{
+       int ret;
+       u32 size_bp = size_buf;
+       char *bp = buf;
+       u64 flags = bargs->flags;
+       char tmp_buf[128] = {'\0'};
+
+       if (!flags)
+               return;
+
+#define CHECK_APPEND_NOARG(a)                                          \
+       do {                                                            \
+               ret = snprintf(bp, size_bp, (a));                       \
+               if (ret < 0 || ret >= size_bp)                          \
+                       goto out_overflow;                              \
+               size_bp -= ret;                                         \
+               bp += ret;                                              \
+       } while (0)
+
+#define CHECK_APPEND_1ARG(a, v1)                                       \
+       do {                                                            \
+               ret = snprintf(bp, size_bp, (a), (v1));                 \
+               if (ret < 0 || ret >= size_bp)                          \
+                       goto out_overflow;                              \
+               size_bp -= ret;                                         \
+               bp += ret;                                              \
+       } while (0)
+
+#define CHECK_APPEND_2ARG(a, v1, v2)                                   \
+       do {                                                            \
+               ret = snprintf(bp, size_bp, (a), (v1), (v2));           \
+               if (ret < 0 || ret >= size_bp)                          \
+                       goto out_overflow;                              \
+               size_bp -= ret;                                         \
+               bp += ret;                                              \
+       } while (0)
+
+       if (flags & BTRFS_BALANCE_ARGS_CONVERT) {
+               int index = btrfs_bg_flags_to_raid_index(bargs->target);
+
+               CHECK_APPEND_1ARG("convert=%s,", get_raid_name(index));
+       }
+
+       if (flags & BTRFS_BALANCE_ARGS_SOFT)
+               CHECK_APPEND_NOARG("soft,");
+
+       if (flags & BTRFS_BALANCE_ARGS_PROFILES) {
+               btrfs_describe_block_groups(bargs->profiles, tmp_buf,
+                                           sizeof(tmp_buf));
+               CHECK_APPEND_1ARG("profiles=%s,", tmp_buf);
+       }
+
+       if (flags & BTRFS_BALANCE_ARGS_USAGE)
+               CHECK_APPEND_1ARG("usage=%llu,", bargs->usage);
+
+       if (flags & BTRFS_BALANCE_ARGS_USAGE_RANGE)
+               CHECK_APPEND_2ARG("usage=%u..%u,",
+                                 bargs->usage_min, bargs->usage_max);
+
+       if (flags & BTRFS_BALANCE_ARGS_DEVID)
+               CHECK_APPEND_1ARG("devid=%llu,", bargs->devid);
+
+       if (flags & BTRFS_BALANCE_ARGS_DRANGE)
+               CHECK_APPEND_2ARG("drange=%llu..%llu,",
+                                 bargs->pstart, bargs->pend);
+
+       if (flags & BTRFS_BALANCE_ARGS_VRANGE)
+               CHECK_APPEND_2ARG("vrange=%llu..%llu,",
+                                 bargs->vstart, bargs->vend);
+
+       if (flags & BTRFS_BALANCE_ARGS_LIMIT)
+               CHECK_APPEND_1ARG("limit=%llu,", bargs->limit);
+
+       if (flags & BTRFS_BALANCE_ARGS_LIMIT_RANGE)
+               CHECK_APPEND_2ARG("limit=%u..%u,",
+                               bargs->limit_min, bargs->limit_max);
+
+       if (flags & BTRFS_BALANCE_ARGS_STRIPES_RANGE)
+               CHECK_APPEND_2ARG("stripes=%u..%u,",
+                                 bargs->stripes_min, bargs->stripes_max);
+
+#undef CHECK_APPEND_2ARG
+#undef CHECK_APPEND_1ARG
+#undef CHECK_APPEND_NOARG
+
+out_overflow:
+
+       if (size_bp < size_buf)
+               buf[size_buf - size_bp - 1] = '\0'; /* remove last , */
+       else
+               buf[0] = '\0';
+}
+
+static void describe_balance_start_or_resume(struct btrfs_fs_info *fs_info)
+{
+       u32 size_buf = 1024;
+       char tmp_buf[192] = {'\0'};
+       char *buf;
+       char *bp;
+       u32 size_bp = size_buf;
+       int ret;
+       struct btrfs_balance_control *bctl = fs_info->balance_ctl;
+
+       buf = kzalloc(size_buf, GFP_KERNEL);
+       if (!buf)
+               return;
+
+       bp = buf;
+
+#define CHECK_APPEND_1ARG(a, v1)                                       \
+       do {                                                            \
+               ret = snprintf(bp, size_bp, (a), (v1));                 \
+               if (ret < 0 || ret >= size_bp)                          \
+                       goto out_overflow;                              \
+               size_bp -= ret;                                         \
+               bp += ret;                                              \
+       } while (0)
+
+       if (bctl->flags & BTRFS_BALANCE_FORCE)
+               CHECK_APPEND_1ARG("%s", "-f ");
+
+       if (bctl->flags & BTRFS_BALANCE_DATA) {
+               describe_balance_args(&bctl->data, tmp_buf, sizeof(tmp_buf));
+               CHECK_APPEND_1ARG("-d%s ", tmp_buf);
+       }
+
+       if (bctl->flags & BTRFS_BALANCE_METADATA) {
+               describe_balance_args(&bctl->meta, tmp_buf, sizeof(tmp_buf));
+               CHECK_APPEND_1ARG("-m%s ", tmp_buf);
+       }
+
+       if (bctl->flags & BTRFS_BALANCE_SYSTEM) {
+               describe_balance_args(&bctl->sys, tmp_buf, sizeof(tmp_buf));
+               CHECK_APPEND_1ARG("-s%s ", tmp_buf);
+       }
+
+#undef CHECK_APPEND_1ARG
+
+out_overflow:
+
+       if (size_bp < size_buf)
+               buf[size_buf - size_bp - 1] = '\0'; /* remove last " " */
+       btrfs_info(fs_info, "balance: %s %s",
+                  (bctl->flags & BTRFS_BALANCE_RESUME) ?
+                  "resume" : "start", buf);
+
+       kfree(buf);
+}
+
 /*
  * Should be called with balance mutexe held
  */
@@ -3896,6 +4095,7 @@ int btrfs_balance(struct btrfs_fs_info *fs_info,
        int ret;
        u64 num_devices;
        unsigned seq;
+       bool reducing_integrity;
 
        if (btrfs_fs_closing(fs_info) ||
            atomic_read(&fs_info->balance_pause_req) ||
@@ -3975,24 +4175,30 @@ int btrfs_balance(struct btrfs_fs_info *fs_info,
                     !(bctl->sys.target & allowed)) ||
                    ((bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT) &&
                     (fs_info->avail_metadata_alloc_bits & allowed) &&
-                    !(bctl->meta.target & allowed))) {
-                       if (bctl->flags & BTRFS_BALANCE_FORCE) {
-                               btrfs_info(fs_info,
-                               "balance: force reducing metadata integrity");
-                       } else {
-                               btrfs_err(fs_info,
-       "balance: reduces metadata integrity, use --force if you want this");
-                               ret = -EINVAL;
-                               goto out;
-                       }
-               }
+                    !(bctl->meta.target & allowed)))
+                       reducing_integrity = true;
+               else
+                       reducing_integrity = false;
+
+               /* if we're not converting, the target field is uninitialized */
+               meta_target = (bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
+                       bctl->meta.target : fs_info->avail_metadata_alloc_bits;
+               data_target = (bctl->data.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
+                       bctl->data.target : fs_info->avail_data_alloc_bits;
        } while (read_seqretry(&fs_info->profiles_lock, seq));
 
-       /* if we're not converting, the target field is uninitialized */
-       meta_target = (bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
-               bctl->meta.target : fs_info->avail_metadata_alloc_bits;
-       data_target = (bctl->data.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
-               bctl->data.target : fs_info->avail_data_alloc_bits;
+       if (reducing_integrity) {
+               if (bctl->flags & BTRFS_BALANCE_FORCE) {
+                       btrfs_info(fs_info,
+                                  "balance: force reducing metadata integrity");
+               } else {
+                       btrfs_err(fs_info,
+         "balance: reduces metadata integrity, use --force if you want this");
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
        if (btrfs_get_num_tolerated_disk_barrier_failures(meta_target) <
                btrfs_get_num_tolerated_disk_barrier_failures(data_target)) {
                int meta_index = btrfs_bg_flags_to_raid_index(meta_target);
@@ -4022,11 +4228,19 @@ int btrfs_balance(struct btrfs_fs_info *fs_info,
 
        ASSERT(!test_bit(BTRFS_FS_BALANCE_RUNNING, &fs_info->flags));
        set_bit(BTRFS_FS_BALANCE_RUNNING, &fs_info->flags);
+       describe_balance_start_or_resume(fs_info);
        mutex_unlock(&fs_info->balance_mutex);
 
        ret = __btrfs_balance(fs_info);
 
        mutex_lock(&fs_info->balance_mutex);
+       if (ret == -ECANCELED && atomic_read(&fs_info->balance_pause_req))
+               btrfs_info(fs_info, "balance: paused");
+       else if (ret == -ECANCELED && atomic_read(&fs_info->balance_cancel_req))
+               btrfs_info(fs_info, "balance: canceled");
+       else
+               btrfs_info(fs_info, "balance: ended with status: %d", ret);
+
        clear_bit(BTRFS_FS_BALANCE_RUNNING, &fs_info->flags);
 
        if (bargs) {
@@ -4059,10 +4273,8 @@ static int balance_kthread(void *data)
        int ret = 0;
 
        mutex_lock(&fs_info->balance_mutex);
-       if (fs_info->balance_ctl) {
-               btrfs_info(fs_info, "balance: resuming");
+       if (fs_info->balance_ctl)
                ret = btrfs_balance(fs_info, fs_info->balance_ctl, NULL);
-       }
        mutex_unlock(&fs_info->balance_mutex);
 
        return ret;
@@ -4835,7 +5047,7 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans,
                BUG_ON(1);
        }
 
-       /* we don't want a chunk larger than 10% of writeable space */
+       /* We don't want a chunk larger than 10% of writable space */
        max_chunk_size = min(div_factor(fs_devices->total_rw_bytes, 1),
                             max_chunk_size);
 
@@ -5143,10 +5355,10 @@ out:
 }
 
 /*
- * Chunk allocation falls into two parts. The first part does works
- * that make the new allocated chunk useable, but not do any operation
- * that modifies the chunk tree. The second part does the works that
- * require modifying the chunk tree. This division is important for the
+ * Chunk allocation falls into two parts. The first part does work
+ * that makes the new allocated chunk usable, but does not do any operation
+ * that modifies the chunk tree. The second part does the work that
+ * requires modifying the chunk tree. This division is important for the
  * bootstrap process of adding storage to a seed btrfs.
  */
 int btrfs_alloc_chunk(struct btrfs_trans_handle *trans, u64 type)
@@ -5294,11 +5506,11 @@ int btrfs_num_copies(struct btrfs_fs_info *fs_info, u64 logical, u64 len)
                ret = 1;
        free_extent_map(em);
 
-       btrfs_dev_replace_read_lock(&fs_info->dev_replace);
+       down_read(&fs_info->dev_replace.rwsem);
        if (btrfs_dev_replace_is_ongoing(&fs_info->dev_replace) &&
            fs_info->dev_replace.tgtdev)
                ret++;
-       btrfs_dev_replace_read_unlock(&fs_info->dev_replace);
+       up_read(&fs_info->dev_replace.rwsem);
 
        return ret;
 }
@@ -5871,17 +6083,21 @@ static int __btrfs_map_block(struct btrfs_fs_info *fs_info,
                *length = em->len - offset;
        }
 
-       /* This is for when we're called from btrfs_merge_bio_hook() and all
-          it cares about is the length */
+       /*
+        * This is for when we're called from btrfs_bio_fits_in_stripe and all
+        * it cares about is the length
+        */
        if (!bbio_ret)
                goto out;
 
-       btrfs_dev_replace_read_lock(dev_replace);
+       down_read(&dev_replace->rwsem);
        dev_replace_is_ongoing = btrfs_dev_replace_is_ongoing(dev_replace);
+       /*
+        * Hold the semaphore for read during the whole operation, write is
+        * requested at commit time but must wait.
+        */
        if (!dev_replace_is_ongoing)
-               btrfs_dev_replace_read_unlock(dev_replace);
-       else
-               btrfs_dev_replace_set_lock_blocking(dev_replace);
+               up_read(&dev_replace->rwsem);
 
        if (dev_replace_is_ongoing && mirror_num == map->num_stripes + 1 &&
            !need_full_stripe(op) && dev_replace->tgtdev != NULL) {
@@ -6076,12 +6292,9 @@ static int __btrfs_map_block(struct btrfs_fs_info *fs_info,
        }
 out:
        if (dev_replace_is_ongoing) {
-               ASSERT(atomic_read(&dev_replace->blocking_readers) > 0);
-               btrfs_dev_replace_read_lock(dev_replace);
-               /* Barrier implied by atomic_dec_and_test */
-               if (atomic_dec_and_test(&dev_replace->blocking_readers))
-                       cond_wake_up_nomb(&dev_replace->read_lock_wq);
-               btrfs_dev_replace_read_unlock(dev_replace);
+               lockdep_assert_held(&dev_replace->rwsem);
+               /* Unlock and let waiting writers proceed */
+               up_read(&dev_replace->rwsem);
        }
        free_extent_map(em);
        return ret;
@@ -7043,7 +7256,7 @@ bool btrfs_check_rw_degradable(struct btrfs_fs_info *fs_info,
                if (missing > max_tolerated) {
                        if (!failing_dev)
                                btrfs_warn(fs_info,
-       "chunk %llu missing %d devices, max tolerance is %d for writeable mount",
+       "chunk %llu missing %d devices, max tolerance is %d for writable mount",
                                   em->start, missing, max_tolerated);
                        free_extent_map(em);
                        ret = false;
@@ -7612,6 +7825,18 @@ static int verify_one_dev_extent(struct btrfs_fs_info *fs_info,
                ret = -EUCLEAN;
                goto out;
        }
+
+       /* It's possible this device is a dummy for seed device */
+       if (dev->disk_total_bytes == 0) {
+               dev = find_device(fs_info->fs_devices->seed, devid, NULL);
+               if (!dev) {
+                       btrfs_err(fs_info, "failed to find seed devid %llu",
+                                 devid);
+                       ret = -EUCLEAN;
+                       goto out;
+               }
+       }
+
        if (physical_offset + physical_len > dev->disk_total_bytes) {
                btrfs_err(fs_info,
 "dev extent devid %llu physical offset %llu len %llu is beyond device boundary %llu",