1 /*
2  * Copyright (c) 2015 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/malloc.h>
37 #include <dev/disk/dm/dm.h>
38 
39 MALLOC_DEFINE(M_DMFLAKEY, "dm_flakey", "Device Mapper Target Flakey");
40 
41 /* dm_flakey never updates any field after initialization */
42 typedef struct target_flakey_config {
43 	dm_pdev_t *pdev;
44 	uint64_t offset;
45 	int up_int;
46 	int down_int;
47 	int offset_time;
48 
49 	/* drop_writes feature */
50 	int drop_writes;
51 
52 	/* corrupt_bio_byte feature */
53 	unsigned int corrupt_buf_byte;
54 	unsigned int corrupt_buf_rw;
55 	unsigned int corrupt_buf_value;
56 	unsigned int corrupt_buf_flags;  /* for B_XXX flags */
57 } dm_target_flakey_config_t;
58 
59 #define FLAKEY_CORRUPT_DIR(tfc) \
60 	((tfc)->corrupt_buf_rw == BUF_CMD_READ ? 'r' : 'w')
61 
62 static int _init_features(dm_target_flakey_config_t*, int, char**);
63 static __inline void _submit(dm_target_flakey_config_t*, struct bio*);
64 static int _flakey_read(dm_target_flakey_config_t*, struct buf*);
65 static int _flakey_write(dm_target_flakey_config_t*, struct buf*);
66 static int _flakey_corrupt_buf(dm_target_flakey_config_t*, struct bio*);
67 
68 static int
69 dm_target_flakey_init(dm_table_entry_t *table_en, int argc, char **argv)
70 {
71 	dm_target_flakey_config_t *tfc;
72 	dm_pdev_t *dmp;
73 	int err;
74 
75 	dmdebug("Flakey target init: argc=%d\n", argc);
76 
77 	if (argc < 4) {
78 		kprintf("Flakey target takes 4 or more args\n");
79 		return EINVAL;
80 	}
81 
82 	tfc = kmalloc(sizeof(*tfc), M_DMFLAKEY, M_WAITOK | M_ZERO);
83 	if (tfc == NULL)
84 		return ENOMEM;
85 
86 	if ((dmp = dm_pdev_insert(argv[0])) == NULL) {
87 		err = ENOENT;
88 		goto fail;
89 	}
90 	tfc->pdev = dmp;
91 	tfc->offset = atoi64(argv[1]);
92 	tfc->up_int = atoi64(argv[2]);
93 	tfc->down_int = atoi64(argv[3]);
94 	tfc->offset_time = ticks;
95 
96 	if ((tfc->up_int + tfc->down_int) == 0) {
97 		kprintf("Sum of up/down interval is 0\n");
98 		err = EINVAL;
99 		goto fail;
100 	}
101 
102 	if (tfc->up_int + tfc->down_int < tfc->up_int) {
103 		kprintf("Interval time overflow\n");
104 		err = EINVAL;
105 		goto fail;
106 	}
107 
108 	err = _init_features(tfc, argc - 4, argv + 4);
109 	if (err)
110 		goto fail;
111 
112 	dm_table_add_deps(table_en, dmp);
113 
114 	dm_table_init_target(table_en, tfc);
115 
116 	return 0;
117 fail:
118 	kfree(tfc, M_DMFLAKEY);
119 	return err;
120 }
121 
122 static int
123 _init_features(dm_target_flakey_config_t *tfc, int argc, char **argv)
124 {
125 	char *arg;
126 	unsigned int value;
127 
128 	if (argc == 0)
129 		return 0;
130 
131 	argc = atoi64(*argv++);  /* # of args for features */
132 	if (argc > 6) {
133 		kprintf("Invalid # of feature args %d\n", argc);
134 		return EINVAL;
135 	}
136 
137 	while (argc) {
138 		argc--;
139 		arg = *argv++;
140 
141 		/* drop_writes */
142 		if (strcmp(arg, "drop_writes") == 0) {
143 			tfc->drop_writes = 1;
144 			continue;
145 		}
146 
147 		/* corrupt_bio_byte <Nth_byte> <direction> <value> <flags> */
148 		if (strcmp(arg, "corrupt_bio_byte") == 0) {
149 			if (argc < 4) {
150 				kprintf("Invalid # of feature args %d for "
151 					"corrupt_bio_byte\n", argc);
152 				return EINVAL;
153 			}
154 
155 			/* <Nth_byte> */
156 			argc--;
157 			value = atoi64(*argv++);
158 			if (value < 1) {
159 				kprintf("Invalid corrupt_bio_byte "
160 					"<Nth_byte> arg %u\n", value);
161 				return EINVAL;
162 			}
163 			tfc->corrupt_buf_byte = value;
164 
165 			/* <direction> */
166 			argc--;
167 			arg = *argv++;
168 			if (strcmp(arg, "r") == 0) {
169 				tfc->corrupt_buf_rw = BUF_CMD_READ;
170 			} else if (strcmp(arg, "w") == 0) {
171 				tfc->corrupt_buf_rw = BUF_CMD_WRITE;
172 			} else {
173 				kprintf("Invalid corrupt_bio_byte "
174 					"<direction> arg %s\n", arg);
175 				return EINVAL;
176 			}
177 
178 			/* <value> */
179 			argc--;
180 			value = atoi64(*argv++);
181 			if (value > 0xff) {
182 				kprintf("Invalid corrupt_bio_byte "
183 					"<value> arg %u\n", value);
184 				return EINVAL;
185 			}
186 			tfc->corrupt_buf_value = value;
187 
188 			/* <flags> */
189 			argc--;
190 			tfc->corrupt_buf_flags = atoi64(*argv++);
191 
192 			continue;
193 		}
194 
195 		kprintf("Unknown Flakey target feature %s\n", arg);
196 		return EINVAL;
197 	}
198 
199 	if (tfc->drop_writes && (tfc->corrupt_buf_rw == BUF_CMD_WRITE)) {
200 		kprintf("Flakey target doesn't allow drop_writes feature "
201 			"and corrupt_bio_byte feature with 'w' set\n");
202 		return EINVAL;
203 	}
204 
205 	return 0;
206 }
207 
208 static int
209 dm_target_flakey_destroy(dm_table_entry_t *table_en)
210 {
211 	dm_target_flakey_config_t *tfc;
212 
213 	tfc = table_en->target_config;
214 	if (tfc == NULL)
215 		return 0;
216 
217 	dm_pdev_decr(tfc->pdev);
218 
219 	kfree(tfc, M_DMFLAKEY);
220 
221 	return 0;
222 }
223 
224 static int
225 dm_target_flakey_strategy(dm_table_entry_t *table_en, struct buf *bp)
226 {
227 	dm_target_flakey_config_t *tfc;
228 	int elapsed;
229 
230 	tfc = table_en->target_config;
231 
232 	elapsed = (ticks - tfc->offset_time) / hz;
233 	if (elapsed % (tfc->up_int + tfc->down_int) >= tfc->up_int) {
234 		switch (bp->b_cmd) {
235 		case BUF_CMD_READ:
236 			return _flakey_read(tfc, bp);
237 		case BUF_CMD_WRITE:
238 			return _flakey_write(tfc, bp);
239 		default:
240 			break;
241 		}
242 	}
243 
244 	/* This is what linear target does */
245 	_submit(tfc, &bp->b_bio1);
246 
247 	return 0;
248 }
249 
250 static __inline
251 void
252 _submit(dm_target_flakey_config_t *tfc, struct bio *bio)
253 {
254 	bio->bio_offset += tfc->offset * DEV_BSIZE;
255 	vn_strategy(tfc->pdev->pdev_vnode, bio);
256 }
257 
258 static __inline
259 void
260 _flakey_eio_buf(struct buf *bp)
261 {
262 	bp->b_error = EIO;
263 	bp->b_resid = 0;
264 }
265 
266 static void
267 _flakey_read_iodone(struct bio *bio)
268 {
269 	struct bio *obio;
270 	dm_target_flakey_config_t *tfc;
271 
272 	tfc = bio->bio_caller_info1.ptr;
273 	obio = pop_bio(bio);
274 
275 	/*
276 	 * Linux dm-flakey has changed its read behavior in 2016.
277 	 * This conditional is to sync with that change.
278 	 */
279 	if (tfc->corrupt_buf_byte && tfc->corrupt_buf_rw == BUF_CMD_READ)
280 		_flakey_corrupt_buf(tfc, obio);
281 	else if (!tfc->drop_writes)
282 		_flakey_eio_buf(bio->bio_buf);
283 
284 	biodone(obio);
285 }
286 
287 static int
288 _flakey_read(dm_target_flakey_config_t *tfc, struct buf *bp)
289 {
290 	struct bio *bio = &bp->b_bio1;
291 	struct bio *nbio;
292 
293 	/*
294 	 * Linux dm-flakey has changed its read behavior in 2016.
295 	 * This conditional is to sync with that change.
296 	 */
297 	if (!tfc->corrupt_buf_byte && !tfc->drop_writes) {
298 		_flakey_eio_buf(bp);
299 		biodone(bio);
300 		return 0;
301 	}
302 
303 	nbio = push_bio(bio);
304 	nbio->bio_done = _flakey_read_iodone;
305 	nbio->bio_caller_info1.ptr = tfc;
306 	nbio->bio_offset = pop_bio(nbio)->bio_offset;
307 
308 	_submit(tfc, nbio);
309 
310 	return 0;
311 }
312 
313 static int
314 _flakey_write(dm_target_flakey_config_t *tfc, struct buf *bp)
315 {
316 	struct bio *bio = &bp->b_bio1;
317 
318 	if (tfc->drop_writes) {
319 		dmdebug("bio=%p drop_writes offset=%ju\n",
320 			bio, bio->bio_offset);
321 		biodone(bio);
322 		return 0;
323 	}
324 
325 	if (tfc->corrupt_buf_byte && tfc->corrupt_buf_rw == BUF_CMD_WRITE) {
326 		_flakey_corrupt_buf(tfc, bio);
327 		_submit(tfc, bio);
328 		return 0;
329 	}
330 
331 	/* Error all I/Os if neither of the above two */
332 	_flakey_eio_buf(bp);
333 	biodone(bio);
334 
335 	return 0;
336 }
337 
338 static int
339 _flakey_corrupt_buf(dm_target_flakey_config_t *tfc, struct bio *bio)
340 {
341 	struct buf *bp;
342 
343 	bp = bio->bio_buf;
344 
345 	if (bp->b_data == NULL)
346 		return 1;
347 	if (bp->b_error)
348 		return 1;  /* Don't corrupt on error */
349 	if (bp->b_bcount < tfc->corrupt_buf_byte)
350 		return 1;
351 	if ((bp->b_flags & tfc->corrupt_buf_flags) != tfc->corrupt_buf_flags)
352 		return 1;
353 
354 	bp->b_data[tfc->corrupt_buf_byte - 1] = tfc->corrupt_buf_value;
355 	dmdebug("bio=%p dir=%c offset=%ju Nth=%u value=%u\n",
356 		bio,
357 		FLAKEY_CORRUPT_DIR(tfc),
358 		bio->bio_offset,
359 		tfc->corrupt_buf_byte,
360 		tfc->corrupt_buf_value);
361 
362 	return 0;
363 }
364 
365 static char *
366 dm_target_flakey_table(void *target_config)
367 {
368 	dm_target_flakey_config_t *tfc;
369 	char *params, *p;
370 	int drop_writes;
371 
372 	tfc = target_config;
373 	KKASSERT(tfc != NULL);
374 
375 	drop_writes = tfc->drop_writes;
376 
377 	params = dm_alloc_string(DM_MAX_PARAMS_SIZE);
378 	p = params;
379 	p += ksnprintf(p, DM_MAX_PARAMS_SIZE, "%s %d %d %d %u ",
380 		tfc->pdev->udev_name, tfc->offset_time,
381 		tfc->up_int, tfc->down_int,
382 		drop_writes + (tfc->corrupt_buf_byte > 0) * 5);
383 
384 	if (drop_writes)
385 		p += ksnprintf(p, DM_MAX_PARAMS_SIZE, "drop_writes ");
386 
387 	if (tfc->corrupt_buf_byte)
388 		p += ksnprintf(p, DM_MAX_PARAMS_SIZE,
389 			"corrupt_bio_byte %u %c %u %u ",
390 			tfc->corrupt_buf_byte,
391 			FLAKEY_CORRUPT_DIR(tfc),
392 			tfc->corrupt_buf_value,
393 			tfc->corrupt_buf_flags);
394 	*(--p) = '\0';
395 
396 	return params;
397 }
398 
399 static int
400 dmtf_mod_handler(module_t mod, int type, void *unused)
401 {
402 	dm_target_t *dmt = NULL;
403 	int err = 0;
404 
405 	switch(type) {
406 	case MOD_LOAD:
407 		if ((dmt = dm_target_lookup("flakey")) != NULL) {
408 			dm_target_unbusy(dmt);
409 			return EEXIST;
410 		}
411 		dmt = dm_target_alloc("flakey");
412 		dmt->version[0] = 1;
413 		dmt->version[1] = 0;
414 		dmt->version[2] = 0;
415 		strlcpy(dmt->name, "flakey", DM_MAX_TYPE_NAME);
416 		dmt->init = &dm_target_flakey_init;
417 		dmt->destroy = &dm_target_flakey_destroy;
418 		dmt->strategy = &dm_target_flakey_strategy;
419 		dmt->table = &dm_target_flakey_table;
420 
421 		err = dm_target_insert(dmt);
422 		if (err == 0)
423 			kprintf("dm_target_flakey: Successfully initialized\n");
424 		break;
425 
426 	case MOD_UNLOAD:
427 		err = dm_target_remove("flakey");
428 		if (err == 0)
429 			kprintf("dm_target_flakey: unloaded\n");
430 		break;
431 	}
432 
433 	return err;
434 }
435 
436 DM_TARGET_MODULE(dm_target_flakey, dmtf_mod_handler);
437