Commit 09af7736 authored by Yu Kuai's avatar Yu Kuai
Browse files

md: add fallback to correct bitmap_ops on version mismatch

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: default avatarYu Kuai <yukuai@fnnas.com>
parent b0cc3ae9
Loading
Loading
Loading
Loading
+110 −1
Original line number Diff line number Diff line
@@ -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)