]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
md: add fallback to correct bitmap_ops on version mismatch
authorYu Kuai <yukuai@fnnas.com>
Mon, 23 Mar 2026 05:46:42 +0000 (13:46 +0800)
committerYu Kuai <yukuai@fnnas.com>
Tue, 7 Apr 2026 05:09:22 +0000 (13:09 +0800)
If default bitmap version and on-disk version doesn't match, and mdadm
is not the latest version to set bitmap_type, set bitmap_ops based on
the disk version.

Link: https://lore.kernel.org/linux-raid/20260323054644.3351791-2-yukuai@fnnas.com/
Signed-off-by: Yu Kuai <yukuai@fnnas.com>
drivers/md/md.c

index e0a935f5a3e9d52f7c15d6f2ab0c558d54dd40a1..ee01e050ee12238ec67eae43f6ec72ff88063934 100644 (file)
@@ -6454,15 +6454,124 @@ static void md_safemode_timeout(struct timer_list *t)
 
 static int start_dirty_degraded;
 
+/*
+ * Read bitmap superblock and return the bitmap_id based on disk version.
+ * This is used as fallback when default bitmap version and on-disk version
+ * doesn't match, and mdadm is not the latest version to set bitmap_type.
+ */
+static enum md_submodule_id md_bitmap_get_id_from_sb(struct mddev *mddev)
+{
+       struct md_rdev *rdev;
+       struct page *sb_page;
+       bitmap_super_t *sb;
+       enum md_submodule_id id = ID_BITMAP_NONE;
+       sector_t sector;
+       u32 version;
+
+       if (!mddev->bitmap_info.offset)
+               return ID_BITMAP_NONE;
+
+       sb_page = alloc_page(GFP_KERNEL);
+       if (!sb_page) {
+               pr_warn("md: %s: failed to allocate memory for bitmap\n",
+                       mdname(mddev));
+               return ID_BITMAP_NONE;
+       }
+
+       sector = mddev->bitmap_info.offset;
+
+       rdev_for_each(rdev, mddev) {
+               u32 iosize;
+
+               if (!test_bit(In_sync, &rdev->flags) ||
+                   test_bit(Faulty, &rdev->flags) ||
+                   test_bit(Bitmap_sync, &rdev->flags))
+                       continue;
+
+               iosize = roundup(sizeof(bitmap_super_t),
+                                bdev_logical_block_size(rdev->bdev));
+               if (sync_page_io(rdev, sector, iosize, sb_page, REQ_OP_READ,
+                                true))
+                       goto read_ok;
+       }
+       pr_warn("md: %s: failed to read bitmap from any device\n",
+               mdname(mddev));
+       goto out;
+
+read_ok:
+       sb = kmap_local_page(sb_page);
+       if (sb->magic != cpu_to_le32(BITMAP_MAGIC)) {
+               pr_warn("md: %s: invalid bitmap magic 0x%x\n",
+                       mdname(mddev), le32_to_cpu(sb->magic));
+               goto out_unmap;
+       }
+
+       version = le32_to_cpu(sb->version);
+       switch (version) {
+       case BITMAP_MAJOR_LO:
+       case BITMAP_MAJOR_HI:
+       case BITMAP_MAJOR_CLUSTERED:
+               id = ID_BITMAP;
+               break;
+       case BITMAP_MAJOR_LOCKLESS:
+               id = ID_LLBITMAP;
+               break;
+       default:
+               pr_warn("md: %s: unknown bitmap version %u\n",
+                       mdname(mddev), version);
+               break;
+       }
+
+out_unmap:
+       kunmap_local(sb);
+out:
+       __free_page(sb_page);
+       return id;
+}
+
 static int md_bitmap_create(struct mddev *mddev)
 {
+       enum md_submodule_id orig_id = mddev->bitmap_id;
+       enum md_submodule_id sb_id;
+       int err;
+
        if (mddev->bitmap_id == ID_BITMAP_NONE)
                return -EINVAL;
 
        if (!mddev_set_bitmap_ops(mddev))
                return -ENOENT;
 
-       return mddev->bitmap_ops->create(mddev);
+       err = mddev->bitmap_ops->create(mddev);
+       if (!err)
+               return 0;
+
+       /*
+        * Create failed, if default bitmap version and on-disk version
+        * doesn't match, and mdadm is not the latest version to set
+        * bitmap_type, set bitmap_ops based on the disk version.
+        */
+       mddev_clear_bitmap_ops(mddev);
+
+       sb_id = md_bitmap_get_id_from_sb(mddev);
+       if (sb_id == ID_BITMAP_NONE || sb_id == orig_id)
+               return err;
+
+       pr_info("md: %s: bitmap version mismatch, switching from %d to %d\n",
+               mdname(mddev), orig_id, sb_id);
+
+       mddev->bitmap_id = sb_id;
+       if (!mddev_set_bitmap_ops(mddev)) {
+               mddev->bitmap_id = orig_id;
+               return -ENOENT;
+       }
+
+       err = mddev->bitmap_ops->create(mddev);
+       if (err) {
+               mddev_clear_bitmap_ops(mddev);
+               mddev->bitmap_id = orig_id;
+       }
+
+       return err;
 }
 
 static void md_bitmap_destroy(struct mddev *mddev)