[PATCH v4 2/3] md/raid5: do not reshape if overlapping rdev bad block ranges prevent it
From: Doug V Johnson
Date: Fri Oct 16 2026 - 00:26:18 EST
Ensure that a reshape is not started if there are more than max_degraded
devices in the array that are either out of sync, faulty, missing,
or have overlapping bad blocks such that a read is not recoverable.
Cheaply verify that the number of bad or unavailable devices exceeds
the max number of degraded drives and if so sweep the bad block lists
on each device for overlaps using a new function array_has_badblock().
Invoke this function in raid5_start_reshape() and if the array has
unreadable blocks, return -EIO to report it to userspace.
Leave in place the existing functionality to handle a failed reshape
in progress because it's possible for bad blocks to be discovered or
placed in metadata after a reshape has already been initiated.
Link: https://lore.kernel.org/r/21f250f9-04b5-76f6-84c9-1fd245b748c6@xxxxxxxxxxxxxxx
Link: https://lore.kernel.org/r/a9119707-4fa1-58cb-377c-63df67f88957@xxxxxxxxxxxxxxx
Suggested-by: Yu Kuai <yukuai1@xxxxxxxxxxxxxxx>
Assisted-by: LLM
Signed-off-by: Doug V Johnson <me@xxxxxxxxx>
---
drivers/md/raid5.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 86ea65559576..23ff549fa363 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -8636,6 +8636,89 @@ static int check_reshape(struct mddev *mddev)
+ mddev->delta_disks));
}
+static int rdev_is_unavailable(struct md_rdev *rdev)
+{
+ return !rdev || test_bit(Faulty, &rdev->flags) ||
+ !test_bit(In_sync, &rdev->flags);
+}
+
+static int array_has_badblock(struct r5conf *conf)
+{
+ sector_t dev_sectors = conf->mddev->dev_sectors;
+ sector_t stripe_sectors = RAID5_STRIPE_SECTORS(conf);
+ sector_t max_sectors = round_down((sector_t)INT_MAX, stripe_sectors);
+ sector_t offset = 0;
+ int num_bad_devs = 0;
+ int i;
+
+ /* Check if there are even enough bad disks for a bad block condition */
+ for (i = 0; i < conf->raid_disks; i++) {
+ struct md_rdev *rdev = conf->disks[i].rdev;
+
+ if (rdev_is_unavailable(rdev) || rdev->badblocks.count)
+ num_bad_devs++;
+ }
+ if (num_bad_devs <= conf->max_degraded)
+ return 0;
+
+ /*
+ * Sweep the sectors of the array for bad blocks in constituent devices.
+ * Within a stripe if there are more bad disks than can be recoverable
+ * (max degraded) then the array has bad blocks.
+ *
+ * The offset tracks the sweep. We advance to the first bad stripe
+ * boundary found in any device.
+ */
+ while (offset < dev_sectors) {
+ sector_t len = min(dev_sectors - offset, max_sectors);
+ int bad_disks = 0;
+
+ for (i = 0; i < conf->raid_disks; i++) {
+ struct md_rdev *rdev = conf->disks[i].rdev;
+ sector_t first_bad, bad_sectors, end;
+
+ if (rdev_is_unavailable(rdev)) {
+ bad_disks++;
+ continue;
+ }
+ if (!rdev->badblocks.count)
+ continue;
+ if (!badblocks_check(&rdev->badblocks,
+ rdev->data_offset + offset, len,
+ &first_bad, &bad_sectors))
+ continue;
+
+ /*
+ * Rebase the bad blocks start and end to the device's data
+ * offset for consistent comparision
+ */
+ end = first_bad + bad_sectors - rdev->data_offset;
+ first_bad = max(first_bad, rdev->data_offset) -
+ rdev->data_offset;
+
+ /* Snap the range to the stripe boundary extent */
+ first_bad = round_down(first_bad, stripe_sectors);
+ end = round_up(end, stripe_sectors);
+
+ /*
+ * This device has a bad block at or overlapping the current
+ * sweep position, so it's a bad disk
+ */
+ if (first_bad <= offset) {
+ len = min(len, end - offset);
+ bad_disks++;
+ } else {
+ len = min(len, first_bad - offset);
+ }
+ }
+ if (bad_disks > conf->max_degraded)
+ return 1;
+ offset += len;
+ cond_resched();
+ }
+ return 0;
+}
+
static int raid5_start_reshape(struct mddev *mddev)
{
struct r5conf *conf = mddev->private;
@@ -8685,6 +8768,11 @@ static int raid5_start_reshape(struct mddev *mddev)
mdname(mddev));
return -EINVAL;
}
+ if (array_has_badblock(conf)) {
+ pr_warn("md/raid:%s: reshape not possible due to bad block list\n",
+ mdname(mddev));
+ return -EIO;
+ }
if (md_bitmap_enabled(mddev, false) &&
mddev->bitmap_id == ID_LLBITMAP) {
i = mddev->bitmap_ops->resize(mddev, mddev->dev_sectors, 0);
--
2.54.0