aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/md
diff options
context:
space:
mode:
authorNeilBrown <neilb@suse.de>2019-09-09 02:30:02 -0400
committerSong Liu <songliubraving@fb.com>2019-09-13 16:10:05 -0400
commitc84a1372df929033cb1a0441fb57bd3932f39ac9 (patch)
tree25d1dfda789f506f6af96b793eb6ae16caf3198c /drivers/md
parent6ce220dd2f8ea71d6afc29b9a7524c12e39f374a (diff)
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: 20d0189b1012 ("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>
Diffstat (limited to 'drivers/md')
-rw-r--r--drivers/md/raid0.c32
-rw-r--r--drivers/md/raid0.h14
2 files changed, 45 insertions, 1 deletions
diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c
index bc422eae2c95..ec611abda835 100644
--- a/drivers/md/raid0.c
+++ b/drivers/md/raid0.c
@@ -19,6 +19,9 @@
19#include "raid0.h" 19#include "raid0.h"
20#include "raid5.h" 20#include "raid5.h"
21 21
22static int default_layout = 0;
23module_param(default_layout, int, 0644);
24
22#define UNSUPPORTED_MDDEV_FLAGS \ 25#define UNSUPPORTED_MDDEV_FLAGS \
23 ((1L << MD_HAS_JOURNAL) | \ 26 ((1L << MD_HAS_JOURNAL) | \
24 (1L << MD_JOURNAL_CLEAN) | \ 27 (1L << MD_JOURNAL_CLEAN) | \
@@ -139,6 +142,19 @@ static int create_strip_zones(struct mddev *mddev, struct r0conf **private_conf)
139 } 142 }
140 pr_debug("md/raid0:%s: FINAL %d zones\n", 143 pr_debug("md/raid0:%s: FINAL %d zones\n",
141 mdname(mddev), conf->nr_strip_zones); 144 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 }
142 /* 158 /*
143 * now since we have the hard sector sizes, we can make sure 159 * now since we have the hard sector sizes, we can make sure
144 * chunk size is a multiple of that sector size 160 * chunk size is a multiple of that sector size
@@ -547,10 +563,12 @@ static void raid0_handle_discard(struct mddev *mddev, struct bio *bio)
547 563
548static bool raid0_make_request(struct mddev *mddev, struct bio *bio) 564static bool raid0_make_request(struct mddev *mddev, struct bio *bio)
549{ 565{
566 struct r0conf *conf = mddev->private;
550 struct strip_zone *zone; 567 struct strip_zone *zone;
551 struct md_rdev *tmp_dev; 568 struct md_rdev *tmp_dev;
552 sector_t bio_sector; 569 sector_t bio_sector;
553 sector_t sector; 570 sector_t sector;
571 sector_t orig_sector;
554 unsigned chunk_sects; 572 unsigned chunk_sects;
555 unsigned sectors; 573 unsigned sectors;
556 574
@@ -584,8 +602,20 @@ static bool raid0_make_request(struct mddev *mddev, struct bio *bio)
584 bio = split; 602 bio = split;
585 } 603 }
586 604
605 orig_sector = sector;
587 zone = find_zone(mddev->private, &sector); 606 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 }
589 619
590 if (unlikely(is_mddev_broken(tmp_dev, "raid0"))) { 620 if (unlikely(is_mddev_broken(tmp_dev, "raid0"))) {
591 bio_io_error(bio); 621 bio_io_error(bio);
diff --git a/drivers/md/raid0.h b/drivers/md/raid0.h
index 540e65d92642..3816e5477db1 100644
--- a/drivers/md/raid0.h
+++ b/drivers/md/raid0.h
@@ -8,11 +8,25 @@ struct strip_zone {
8 int nb_dev; /* # of devices attached to the zone */ 8 int nb_dev; /* # of devices attached to the zone */
9}; 9};
10 10
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
20enum r0layout {
21 RAID0_ORIG_LAYOUT = 1,
22 RAID0_ALT_MULTIZONE_LAYOUT = 2,
23};
11struct r0conf { 24struct r0conf {
12 struct strip_zone *strip_zone; 25 struct strip_zone *strip_zone;
13 struct md_rdev **devlist; /* lists of rdevs, pointed to 26 struct md_rdev **devlist; /* lists of rdevs, pointed to
14 * by strip_zone->dev */ 27 * by strip_zone->dev */
15 int nr_strip_zones; 28 int nr_strip_zones;
29 enum r0layout layout;
16}; 30};
17 31
18#endif 32#endif