From: Mike Snitzer <snitzer@redhat.com>

Use .end_io hook to corrupt_bio_data() after reads complete.

User may specify a series of bio->bi_rw flags that must be seen in order
to trigger corruption (e.g. 33 = REQ_WRITE|REQ_META).

Signed-off-by: Mike Snitzer <snitzer@redhat.com>

---
drivers/md/dm-flakey.c |   40 ++++++++++++++++++++++++++++++++++------
 drivers/md/dm-flakey.c |   51 ++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 7 deletions(-)

Index: linux-2.6.39/drivers/md/dm-flakey.c
===================================================================
--- linux-2.6.39.orig/drivers/md/dm-flakey.c
+++ linux-2.6.39/drivers/md/dm-flakey.c
@@ -28,6 +28,7 @@ struct flakey_c {
 	unsigned down_interval;
 	unsigned long flags;
 	unsigned corrupt_bio_byte;
+	unsigned corrupt_bio_flags;
 };
 
 enum feature_flag_bits {
@@ -42,8 +43,9 @@ static int parse_features(struct dm_arg_
 	const char *arg_name;
 
 	static struct dm_arg _args[] = {
-		{0, 3, "invalid number of feature args"},
+		{0, 4, "invalid number of feature args"},
 		{1, UINT_MAX, "invalid corrupt bio byte value"},
+		{0, UINT_MAX, "invalid corrupt bio flags mask"},
 	};
 
 	/* No feature arguments supplied. */
@@ -58,12 +60,20 @@ static int parse_features(struct dm_arg_
 		arg_name = dm_shift_arg(as);
 		argc--;
 
-		/* corrupt_bio_byte <Nth byte> */
+		/* corrupt_bio_byte <Nth byte> <bio_flags> */
 		if (!strnicmp(arg_name, MESG_STR("corrupt_bio_byte")) &&
 		    (argc >= 1)) {
 			r = dm_read_arg(_args + 1, dm_shift_arg(as),
 					&fc->corrupt_bio_byte, &ti->error);
 			argc--;
+
+			/*
+			 * Only corrupt bios that have specific bi_rw flag(s),
+			 * e.g.: READ=0 or REQ_WRITE=1|REQ_META=32
+			 */
+			r = dm_read_arg(_args + 2, dm_shift_arg(as),
+					&fc->corrupt_bio_flags, &ti->error);
+			argc--;
 			continue;
 		}
 
@@ -180,14 +190,20 @@ static void flakey_map_bio(struct dm_tar
 		bio->bi_sector = flakey_map_sector(ti, bio->bi_sector);
 }
 
+#define corrupt_bio_data_dir(fc)	((fc)->corrupt_bio_flags & 1)
+#define all_corrupt_bio_flags_match(fc, bio)	\
+	(((bio)->bi_rw & (fc)->corrupt_bio_flags) == (fc)->corrupt_bio_flags)
+
 static void corrupt_bio_data(struct bio *bio, struct flakey_c *fc)
 {
 	unsigned bio_bytes = bio_cur_bytes(bio);
 	char *data = bio_data(bio);
 
 	/* write 0 to the specified Nth byte of the bio */
-	if (data && bio_bytes >= fc->corrupt_bio_byte)
+	if (data && bio_bytes >= fc->corrupt_bio_byte) {
 		data[fc->corrupt_bio_byte - 1] = 0;
+		DMDEBUG("corrupting data rw=%lu\n", bio_data_dir(bio));
+	}
 }
 
 static int flakey_map(struct dm_target *ti, struct bio *bio,
@@ -203,9 +219,13 @@ static int flakey_map(struct dm_target *
 		rw = bio_data_dir(bio);
 
 		if (fc->corrupt_bio_byte) {
-			/* corrupt writes but don't touch reads */
-			if (rw == WRITE)
+			/* corrupt matching writes, defer reads until end_io */
+			if (rw == WRITE && rw == corrupt_bio_data_dir(fc) &&
+			    all_corrupt_bio_flags_match(fc, bio))
 				corrupt_bio_data(bio, fc);
+
+			/* flag this bio as submitted while down */
+			map_context->ll = 1;
 			goto map_bio;
 		}
 		if (test_bit(DROP_WRITES, &fc->flags)) {
@@ -227,6 +247,21 @@ map_bio:
 	return DM_MAPIO_REMAPPED;
 }
 
+static int flakey_end_io(struct dm_target *ti, struct bio *bio,
+			 int error, union map_info *map_context)
+{
+	struct flakey_c *fc = ti->private;
+	unsigned bio_submitted_while_down = map_context->ll;
+	unsigned rw = bio_data_dir(bio);
+
+	if (!error && bio_submitted_while_down)
+		if (rw == READ && rw == corrupt_bio_data_dir(fc) &&
+		    all_corrupt_bio_flags_match(fc, bio))
+			corrupt_bio_data(bio, fc);
+
+	return error;
+}
+
 static int flakey_status(struct dm_target *ti, status_type_t type,
 			 char *result, unsigned int maxlen)
 {
@@ -245,10 +280,11 @@ static int flakey_status(struct dm_targe
 		       fc->down_interval);
 
 		drop_writes = test_bit(DROP_WRITES, &fc->flags);
-		DMEMIT("%u ", drop_writes + (fc->corrupt_bio_byte > 0) * 2);
+		DMEMIT("%u ", drop_writes + (fc->corrupt_bio_byte > 0) * 3);
 
 		if (fc->corrupt_bio_byte)
-			DMEMIT("corrupt_bio_byte %u ", fc->corrupt_bio_byte);
+			DMEMIT("corrupt_bio_byte %u %u ", fc->corrupt_bio_byte,
+			       fc->corrupt_bio_flags);
 		if (drop_writes)
 			DMEMIT("drop_writes ");
 		break;
@@ -292,6 +328,7 @@ static struct target_type flakey_target 
 	.ctr    = flakey_ctr,
 	.dtr    = flakey_dtr,
 	.map    = flakey_map,
+	.end_io = flakey_end_io,
 	.status = flakey_status,
 	.ioctl	= flakey_ioctl,
 	.merge	= flakey_merge,
