dm flakey: introduce random_read_corrupt and random_write_corrupt options
authorMikulas Patocka <mpatocka@redhat.com>
Mon, 1 May 2023 13:20:08 +0000 (09:20 -0400)
committerMike Snitzer <snitzer@kernel.org>
Fri, 16 Jun 2023 22:24:13 +0000 (18:24 -0400)
The random_read_corrupt and random_write_corrupt options corrupt a
random byte in a bio with the provided probability. The corruption
only happens in the "down" interval.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
Documentation/admin-guide/device-mapper/dm-flakey.rst
drivers/md/dm-flakey.c

index f7104c01b0f7425f99bd035a2310169175861e55..f967c5fea219b9c3cbe1fd81bc83cef42e564336 100644 (file)
@@ -67,6 +67,16 @@ Optional feature parameters:
        Perform the replacement only if bio->bi_opf has all the
        selected flags set.
 
+  random_read_corrupt <probability>
+       During <down interval>, replace random byte in a read bio
+       with a random value. probability is an integer between
+       0 and 1000000000 meaning 0% to 100% probability of corruption.
+
+  random_write_corrupt <probability>
+       During <down interval>, replace random byte in a write bio
+       with a random value. probability is an integer between
+       0 and 1000000000 meaning 0% to 100% probability of corruption.
+
 Examples:
 
 Replaces the 32nd byte of READ bios with the value 1::
index 079d48b29628857d0ecbf28b97d62877e8325c53..120153e44ae0d374b011407f209968c4788c21d9 100644 (file)
@@ -16,6 +16,8 @@
 
 #define DM_MSG_PREFIX "flakey"
 
+#define PROBABILITY_BASE       1000000000
+
 #define all_corrupt_bio_flags_match(bio, fc)   \
        (((bio)->bi_opf & (fc)->corrupt_bio_flags) == (fc)->corrupt_bio_flags)
 
@@ -34,6 +36,8 @@ struct flakey_c {
        unsigned int corrupt_bio_rw;
        unsigned int corrupt_bio_value;
        blk_opf_t corrupt_bio_flags;
+       unsigned int random_read_corrupt;
+       unsigned int random_write_corrupt;
 };
 
 enum feature_flag_bits {
@@ -54,10 +58,11 @@ static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,
        const char *arg_name;
 
        static const struct dm_arg _args[] = {
-               {0, 7, "Invalid number of feature args"},
+               {0, 11, "Invalid number of feature args"},
                {1, UINT_MAX, "Invalid corrupt bio byte"},
                {0, 255, "Invalid corrupt value to write into bio byte (0-255)"},
                {0, UINT_MAX, "Invalid corrupt bio flags mask"},
+               {0, PROBABILITY_BASE, "Invalid random corrupt argument"},
        };
 
        /* No feature arguments supplied. */
@@ -170,6 +175,32 @@ static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,
                        continue;
                }
 
+               if (!strcasecmp(arg_name, "random_read_corrupt")) {
+                       if (!argc) {
+                               ti->error = "Feature random_read_corrupt requires a parameter";
+                               return -EINVAL;
+                       }
+                       r = dm_read_arg(_args + 4, as, &fc->random_read_corrupt, &ti->error);
+                       if (r)
+                               return r;
+                       argc--;
+
+                       continue;
+               }
+
+               if (!strcasecmp(arg_name, "random_write_corrupt")) {
+                       if (!argc) {
+                               ti->error = "Feature random_write_corrupt requires a parameter";
+                               return -EINVAL;
+                       }
+                       r = dm_read_arg(_args + 4, as, &fc->random_write_corrupt, &ti->error);
+                       if (r)
+                               return r;
+                       argc--;
+
+                       continue;
+               }
+
                ti->error = "Unrecognised flakey feature requested";
                return -EINVAL;
        }
@@ -184,7 +215,8 @@ static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,
        }
 
        if (!fc->corrupt_bio_byte && !test_bit(ERROR_READS, &fc->flags) &&
-           !test_bit(DROP_WRITES, &fc->flags) && !test_bit(ERROR_WRITES, &fc->flags)) {
+           !test_bit(DROP_WRITES, &fc->flags) && !test_bit(ERROR_WRITES, &fc->flags) &&
+           !fc->random_read_corrupt && !fc->random_write_corrupt) {
                set_bit(ERROR_WRITES, &fc->flags);
                set_bit(ERROR_READS, &fc->flags);
        }
@@ -306,36 +338,57 @@ static void flakey_map_bio(struct dm_target *ti, struct bio *bio)
        bio->bi_iter.bi_sector = flakey_map_sector(ti, bio->bi_iter.bi_sector);
 }
 
-static void corrupt_bio_data(struct bio *bio, struct flakey_c *fc)
+static void corrupt_bio_common(struct bio *bio, unsigned int corrupt_bio_byte,
+                              unsigned char corrupt_bio_value)
 {
-       unsigned int corrupt_bio_byte = fc->corrupt_bio_byte - 1;
-
        struct bvec_iter iter;
        struct bio_vec bvec;
 
-       if (!bio_has_data(bio))
-               return;
-
        /*
         * Overwrite the Nth byte of the bio's data, on whichever page
         * it falls.
         */
        bio_for_each_segment(bvec, bio, iter) {
                if (bio_iter_len(bio, iter) > corrupt_bio_byte) {
-                       char *segment = bvec_kmap_local(&bvec);
-                       segment[corrupt_bio_byte] = fc->corrupt_bio_value;
+                       unsigned char *segment = bvec_kmap_local(&bvec);
+                       segment[corrupt_bio_byte] = corrupt_bio_value;
                        kunmap_local(segment);
                        DMDEBUG("Corrupting data bio=%p by writing %u to byte %u "
                                "(rw=%c bi_opf=%u bi_sector=%llu size=%u)\n",
-                               bio, fc->corrupt_bio_value, fc->corrupt_bio_byte,
+                               bio, corrupt_bio_value, corrupt_bio_byte,
                                (bio_data_dir(bio) == WRITE) ? 'w' : 'r', bio->bi_opf,
-                               (unsigned long long)bio->bi_iter.bi_sector, bio->bi_iter.bi_size);
+                               (unsigned long long)bio->bi_iter.bi_sector,
+                               bio->bi_iter.bi_size);
                        break;
                }
                corrupt_bio_byte -= bio_iter_len(bio, iter);
        }
 }
 
+static void corrupt_bio_data(struct bio *bio, struct flakey_c *fc)
+{
+       unsigned int corrupt_bio_byte = fc->corrupt_bio_byte - 1;
+
+       if (!bio_has_data(bio))
+               return;
+
+       corrupt_bio_common(bio, corrupt_bio_byte, fc->corrupt_bio_value);
+}
+
+static void corrupt_bio_random(struct bio *bio)
+{
+       unsigned int corrupt_byte;
+       unsigned char corrupt_value;
+
+       if (!bio_has_data(bio))
+               return;
+
+       corrupt_byte = get_random_u32() % bio->bi_iter.bi_size;
+       corrupt_value = get_random_u8();
+
+       corrupt_bio_common(bio, corrupt_byte, corrupt_value);
+}
+
 static void clone_free(struct bio *clone)
 {
        struct folio_iter fi;
@@ -436,6 +489,7 @@ static int flakey_map(struct dm_target *ti, struct bio *bio)
        /* Are we alive ? */
        elapsed = (jiffies - fc->start_time) / HZ;
        if (elapsed % (fc->up_interval + fc->down_interval) >= fc->up_interval) {
+               bool corrupt_fixed, corrupt_random;
                /*
                 * Flag this bio as submitted while down.
                 */
@@ -465,16 +519,28 @@ static int flakey_map(struct dm_target *ti, struct bio *bio)
                /*
                 * Corrupt matching writes.
                 */
+               corrupt_fixed = false;
+               corrupt_random = false;
                if (fc->corrupt_bio_byte && fc->corrupt_bio_rw == WRITE) {
-                       if (all_corrupt_bio_flags_match(bio, fc)) {
-                               struct bio *clone = clone_bio(ti, fc, bio);
-                               if (clone) {
+                       if (all_corrupt_bio_flags_match(bio, fc))
+                               corrupt_fixed = true;
+               }
+               if (fc->random_write_corrupt) {
+                       u64 rnd = get_random_u64();
+                       u32 rem = do_div(rnd, PROBABILITY_BASE);
+                       if (rem < fc->random_write_corrupt)
+                               corrupt_random = true;
+               }
+               if (corrupt_fixed || corrupt_random) {
+                       struct bio *clone = clone_bio(ti, fc, bio);
+                       if (clone) {
+                               if (corrupt_fixed)
                                        corrupt_bio_data(clone, fc);
-                                       submit_bio(clone);
-                                       return DM_MAPIO_SUBMITTED;
-                               }
+                               if (corrupt_random)
+                                       corrupt_bio_random(clone);
+                               submit_bio(clone);
+                               return DM_MAPIO_SUBMITTED;
                        }
-                       goto map_bio;
                }
        }
 
@@ -503,6 +569,12 @@ static int flakey_end_io(struct dm_target *ti, struct bio *bio,
                                corrupt_bio_data(bio, fc);
                        }
                }
+               if (fc->random_read_corrupt) {
+                       u64 rnd = get_random_u64();
+                       u32 rem = do_div(rnd, PROBABILITY_BASE);
+                       if (rem < fc->random_read_corrupt)
+                               corrupt_bio_random(bio);
+               }
                if (test_bit(ERROR_READS, &fc->flags)) {
                        /*
                         * Error read during the down_interval if drop_writes
@@ -535,7 +607,10 @@ static void flakey_status(struct dm_target *ti, status_type_t type,
                error_reads = test_bit(ERROR_READS, &fc->flags);
                drop_writes = test_bit(DROP_WRITES, &fc->flags);
                error_writes = test_bit(ERROR_WRITES, &fc->flags);
-               DMEMIT(" %u", error_reads + drop_writes + error_writes + (fc->corrupt_bio_byte > 0) * 5);
+               DMEMIT(" %u", error_reads + drop_writes + error_writes +
+                       (fc->corrupt_bio_byte > 0) * 5 +
+                       (fc->random_read_corrupt > 0) * 2 +
+                       (fc->random_write_corrupt > 0) * 2);
 
                if (error_reads)
                        DMEMIT(" error_reads");
@@ -550,6 +625,11 @@ static void flakey_status(struct dm_target *ti, status_type_t type,
                               (fc->corrupt_bio_rw == WRITE) ? 'w' : 'r',
                               fc->corrupt_bio_value, fc->corrupt_bio_flags);
 
+               if (fc->random_read_corrupt > 0)
+                       DMEMIT(" random_read_corrupt %u", fc->random_read_corrupt);
+               if (fc->random_write_corrupt > 0)
+                       DMEMIT(" random_write_corrupt %u", fc->random_write_corrupt);
+
                break;
 
        case STATUSTYPE_IMA: