695 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (C) 2003 Sistina Software (UK) Limited.4 * Copyright (C) 2004, 2010-2011 Red Hat, Inc. All rights reserved.5 *6 * This file is released under the GPL.7 */8 9#include <linux/device-mapper.h>10 11#include <linux/module.h>12#include <linux/init.h>13#include <linux/blkdev.h>14#include <linux/bio.h>15#include <linux/slab.h>16 17#define DM_MSG_PREFIX "flakey"18 19#define PROBABILITY_BASE 100000000020 21#define all_corrupt_bio_flags_match(bio, fc) \22 (((bio)->bi_opf & (fc)->corrupt_bio_flags) == (fc)->corrupt_bio_flags)23 24/*25 * Flakey: Used for testing only, simulates intermittent,26 * catastrophic device failure.27 */28struct flakey_c {29 struct dm_dev *dev;30 unsigned long start_time;31 sector_t start;32 unsigned int up_interval;33 unsigned int down_interval;34 unsigned long flags;35 unsigned int corrupt_bio_byte;36 unsigned int corrupt_bio_rw;37 unsigned int corrupt_bio_value;38 blk_opf_t corrupt_bio_flags;39 unsigned int random_read_corrupt;40 unsigned int random_write_corrupt;41};42 43enum feature_flag_bits {44 ERROR_READS,45 DROP_WRITES,46 ERROR_WRITES47};48 49struct per_bio_data {50 bool bio_submitted;51};52 53static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,54 struct dm_target *ti)55{56 int r;57 unsigned int argc;58 const char *arg_name;59 60 static const struct dm_arg _args[] = {61 {0, 11, "Invalid number of feature args"},62 {1, UINT_MAX, "Invalid corrupt bio byte"},63 {0, 255, "Invalid corrupt value to write into bio byte (0-255)"},64 {0, UINT_MAX, "Invalid corrupt bio flags mask"},65 {0, PROBABILITY_BASE, "Invalid random corrupt argument"},66 };67 68 /* No feature arguments supplied. */69 if (!as->argc)70 return 0;71 72 r = dm_read_arg_group(_args, as, &argc, &ti->error);73 if (r)74 return r;75 76 while (argc) {77 arg_name = dm_shift_arg(as);78 argc--;79 80 if (!arg_name) {81 ti->error = "Insufficient feature arguments";82 return -EINVAL;83 }84 85 /*86 * error_reads87 */88 if (!strcasecmp(arg_name, "error_reads")) {89 if (test_and_set_bit(ERROR_READS, &fc->flags)) {90 ti->error = "Feature error_reads duplicated";91 return -EINVAL;92 }93 continue;94 }95 96 /*97 * drop_writes98 */99 if (!strcasecmp(arg_name, "drop_writes")) {100 if (test_and_set_bit(DROP_WRITES, &fc->flags)) {101 ti->error = "Feature drop_writes duplicated";102 return -EINVAL;103 } else if (test_bit(ERROR_WRITES, &fc->flags)) {104 ti->error = "Feature drop_writes conflicts with feature error_writes";105 return -EINVAL;106 }107 108 continue;109 }110 111 /*112 * error_writes113 */114 if (!strcasecmp(arg_name, "error_writes")) {115 if (test_and_set_bit(ERROR_WRITES, &fc->flags)) {116 ti->error = "Feature error_writes duplicated";117 return -EINVAL;118 119 } else if (test_bit(DROP_WRITES, &fc->flags)) {120 ti->error = "Feature error_writes conflicts with feature drop_writes";121 return -EINVAL;122 }123 124 continue;125 }126 127 /*128 * corrupt_bio_byte <Nth_byte> <direction> <value> <bio_flags>129 */130 if (!strcasecmp(arg_name, "corrupt_bio_byte")) {131 if (!argc) {132 ti->error = "Feature corrupt_bio_byte requires parameters";133 return -EINVAL;134 }135 136 r = dm_read_arg(_args + 1, as, &fc->corrupt_bio_byte, &ti->error);137 if (r)138 return r;139 argc--;140 141 /*142 * Direction r or w?143 */144 arg_name = dm_shift_arg(as);145 if (arg_name && !strcasecmp(arg_name, "w"))146 fc->corrupt_bio_rw = WRITE;147 else if (arg_name && !strcasecmp(arg_name, "r"))148 fc->corrupt_bio_rw = READ;149 else {150 ti->error = "Invalid corrupt bio direction (r or w)";151 return -EINVAL;152 }153 argc--;154 155 /*156 * Value of byte (0-255) to write in place of correct one.157 */158 r = dm_read_arg(_args + 2, as, &fc->corrupt_bio_value, &ti->error);159 if (r)160 return r;161 argc--;162 163 /*164 * Only corrupt bios with these flags set.165 */166 BUILD_BUG_ON(sizeof(fc->corrupt_bio_flags) !=167 sizeof(unsigned int));168 r = dm_read_arg(_args + 3, as,169 (__force unsigned int *)&fc->corrupt_bio_flags,170 &ti->error);171 if (r)172 return r;173 argc--;174 175 continue;176 }177 178 if (!strcasecmp(arg_name, "random_read_corrupt")) {179 if (!argc) {180 ti->error = "Feature random_read_corrupt requires a parameter";181 return -EINVAL;182 }183 r = dm_read_arg(_args + 4, as, &fc->random_read_corrupt, &ti->error);184 if (r)185 return r;186 argc--;187 188 continue;189 }190 191 if (!strcasecmp(arg_name, "random_write_corrupt")) {192 if (!argc) {193 ti->error = "Feature random_write_corrupt requires a parameter";194 return -EINVAL;195 }196 r = dm_read_arg(_args + 4, as, &fc->random_write_corrupt, &ti->error);197 if (r)198 return r;199 argc--;200 201 continue;202 }203 204 ti->error = "Unrecognised flakey feature requested";205 return -EINVAL;206 }207 208 if (test_bit(DROP_WRITES, &fc->flags) && (fc->corrupt_bio_rw == WRITE)) {209 ti->error = "drop_writes is incompatible with corrupt_bio_byte with the WRITE flag set";210 return -EINVAL;211 212 } else if (test_bit(ERROR_WRITES, &fc->flags) && (fc->corrupt_bio_rw == WRITE)) {213 ti->error = "error_writes is incompatible with corrupt_bio_byte with the WRITE flag set";214 return -EINVAL;215 }216 217 if (!fc->corrupt_bio_byte && !test_bit(ERROR_READS, &fc->flags) &&218 !test_bit(DROP_WRITES, &fc->flags) && !test_bit(ERROR_WRITES, &fc->flags) &&219 !fc->random_read_corrupt && !fc->random_write_corrupt) {220 set_bit(ERROR_WRITES, &fc->flags);221 set_bit(ERROR_READS, &fc->flags);222 }223 224 return 0;225}226 227/*228 * Construct a flakey mapping:229 * <dev_path> <offset> <up interval> <down interval> [<#feature args> [<arg>]*]230 *231 * Feature args:232 * [drop_writes]233 * [corrupt_bio_byte <Nth_byte> <direction> <value> <bio_flags>]234 *235 * Nth_byte starts from 1 for the first byte.236 * Direction is r for READ or w for WRITE.237 * bio_flags is ignored if 0.238 */239static int flakey_ctr(struct dm_target *ti, unsigned int argc, char **argv)240{241 static const struct dm_arg _args[] = {242 {0, UINT_MAX, "Invalid up interval"},243 {0, UINT_MAX, "Invalid down interval"},244 };245 246 int r;247 struct flakey_c *fc;248 unsigned long long tmpll;249 struct dm_arg_set as;250 const char *devname;251 char dummy;252 253 as.argc = argc;254 as.argv = argv;255 256 if (argc < 4) {257 ti->error = "Invalid argument count";258 return -EINVAL;259 }260 261 fc = kzalloc(sizeof(*fc), GFP_KERNEL);262 if (!fc) {263 ti->error = "Cannot allocate context";264 return -ENOMEM;265 }266 fc->start_time = jiffies;267 268 devname = dm_shift_arg(&as);269 270 r = -EINVAL;271 if (sscanf(dm_shift_arg(&as), "%llu%c", &tmpll, &dummy) != 1 || tmpll != (sector_t)tmpll) {272 ti->error = "Invalid device sector";273 goto bad;274 }275 fc->start = tmpll;276 277 r = dm_read_arg(_args, &as, &fc->up_interval, &ti->error);278 if (r)279 goto bad;280 281 r = dm_read_arg(_args, &as, &fc->down_interval, &ti->error);282 if (r)283 goto bad;284 285 if (!(fc->up_interval + fc->down_interval)) {286 ti->error = "Total (up + down) interval is zero";287 r = -EINVAL;288 goto bad;289 }290 291 if (fc->up_interval + fc->down_interval < fc->up_interval) {292 ti->error = "Interval overflow";293 r = -EINVAL;294 goto bad;295 }296 297 r = parse_features(&as, fc, ti);298 if (r)299 goto bad;300 301 r = dm_get_device(ti, devname, dm_table_get_mode(ti->table), &fc->dev);302 if (r) {303 ti->error = "Device lookup failed";304 goto bad;305 }306 307 ti->num_flush_bios = 1;308 ti->num_discard_bios = 1;309 ti->per_io_data_size = sizeof(struct per_bio_data);310 ti->private = fc;311 return 0;312 313bad:314 kfree(fc);315 return r;316}317 318static void flakey_dtr(struct dm_target *ti)319{320 struct flakey_c *fc = ti->private;321 322 dm_put_device(ti, fc->dev);323 kfree(fc);324}325 326static sector_t flakey_map_sector(struct dm_target *ti, sector_t bi_sector)327{328 struct flakey_c *fc = ti->private;329 330 return fc->start + dm_target_offset(ti, bi_sector);331}332 333static void flakey_map_bio(struct dm_target *ti, struct bio *bio)334{335 struct flakey_c *fc = ti->private;336 337 bio_set_dev(bio, fc->dev->bdev);338 bio->bi_iter.bi_sector = flakey_map_sector(ti, bio->bi_iter.bi_sector);339}340 341static void corrupt_bio_common(struct bio *bio, unsigned int corrupt_bio_byte,342 unsigned char corrupt_bio_value)343{344 struct bvec_iter iter;345 struct bio_vec bvec;346 347 /*348 * Overwrite the Nth byte of the bio's data, on whichever page349 * it falls.350 */351 bio_for_each_segment(bvec, bio, iter) {352 if (bio_iter_len(bio, iter) > corrupt_bio_byte) {353 unsigned char *segment = bvec_kmap_local(&bvec);354 segment[corrupt_bio_byte] = corrupt_bio_value;355 kunmap_local(segment);356 DMDEBUG("Corrupting data bio=%p by writing %u to byte %u "357 "(rw=%c bi_opf=%u bi_sector=%llu size=%u)\n",358 bio, corrupt_bio_value, corrupt_bio_byte,359 (bio_data_dir(bio) == WRITE) ? 'w' : 'r', bio->bi_opf,360 (unsigned long long)bio->bi_iter.bi_sector,361 bio->bi_iter.bi_size);362 break;363 }364 corrupt_bio_byte -= bio_iter_len(bio, iter);365 }366}367 368static void corrupt_bio_data(struct bio *bio, struct flakey_c *fc)369{370 unsigned int corrupt_bio_byte = fc->corrupt_bio_byte - 1;371 372 if (!bio_has_data(bio))373 return;374 375 corrupt_bio_common(bio, corrupt_bio_byte, fc->corrupt_bio_value);376}377 378static void corrupt_bio_random(struct bio *bio)379{380 unsigned int corrupt_byte;381 unsigned char corrupt_value;382 383 if (!bio_has_data(bio))384 return;385 386 corrupt_byte = get_random_u32() % bio->bi_iter.bi_size;387 corrupt_value = get_random_u8();388 389 corrupt_bio_common(bio, corrupt_byte, corrupt_value);390}391 392static void clone_free(struct bio *clone)393{394 struct folio_iter fi;395 396 if (clone->bi_vcnt > 0) { /* bio_for_each_folio_all crashes with an empty bio */397 bio_for_each_folio_all(fi, clone)398 folio_put(fi.folio);399 }400 401 bio_uninit(clone);402 kfree(clone);403}404 405static void clone_endio(struct bio *clone)406{407 struct bio *bio = clone->bi_private;408 bio->bi_status = clone->bi_status;409 clone_free(clone);410 bio_endio(bio);411}412 413static struct bio *clone_bio(struct dm_target *ti, struct flakey_c *fc, struct bio *bio)414{415 struct bio *clone;416 unsigned size, remaining_size, nr_iovecs, order;417 struct bvec_iter iter = bio->bi_iter;418 419 if (unlikely(bio->bi_iter.bi_size > UIO_MAXIOV << PAGE_SHIFT))420 dm_accept_partial_bio(bio, UIO_MAXIOV << PAGE_SHIFT >> SECTOR_SHIFT);421 422 size = bio->bi_iter.bi_size;423 nr_iovecs = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;424 425 clone = bio_kmalloc(nr_iovecs, GFP_NOIO | __GFP_NORETRY | __GFP_NOWARN);426 if (!clone)427 return NULL;428 429 bio_init(clone, fc->dev->bdev, bio->bi_inline_vecs, nr_iovecs, bio->bi_opf);430 431 clone->bi_iter.bi_sector = flakey_map_sector(ti, bio->bi_iter.bi_sector);432 clone->bi_private = bio;433 clone->bi_end_io = clone_endio;434 435 remaining_size = size;436 437 order = MAX_PAGE_ORDER;438 while (remaining_size) {439 struct page *pages;440 unsigned size_to_add, to_copy;441 unsigned char *virt;442 unsigned remaining_order = __fls((remaining_size + PAGE_SIZE - 1) >> PAGE_SHIFT);443 order = min(order, remaining_order);444 445retry_alloc_pages:446 pages = alloc_pages(GFP_NOIO | __GFP_NORETRY | __GFP_NOWARN | __GFP_COMP, order);447 if (unlikely(!pages)) {448 if (order) {449 order--;450 goto retry_alloc_pages;451 }452 clone_free(clone);453 return NULL;454 }455 size_to_add = min((unsigned)PAGE_SIZE << order, remaining_size);456 457 virt = page_to_virt(pages);458 to_copy = size_to_add;459 do {460 struct bio_vec bvec = bvec_iter_bvec(bio->bi_io_vec, iter);461 unsigned this_step = min(bvec.bv_len, to_copy);462 void *map = bvec_kmap_local(&bvec);463 memcpy(virt, map, this_step);464 kunmap_local(map);465 466 bvec_iter_advance(bio->bi_io_vec, &iter, this_step);467 to_copy -= this_step;468 virt += this_step;469 } while (to_copy);470 471 __bio_add_page(clone, pages, size_to_add, 0);472 remaining_size -= size_to_add;473 }474 475 return clone;476}477 478static int flakey_map(struct dm_target *ti, struct bio *bio)479{480 struct flakey_c *fc = ti->private;481 unsigned int elapsed;482 struct per_bio_data *pb = dm_per_bio_data(bio, sizeof(struct per_bio_data));483 484 pb->bio_submitted = false;485 486 if (op_is_zone_mgmt(bio_op(bio)))487 goto map_bio;488 489 /* Are we alive ? */490 elapsed = (jiffies - fc->start_time) / HZ;491 if (elapsed % (fc->up_interval + fc->down_interval) >= fc->up_interval) {492 bool corrupt_fixed, corrupt_random;493 /*494 * Flag this bio as submitted while down.495 */496 pb->bio_submitted = true;497 498 /*499 * Error reads if neither corrupt_bio_byte or drop_writes or error_writes are set.500 * Otherwise, flakey_end_io() will decide if the reads should be modified.501 */502 if (bio_data_dir(bio) == READ) {503 if (test_bit(ERROR_READS, &fc->flags))504 return DM_MAPIO_KILL;505 goto map_bio;506 }507 508 /*509 * Drop or error writes?510 */511 if (test_bit(DROP_WRITES, &fc->flags)) {512 bio_endio(bio);513 return DM_MAPIO_SUBMITTED;514 } else if (test_bit(ERROR_WRITES, &fc->flags)) {515 bio_io_error(bio);516 return DM_MAPIO_SUBMITTED;517 }518 519 /*520 * Corrupt matching writes.521 */522 corrupt_fixed = false;523 corrupt_random = false;524 if (fc->corrupt_bio_byte && fc->corrupt_bio_rw == WRITE) {525 if (all_corrupt_bio_flags_match(bio, fc))526 corrupt_fixed = true;527 }528 if (fc->random_write_corrupt) {529 u64 rnd = get_random_u64();530 u32 rem = do_div(rnd, PROBABILITY_BASE);531 if (rem < fc->random_write_corrupt)532 corrupt_random = true;533 }534 if (corrupt_fixed || corrupt_random) {535 struct bio *clone = clone_bio(ti, fc, bio);536 if (clone) {537 if (corrupt_fixed)538 corrupt_bio_data(clone, fc);539 if (corrupt_random)540 corrupt_bio_random(clone);541 submit_bio(clone);542 return DM_MAPIO_SUBMITTED;543 }544 }545 }546 547map_bio:548 flakey_map_bio(ti, bio);549 550 return DM_MAPIO_REMAPPED;551}552 553static int flakey_end_io(struct dm_target *ti, struct bio *bio,554 blk_status_t *error)555{556 struct flakey_c *fc = ti->private;557 struct per_bio_data *pb = dm_per_bio_data(bio, sizeof(struct per_bio_data));558 559 if (op_is_zone_mgmt(bio_op(bio)))560 return DM_ENDIO_DONE;561 562 if (!*error && pb->bio_submitted && (bio_data_dir(bio) == READ)) {563 if (fc->corrupt_bio_byte) {564 if ((fc->corrupt_bio_rw == READ) &&565 all_corrupt_bio_flags_match(bio, fc)) {566 /*567 * Corrupt successful matching READs while in down state.568 */569 corrupt_bio_data(bio, fc);570 }571 }572 if (fc->random_read_corrupt) {573 u64 rnd = get_random_u64();574 u32 rem = do_div(rnd, PROBABILITY_BASE);575 if (rem < fc->random_read_corrupt)576 corrupt_bio_random(bio);577 }578 if (test_bit(ERROR_READS, &fc->flags)) {579 /*580 * Error read during the down_interval if drop_writes581 * and error_writes were not configured.582 */583 *error = BLK_STS_IOERR;584 }585 }586 587 return DM_ENDIO_DONE;588}589 590static void flakey_status(struct dm_target *ti, status_type_t type,591 unsigned int status_flags, char *result, unsigned int maxlen)592{593 unsigned int sz = 0;594 struct flakey_c *fc = ti->private;595 unsigned int error_reads, drop_writes, error_writes;596 597 switch (type) {598 case STATUSTYPE_INFO:599 result[0] = '\0';600 break;601 602 case STATUSTYPE_TABLE:603 DMEMIT("%s %llu %u %u", fc->dev->name,604 (unsigned long long)fc->start, fc->up_interval,605 fc->down_interval);606 607 error_reads = test_bit(ERROR_READS, &fc->flags);608 drop_writes = test_bit(DROP_WRITES, &fc->flags);609 error_writes = test_bit(ERROR_WRITES, &fc->flags);610 DMEMIT(" %u", error_reads + drop_writes + error_writes +611 (fc->corrupt_bio_byte > 0) * 5 +612 (fc->random_read_corrupt > 0) * 2 +613 (fc->random_write_corrupt > 0) * 2);614 615 if (error_reads)616 DMEMIT(" error_reads");617 if (drop_writes)618 DMEMIT(" drop_writes");619 else if (error_writes)620 DMEMIT(" error_writes");621 622 if (fc->corrupt_bio_byte)623 DMEMIT(" corrupt_bio_byte %u %c %u %u",624 fc->corrupt_bio_byte,625 (fc->corrupt_bio_rw == WRITE) ? 'w' : 'r',626 fc->corrupt_bio_value, fc->corrupt_bio_flags);627 628 if (fc->random_read_corrupt > 0)629 DMEMIT(" random_read_corrupt %u", fc->random_read_corrupt);630 if (fc->random_write_corrupt > 0)631 DMEMIT(" random_write_corrupt %u", fc->random_write_corrupt);632 633 break;634 635 case STATUSTYPE_IMA:636 result[0] = '\0';637 break;638 }639}640 641static int flakey_prepare_ioctl(struct dm_target *ti, struct block_device **bdev)642{643 struct flakey_c *fc = ti->private;644 645 *bdev = fc->dev->bdev;646 647 /*648 * Only pass ioctls through if the device sizes match exactly.649 */650 if (fc->start || ti->len != bdev_nr_sectors((*bdev)))651 return 1;652 return 0;653}654 655#ifdef CONFIG_BLK_DEV_ZONED656static int flakey_report_zones(struct dm_target *ti,657 struct dm_report_zones_args *args, unsigned int nr_zones)658{659 struct flakey_c *fc = ti->private;660 661 return dm_report_zones(fc->dev->bdev, fc->start,662 flakey_map_sector(ti, args->next_sector),663 args, nr_zones);664}665#else666#define flakey_report_zones NULL667#endif668 669static int flakey_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data)670{671 struct flakey_c *fc = ti->private;672 673 return fn(ti, fc->dev, fc->start, ti->len, data);674}675 676static struct target_type flakey_target = {677 .name = "flakey",678 .version = {1, 5, 0},679 .features = DM_TARGET_ZONED_HM | DM_TARGET_PASSES_CRYPTO,680 .report_zones = flakey_report_zones,681 .module = THIS_MODULE,682 .ctr = flakey_ctr,683 .dtr = flakey_dtr,684 .map = flakey_map,685 .end_io = flakey_end_io,686 .status = flakey_status,687 .prepare_ioctl = flakey_prepare_ioctl,688 .iterate_devices = flakey_iterate_devices,689};690module_dm(flakey);691 692MODULE_DESCRIPTION(DM_NAME " flakey target");693MODULE_AUTHOR("Joe Thornber <dm-devel@lists.linux.dev>");694MODULE_LICENSE("GPL");695