Skip to content

Commit c84a137

Browse files
neilbrownliu-song-6
authored andcommitted
md/raid0: avoid RAID0 data corruption due to layout confusion.
If the drives in a RAID0 are not all the same size, the array is divided into zones. The first zone covers all drives, to the size of the smallest. The second zone covers all drives larger than the smallest, up to the size of the second smallest - etc. A change in Linux 3.14 unintentionally changed the layout for the second and subsequent zones. All the correct data is still stored, but each chunk may be assigned to a different device than in pre-3.14 kernels. This can lead to data corruption. It is not possible to determine what layout to use - it depends which kernel the data was written by. So we add a module parameter to allow the old (0) or new (1) layout to be specified, and refused to assemble an affected array if that parameter is not set. Fixes: 20d0189 ("block: Introduce new bio_split()") cc: stable@vger.kernel.org (3.14+) Acked-by: Guoqing Jiang <guoqing.jiang@cloud.ionos.com> Signed-off-by: NeilBrown <neilb@suse.de> Signed-off-by: Song Liu <songliubraving@fb.com>
1 parent 6ce220d commit c84a137

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

drivers/md/raid0.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#include "raid0.h"
2020
#include "raid5.h"
2121

22+
static int default_layout = 0;
23+
module_param(default_layout, int, 0644);
24+
2225
#define UNSUPPORTED_MDDEV_FLAGS \
2326
((1L << MD_HAS_JOURNAL) | \
2427
(1L << MD_JOURNAL_CLEAN) | \
@@ -139,6 +142,19 @@ static int create_strip_zones(struct mddev *mddev, struct r0conf **private_conf)
139142
}
140143
pr_debug("md/raid0:%s: FINAL %d zones\n",
141144
mdname(mddev), conf->nr_strip_zones);
145+
146+
if (conf->nr_strip_zones == 1) {
147+
conf->layout = RAID0_ORIG_LAYOUT;
148+
} else if (default_layout == RAID0_ORIG_LAYOUT ||
149+
default_layout == RAID0_ALT_MULTIZONE_LAYOUT) {
150+
conf->layout = default_layout;
151+
} else {
152+
pr_err("md/raid0:%s: cannot assemble multi-zone RAID0 with default_layout setting\n",
153+
mdname(mddev));
154+
pr_err("md/raid0: please set raid.default_layout to 1 or 2\n");
155+
err = -ENOTSUPP;
156+
goto abort;
157+
}
142158
/*
143159
* now since we have the hard sector sizes, we can make sure
144160
* chunk size is a multiple of that sector size
@@ -547,10 +563,12 @@ static void raid0_handle_discard(struct mddev *mddev, struct bio *bio)
547563

548564
static bool raid0_make_request(struct mddev *mddev, struct bio *bio)
549565
{
566+
struct r0conf *conf = mddev->private;
550567
struct strip_zone *zone;
551568
struct md_rdev *tmp_dev;
552569
sector_t bio_sector;
553570
sector_t sector;
571+
sector_t orig_sector;
554572
unsigned chunk_sects;
555573
unsigned sectors;
556574

@@ -584,8 +602,20 @@ static bool raid0_make_request(struct mddev *mddev, struct bio *bio)
584602
bio = split;
585603
}
586604

605+
orig_sector = sector;
587606
zone = find_zone(mddev->private, &sector);
588-
tmp_dev = map_sector(mddev, zone, sector, &sector);
607+
switch (conf->layout) {
608+
case RAID0_ORIG_LAYOUT:
609+
tmp_dev = map_sector(mddev, zone, orig_sector, &sector);
610+
break;
611+
case RAID0_ALT_MULTIZONE_LAYOUT:
612+
tmp_dev = map_sector(mddev, zone, sector, &sector);
613+
break;
614+
default:
615+
WARN("md/raid0:%s: Invalid layout\n", mdname(mddev));
616+
bio_io_error(bio);
617+
return true;
618+
}
589619

590620
if (unlikely(is_mddev_broken(tmp_dev, "raid0"))) {
591621
bio_io_error(bio);

drivers/md/raid0.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,25 @@ struct strip_zone {
88
int nb_dev; /* # of devices attached to the zone */
99
};
1010

11+
/* Linux 3.14 (20d0189b101) made an unintended change to
12+
* the RAID0 layout for multi-zone arrays (where devices aren't all
13+
* the same size.
14+
* RAID0_ORIG_LAYOUT restores the original layout
15+
* RAID0_ALT_MULTIZONE_LAYOUT uses the altered layout
16+
* The layouts are identical when there is only one zone (all
17+
* devices the same size).
18+
*/
19+
20+
enum r0layout {
21+
RAID0_ORIG_LAYOUT = 1,
22+
RAID0_ALT_MULTIZONE_LAYOUT = 2,
23+
};
1124
struct r0conf {
1225
struct strip_zone *strip_zone;
1326
struct md_rdev **devlist; /* lists of rdevs, pointed to
1427
* by strip_zone->dev */
1528
int nr_strip_zones;
29+
enum r0layout layout;
1630
};
1731

1832
#endif

0 commit comments

Comments
 (0)