xref: /openbmc/linux/drivers/md/dm-flakey.c (revision bbaf1ff0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
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 all_corrupt_bio_flags_match(bio, fc)	\
20 	(((bio)->bi_opf & (fc)->corrupt_bio_flags) == (fc)->corrupt_bio_flags)
21 
22 /*
23  * Flakey: Used for testing only, simulates intermittent,
24  * catastrophic device failure.
25  */
26 struct flakey_c {
27 	struct dm_dev *dev;
28 	unsigned long start_time;
29 	sector_t start;
30 	unsigned int up_interval;
31 	unsigned int down_interval;
32 	unsigned long flags;
33 	unsigned int corrupt_bio_byte;
34 	unsigned int corrupt_bio_rw;
35 	unsigned int corrupt_bio_value;
36 	blk_opf_t corrupt_bio_flags;
37 };
38 
39 enum feature_flag_bits {
40 	ERROR_READS,
41 	DROP_WRITES,
42 	ERROR_WRITES
43 };
44 
45 struct per_bio_data {
46 	bool bio_submitted;
47 };
48 
49 static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,
50 			  struct dm_target *ti)
51 {
52 	int r;
53 	unsigned int argc;
54 	const char *arg_name;
55 
56 	static const struct dm_arg _args[] = {
57 		{0, 7, "Invalid number of feature args"},
58 		{1, UINT_MAX, "Invalid corrupt bio byte"},
59 		{0, 255, "Invalid corrupt value to write into bio byte (0-255)"},
60 		{0, UINT_MAX, "Invalid corrupt bio flags mask"},
61 	};
62 
63 	/* No feature arguments supplied. */
64 	if (!as->argc)
65 		return 0;
66 
67 	r = dm_read_arg_group(_args, as, &argc, &ti->error);
68 	if (r)
69 		return r;
70 
71 	while (argc) {
72 		arg_name = dm_shift_arg(as);
73 		argc--;
74 
75 		if (!arg_name) {
76 			ti->error = "Insufficient feature arguments";
77 			return -EINVAL;
78 		}
79 
80 		/*
81 		 * error_reads
82 		 */
83 		if (!strcasecmp(arg_name, "error_reads")) {
84 			if (test_and_set_bit(ERROR_READS, &fc->flags)) {
85 				ti->error = "Feature error_reads duplicated";
86 				return -EINVAL;
87 			}
88 			continue;
89 		}
90 
91 		/*
92 		 * drop_writes
93 		 */
94 		if (!strcasecmp(arg_name, "drop_writes")) {
95 			if (test_and_set_bit(DROP_WRITES, &fc->flags)) {
96 				ti->error = "Feature drop_writes duplicated";
97 				return -EINVAL;
98 			} else if (test_bit(ERROR_WRITES, &fc->flags)) {
99 				ti->error = "Feature drop_writes conflicts with feature error_writes";
100 				return -EINVAL;
101 			}
102 
103 			continue;
104 		}
105 
106 		/*
107 		 * error_writes
108 		 */
109 		if (!strcasecmp(arg_name, "error_writes")) {
110 			if (test_and_set_bit(ERROR_WRITES, &fc->flags)) {
111 				ti->error = "Feature error_writes duplicated";
112 				return -EINVAL;
113 
114 			} else if (test_bit(DROP_WRITES, &fc->flags)) {
115 				ti->error = "Feature error_writes conflicts with feature drop_writes";
116 				return -EINVAL;
117 			}
118 
119 			continue;
120 		}
121 
122 		/*
123 		 * corrupt_bio_byte <Nth_byte> <direction> <value> <bio_flags>
124 		 */
125 		if (!strcasecmp(arg_name, "corrupt_bio_byte")) {
126 			if (!argc) {
127 				ti->error = "Feature corrupt_bio_byte requires parameters";
128 				return -EINVAL;
129 			}
130 
131 			r = dm_read_arg(_args + 1, as, &fc->corrupt_bio_byte, &ti->error);
132 			if (r)
133 				return r;
134 			argc--;
135 
136 			/*
137 			 * Direction r or w?
138 			 */
139 			arg_name = dm_shift_arg(as);
140 			if (arg_name && !strcasecmp(arg_name, "w"))
141 				fc->corrupt_bio_rw = WRITE;
142 			else if (arg_name && !strcasecmp(arg_name, "r"))
143 				fc->corrupt_bio_rw = READ;
144 			else {
145 				ti->error = "Invalid corrupt bio direction (r or w)";
146 				return -EINVAL;
147 			}
148 			argc--;
149 
150 			/*
151 			 * Value of byte (0-255) to write in place of correct one.
152 			 */
153 			r = dm_read_arg(_args + 2, as, &fc->corrupt_bio_value, &ti->error);
154 			if (r)
155 				return r;
156 			argc--;
157 
158 			/*
159 			 * Only corrupt bios with these flags set.
160 			 */
161 			BUILD_BUG_ON(sizeof(fc->corrupt_bio_flags) !=
162 				     sizeof(unsigned int));
163 			r = dm_read_arg(_args + 3, as,
164 				(__force unsigned int *)&fc->corrupt_bio_flags,
165 				&ti->error);
166 			if (r)
167 				return r;
168 			argc--;
169 
170 			continue;
171 		}
172 
173 		ti->error = "Unrecognised flakey feature requested";
174 		return -EINVAL;
175 	}
176 
177 	if (test_bit(DROP_WRITES, &fc->flags) && (fc->corrupt_bio_rw == WRITE)) {
178 		ti->error = "drop_writes is incompatible with corrupt_bio_byte with the WRITE flag set";
179 		return -EINVAL;
180 
181 	} else if (test_bit(ERROR_WRITES, &fc->flags) && (fc->corrupt_bio_rw == WRITE)) {
182 		ti->error = "error_writes is incompatible with corrupt_bio_byte with the WRITE flag set";
183 		return -EINVAL;
184 	}
185 
186 	if (!fc->corrupt_bio_byte && !test_bit(ERROR_READS, &fc->flags) &&
187 	    !test_bit(DROP_WRITES, &fc->flags) && !test_bit(ERROR_WRITES, &fc->flags)) {
188 		set_bit(ERROR_WRITES, &fc->flags);
189 		set_bit(ERROR_READS, &fc->flags);
190 	}
191 
192 	return 0;
193 }
194 
195 /*
196  * Construct a flakey mapping:
197  * <dev_path> <offset> <up interval> <down interval> [<#feature args> [<arg>]*]
198  *
199  *   Feature args:
200  *     [drop_writes]
201  *     [corrupt_bio_byte <Nth_byte> <direction> <value> <bio_flags>]
202  *
203  *   Nth_byte starts from 1 for the first byte.
204  *   Direction is r for READ or w for WRITE.
205  *   bio_flags is ignored if 0.
206  */
207 static int flakey_ctr(struct dm_target *ti, unsigned int argc, char **argv)
208 {
209 	static const struct dm_arg _args[] = {
210 		{0, UINT_MAX, "Invalid up interval"},
211 		{0, UINT_MAX, "Invalid down interval"},
212 	};
213 
214 	int r;
215 	struct flakey_c *fc;
216 	unsigned long long tmpll;
217 	struct dm_arg_set as;
218 	const char *devname;
219 	char dummy;
220 
221 	as.argc = argc;
222 	as.argv = argv;
223 
224 	if (argc < 4) {
225 		ti->error = "Invalid argument count";
226 		return -EINVAL;
227 	}
228 
229 	fc = kzalloc(sizeof(*fc), GFP_KERNEL);
230 	if (!fc) {
231 		ti->error = "Cannot allocate context";
232 		return -ENOMEM;
233 	}
234 	fc->start_time = jiffies;
235 
236 	devname = dm_shift_arg(&as);
237 
238 	r = -EINVAL;
239 	if (sscanf(dm_shift_arg(&as), "%llu%c", &tmpll, &dummy) != 1 || tmpll != (sector_t)tmpll) {
240 		ti->error = "Invalid device sector";
241 		goto bad;
242 	}
243 	fc->start = tmpll;
244 
245 	r = dm_read_arg(_args, &as, &fc->up_interval, &ti->error);
246 	if (r)
247 		goto bad;
248 
249 	r = dm_read_arg(_args, &as, &fc->down_interval, &ti->error);
250 	if (r)
251 		goto bad;
252 
253 	if (!(fc->up_interval + fc->down_interval)) {
254 		ti->error = "Total (up + down) interval is zero";
255 		r = -EINVAL;
256 		goto bad;
257 	}
258 
259 	if (fc->up_interval + fc->down_interval < fc->up_interval) {
260 		ti->error = "Interval overflow";
261 		r = -EINVAL;
262 		goto bad;
263 	}
264 
265 	r = parse_features(&as, fc, ti);
266 	if (r)
267 		goto bad;
268 
269 	r = dm_get_device(ti, devname, dm_table_get_mode(ti->table), &fc->dev);
270 	if (r) {
271 		ti->error = "Device lookup failed";
272 		goto bad;
273 	}
274 
275 	ti->num_flush_bios = 1;
276 	ti->num_discard_bios = 1;
277 	ti->per_io_data_size = sizeof(struct per_bio_data);
278 	ti->private = fc;
279 	return 0;
280 
281 bad:
282 	kfree(fc);
283 	return r;
284 }
285 
286 static void flakey_dtr(struct dm_target *ti)
287 {
288 	struct flakey_c *fc = ti->private;
289 
290 	dm_put_device(ti, fc->dev);
291 	kfree(fc);
292 }
293 
294 static sector_t flakey_map_sector(struct dm_target *ti, sector_t bi_sector)
295 {
296 	struct flakey_c *fc = ti->private;
297 
298 	return fc->start + dm_target_offset(ti, bi_sector);
299 }
300 
301 static void flakey_map_bio(struct dm_target *ti, struct bio *bio)
302 {
303 	struct flakey_c *fc = ti->private;
304 
305 	bio_set_dev(bio, fc->dev->bdev);
306 	bio->bi_iter.bi_sector = flakey_map_sector(ti, bio->bi_iter.bi_sector);
307 }
308 
309 static void corrupt_bio_data(struct bio *bio, struct flakey_c *fc)
310 {
311 	unsigned int corrupt_bio_byte = fc->corrupt_bio_byte - 1;
312 
313 	struct bvec_iter iter;
314 	struct bio_vec bvec;
315 
316 	if (!bio_has_data(bio))
317 		return;
318 
319 	/*
320 	 * Overwrite the Nth byte of the bio's data, on whichever page
321 	 * it falls.
322 	 */
323 	bio_for_each_segment(bvec, bio, iter) {
324 		if (bio_iter_len(bio, iter) > corrupt_bio_byte) {
325 			char *segment;
326 			struct page *page = bio_iter_page(bio, iter);
327 			if (unlikely(page == ZERO_PAGE(0)))
328 				break;
329 			segment = bvec_kmap_local(&bvec);
330 			segment[corrupt_bio_byte] = fc->corrupt_bio_value;
331 			kunmap_local(segment);
332 			DMDEBUG("Corrupting data bio=%p by writing %u to byte %u "
333 				"(rw=%c bi_opf=%u bi_sector=%llu size=%u)\n",
334 				bio, fc->corrupt_bio_value, fc->corrupt_bio_byte,
335 				(bio_data_dir(bio) == WRITE) ? 'w' : 'r', bio->bi_opf,
336 				(unsigned long long)bio->bi_iter.bi_sector, bio->bi_iter.bi_size);
337 			break;
338 		}
339 		corrupt_bio_byte -= bio_iter_len(bio, iter);
340 	}
341 }
342 
343 static int flakey_map(struct dm_target *ti, struct bio *bio)
344 {
345 	struct flakey_c *fc = ti->private;
346 	unsigned int elapsed;
347 	struct per_bio_data *pb = dm_per_bio_data(bio, sizeof(struct per_bio_data));
348 
349 	pb->bio_submitted = false;
350 
351 	if (op_is_zone_mgmt(bio_op(bio)))
352 		goto map_bio;
353 
354 	/* Are we alive ? */
355 	elapsed = (jiffies - fc->start_time) / HZ;
356 	if (elapsed % (fc->up_interval + fc->down_interval) >= fc->up_interval) {
357 		/*
358 		 * Flag this bio as submitted while down.
359 		 */
360 		pb->bio_submitted = true;
361 
362 		/*
363 		 * Error reads if neither corrupt_bio_byte or drop_writes or error_writes are set.
364 		 * Otherwise, flakey_end_io() will decide if the reads should be modified.
365 		 */
366 		if (bio_data_dir(bio) == READ) {
367 			if (test_bit(ERROR_READS, &fc->flags))
368 				return DM_MAPIO_KILL;
369 			goto map_bio;
370 		}
371 
372 		/*
373 		 * Drop or error writes?
374 		 */
375 		if (test_bit(DROP_WRITES, &fc->flags)) {
376 			bio_endio(bio);
377 			return DM_MAPIO_SUBMITTED;
378 		} else if (test_bit(ERROR_WRITES, &fc->flags)) {
379 			bio_io_error(bio);
380 			return DM_MAPIO_SUBMITTED;
381 		}
382 
383 		/*
384 		 * Corrupt matching writes.
385 		 */
386 		if (fc->corrupt_bio_byte) {
387 			if (fc->corrupt_bio_rw == WRITE) {
388 				if (all_corrupt_bio_flags_match(bio, fc))
389 					corrupt_bio_data(bio, fc);
390 			}
391 			goto map_bio;
392 		}
393 	}
394 
395 map_bio:
396 	flakey_map_bio(ti, bio);
397 
398 	return DM_MAPIO_REMAPPED;
399 }
400 
401 static int flakey_end_io(struct dm_target *ti, struct bio *bio,
402 			 blk_status_t *error)
403 {
404 	struct flakey_c *fc = ti->private;
405 	struct per_bio_data *pb = dm_per_bio_data(bio, sizeof(struct per_bio_data));
406 
407 	if (op_is_zone_mgmt(bio_op(bio)))
408 		return DM_ENDIO_DONE;
409 
410 	if (!*error && pb->bio_submitted && (bio_data_dir(bio) == READ)) {
411 		if (fc->corrupt_bio_byte) {
412 			if ((fc->corrupt_bio_rw == READ) &&
413 			    all_corrupt_bio_flags_match(bio, fc)) {
414 				/*
415 				 * Corrupt successful matching READs while in down state.
416 				 */
417 				corrupt_bio_data(bio, fc);
418 			}
419 		}
420 		if (test_bit(ERROR_READS, &fc->flags)) {
421 			/*
422 			 * Error read during the down_interval if drop_writes
423 			 * and error_writes were not configured.
424 			 */
425 			*error = BLK_STS_IOERR;
426 		}
427 	}
428 
429 	return DM_ENDIO_DONE;
430 }
431 
432 static void flakey_status(struct dm_target *ti, status_type_t type,
433 			  unsigned int status_flags, char *result, unsigned int maxlen)
434 {
435 	unsigned int sz = 0;
436 	struct flakey_c *fc = ti->private;
437 	unsigned int error_reads, drop_writes, error_writes;
438 
439 	switch (type) {
440 	case STATUSTYPE_INFO:
441 		result[0] = '\0';
442 		break;
443 
444 	case STATUSTYPE_TABLE:
445 		DMEMIT("%s %llu %u %u", fc->dev->name,
446 		       (unsigned long long)fc->start, fc->up_interval,
447 		       fc->down_interval);
448 
449 		error_reads = test_bit(ERROR_READS, &fc->flags);
450 		drop_writes = test_bit(DROP_WRITES, &fc->flags);
451 		error_writes = test_bit(ERROR_WRITES, &fc->flags);
452 		DMEMIT(" %u", error_reads + drop_writes + error_writes + (fc->corrupt_bio_byte > 0) * 5);
453 
454 		if (error_reads)
455 			DMEMIT(" error_reads");
456 		if (drop_writes)
457 			DMEMIT(" drop_writes");
458 		else if (error_writes)
459 			DMEMIT(" error_writes");
460 
461 		if (fc->corrupt_bio_byte)
462 			DMEMIT(" corrupt_bio_byte %u %c %u %u",
463 			       fc->corrupt_bio_byte,
464 			       (fc->corrupt_bio_rw == WRITE) ? 'w' : 'r',
465 			       fc->corrupt_bio_value, fc->corrupt_bio_flags);
466 
467 		break;
468 
469 	case STATUSTYPE_IMA:
470 		result[0] = '\0';
471 		break;
472 	}
473 }
474 
475 static int flakey_prepare_ioctl(struct dm_target *ti, struct block_device **bdev)
476 {
477 	struct flakey_c *fc = ti->private;
478 
479 	*bdev = fc->dev->bdev;
480 
481 	/*
482 	 * Only pass ioctls through if the device sizes match exactly.
483 	 */
484 	if (fc->start || ti->len != bdev_nr_sectors((*bdev)))
485 		return 1;
486 	return 0;
487 }
488 
489 #ifdef CONFIG_BLK_DEV_ZONED
490 static int flakey_report_zones(struct dm_target *ti,
491 		struct dm_report_zones_args *args, unsigned int nr_zones)
492 {
493 	struct flakey_c *fc = ti->private;
494 
495 	return dm_report_zones(fc->dev->bdev, fc->start,
496 			       flakey_map_sector(ti, args->next_sector),
497 			       args, nr_zones);
498 }
499 #else
500 #define flakey_report_zones NULL
501 #endif
502 
503 static int flakey_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data)
504 {
505 	struct flakey_c *fc = ti->private;
506 
507 	return fn(ti, fc->dev, fc->start, ti->len, data);
508 }
509 
510 static struct target_type flakey_target = {
511 	.name   = "flakey",
512 	.version = {1, 5, 0},
513 	.features = DM_TARGET_ZONED_HM | DM_TARGET_PASSES_CRYPTO,
514 	.report_zones = flakey_report_zones,
515 	.module = THIS_MODULE,
516 	.ctr    = flakey_ctr,
517 	.dtr    = flakey_dtr,
518 	.map    = flakey_map,
519 	.end_io = flakey_end_io,
520 	.status = flakey_status,
521 	.prepare_ioctl = flakey_prepare_ioctl,
522 	.iterate_devices = flakey_iterate_devices,
523 };
524 module_dm(flakey);
525 
526 MODULE_DESCRIPTION(DM_NAME " flakey target");
527 MODULE_AUTHOR("Joe Thornber <dm-devel@redhat.com>");
528 MODULE_LICENSE("GPL");
529