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 <dev/disk/dm/dm.h>
36 
37 MALLOC_DEFINE(M_DMFLAKEY, "dm_flakey", "Device Mapper Target Flakey");
38 
39 /* dm_flakey never updates any field after initialization */
40 typedef struct target_flakey_config {
41 	dm_pdev_t *pdev;
42 	uint64_t offset;
43 	int up_int;
44 	int down_int;
45 	int offset_time;
46 
47 	/* drop_writes feature */
48 	int drop_writes;
49 
50 	/* corrupt_bio_byte feature */
51 	unsigned int corrupt_buf_byte;
52 	unsigned int corrupt_buf_rw;
53 	unsigned int corrupt_buf_value;
54 	unsigned int corrupt_buf_flags;  /* for B_XXX flags */
55 } dm_target_flakey_config_t;
56 
57 #define FLAKEY_CORRUPT_DIR(tfc) \
58 	((tfc)->corrupt_buf_rw == BUF_CMD_READ ? 'r' : 'w')
59 
60 static int _init_features(dm_target_flakey_config_t*, int, char**);
61 static __inline void _submit(dm_target_flakey_config_t*, struct bio*);
62 static int _flakey_read(dm_target_flakey_config_t*, struct buf*);
63 static int _flakey_write(dm_target_flakey_config_t*, struct buf*);
64 static int _flakey_corrupt_buf(dm_target_flakey_config_t*, struct bio*);
65 
66 static int
67 dm_target_flakey_init(dm_table_entry_t *table_en, int argc, char **argv)
68 {
69 	dm_target_flakey_config_t *tfc;
70 	dm_pdev_t *dmp;
71 	int err;
72 
73 	dmdebug("Flakey target init: argc=%d\n", argc);
74 
75 	if (argc < 4) {
76 		kprintf("Flakey target takes 4 or more args\n");
77 		return EINVAL;
78 	}
79 
80 	tfc = kmalloc(sizeof(*tfc), M_DMFLAKEY, M_WAITOK | M_ZERO);
81 	if (tfc == NULL)
82 		return ENOMEM;
83 
84 	if ((dmp = dm_pdev_insert(argv[0])) == NULL) {
85 		err = ENOENT;
86 		goto fail;
87 	}
88 	tfc->pdev = dmp;
89 	tfc->offset = atoi64(argv[1]);
90 	tfc->up_int = atoi64(argv[2]);
91 	tfc->down_int = atoi64(argv[3]);
92 	tfc->offset_time = ticks;
93 
94 	if ((tfc->up_int + tfc->down_int) == 0) {
95 		kprintf("Sum of up/down interval is 0\n");
96 		err = EINVAL;
97 		goto fail;
98 	}
99 
100 	if (tfc->up_int + tfc->down_int < tfc->up_int) {
101 		kprintf("Interval time overflow\n");
102 		err = EINVAL;
103 		goto fail;
104 	}
105 
106 	err = _init_features(tfc, argc - 4, argv + 4);
107 	if (err)
108 		goto fail;
109 
110 	dm_table_add_deps(table_en, dmp);
111 
112 	dm_table_init_target(table_en, tfc);
113 
114 	return 0;
115 fail:
116 	kfree(tfc, M_DMFLAKEY);
117 	return err;
118 }
119 
120 static int
121 _init_features(dm_target_flakey_config_t *tfc, int argc, char **argv)
122 {
123 	char *arg;
124 	unsigned int value;
125 
126 	if (argc == 0)
127 		return 0;
128 
129 	argc = atoi64(*argv++);  /* # of args for features */
130 	if (argc > 6) {
131 		kprintf("Invalid # of feature args %d\n", argc);
132 		return EINVAL;
133 	}
134 
135 	while (argc) {
136 		argc--;
137 		arg = *argv++;
138 
139 		/* drop_writes */
140 		if (strcmp(arg, "drop_writes") == 0) {
141 			tfc->drop_writes = 1;
142 			continue;
143 		}
144 
145 		/* corrupt_bio_byte <Nth_byte> <direction> <value> <flags> */
146 		if (strcmp(arg, "corrupt_bio_byte") == 0) {
147 			if (argc < 4) {
148 				kprintf("Invalid # of feature args %d for "
149 					"corrupt_bio_byte\n", argc);
150 				return EINVAL;
151 			}
152 
153 			/* <Nth_byte> */
154 			argc--;
155 			value = atoi64(*argv++);
156 			if (value < 1) {
157 				kprintf("Invalid corrupt_bio_byte "
158 					"<Nth_byte> arg %u\n", value);
159 				return EINVAL;
160 			}
161 			tfc->corrupt_buf_byte = value;
162 
163 			/* <direction> */
164 			argc--;
165 			arg = *argv++;
166 			if (strcmp(arg, "r") == 0) {
167 				tfc->corrupt_buf_rw = BUF_CMD_READ;
168 			} else if (strcmp(arg, "w") == 0) {
169 				tfc->corrupt_buf_rw = BUF_CMD_WRITE;
170 			} else {
171 				kprintf("Invalid corrupt_bio_byte "
172 					"<direction> arg %s\n", arg);
173 				return EINVAL;
174 			}
175 
176 			/* <value> */
177 			argc--;
178 			value = atoi64(*argv++);
179 			if (value > 0xff) {
180 				kprintf("Invalid corrupt_bio_byte "
181 					"<value> arg %u\n", value);
182 				return EINVAL;
183 			}
184 			tfc->corrupt_buf_value = value;
185 
186 			/* <flags> */
187 			argc--;
188 			tfc->corrupt_buf_flags = atoi64(*argv++);
189 
190 			continue;
191 		}
192 
193 		kprintf("Unknown Flakey target feature %s\n", arg);
194 		return EINVAL;
195 	}
196 
197 	if (tfc->drop_writes && (tfc->corrupt_buf_rw == BUF_CMD_WRITE)) {
198 		kprintf("Flakey target doesn't allow drop_writes feature "
199 			"and corrupt_bio_byte feature with 'w' set\n");
200 		return EINVAL;
201 	}
202 
203 	return 0;
204 }
205 
206 static int
207 dm_target_flakey_destroy(dm_table_entry_t *table_en)
208 {
209 	dm_target_flakey_config_t *tfc;
210 
211 	tfc = table_en->target_config;
212 	if (tfc == NULL)
213 		return 0;
214 
215 	dm_pdev_decr(tfc->pdev);
216 
217 	kfree(tfc, M_DMFLAKEY);
218 
219 	return 0;
220 }
221 
222 static int
223 dm_target_flakey_strategy(dm_table_entry_t *table_en, struct buf *bp)
224 {
225 	dm_target_flakey_config_t *tfc;
226 	int elapsed;
227 
228 	tfc = table_en->target_config;
229 
230 	elapsed = (ticks - tfc->offset_time) / hz;
231 	if (elapsed % (tfc->up_int + tfc->down_int) >= tfc->up_int) {
232 		switch (bp->b_cmd) {
233 		case BUF_CMD_READ:
234 			return _flakey_read(tfc, bp);
235 		case BUF_CMD_WRITE:
236 			return _flakey_write(tfc, bp);
237 		default:
238 			break;
239 		}
240 	}
241 
242 	/* This is what linear target does */
243 	_submit(tfc, &bp->b_bio1);
244 
245 	return 0;
246 }
247 
248 static __inline
249 void
250 _submit(dm_target_flakey_config_t *tfc, struct bio *bio)
251 {
252 	bio->bio_offset += tfc->offset * DEV_BSIZE;
253 	vn_strategy(tfc->pdev->pdev_vnode, bio);
254 }
255 
256 static __inline
257 void
258 _flakey_eio_buf(struct buf *bp)
259 {
260 	bp->b_error = EIO;
261 	bp->b_resid = 0;
262 }
263 
264 static void
265 _flakey_read_iodone(struct bio *bio)
266 {
267 	struct bio *obio;
268 	dm_target_flakey_config_t *tfc;
269 
270 	tfc = bio->bio_caller_info1.ptr;
271 	obio = pop_bio(bio);
272 
273 	/*
274 	 * Linux dm-flakey has changed its read behavior in 2016.
275 	 * This conditional is to sync with that change.
276 	 */
277 	if (tfc->corrupt_buf_byte && tfc->corrupt_buf_rw == BUF_CMD_READ)
278 		_flakey_corrupt_buf(tfc, obio);
279 	else if (!tfc->drop_writes)
280 		_flakey_eio_buf(bio->bio_buf);
281 
282 	biodone(obio);
283 }
284 
285 static int
286 _flakey_read(dm_target_flakey_config_t *tfc, struct buf *bp)
287 {
288 	struct bio *bio = &bp->b_bio1;
289 	struct bio *nbio;
290 
291 	/*
292 	 * Linux dm-flakey has changed its read behavior in 2016.
293 	 * This conditional is to sync with that change.
294 	 */
295 	if (!tfc->corrupt_buf_byte && !tfc->drop_writes) {
296 		_flakey_eio_buf(bp);
297 		biodone(bio);
298 		return 0;
299 	}
300 
301 	nbio = push_bio(bio);
302 	nbio->bio_done = _flakey_read_iodone;
303 	nbio->bio_caller_info1.ptr = tfc;
304 	nbio->bio_offset = pop_bio(nbio)->bio_offset;
305 
306 	_submit(tfc, nbio);
307 
308 	return 0;
309 }
310 
311 static int
312 _flakey_write(dm_target_flakey_config_t *tfc, struct buf *bp)
313 {
314 	struct bio *bio = &bp->b_bio1;
315 
316 	if (tfc->drop_writes) {
317 		dmdebug("bio=%p drop_writes offset=%ju\n",
318 			bio, bio->bio_offset);
319 		biodone(bio);
320 		return 0;
321 	}
322 
323 	if (tfc->corrupt_buf_byte && tfc->corrupt_buf_rw == BUF_CMD_WRITE) {
324 		_flakey_corrupt_buf(tfc, bio);
325 		_submit(tfc, bio);
326 		return 0;
327 	}
328 
329 	/* Error all I/Os if neither of the above two */
330 	_flakey_eio_buf(bp);
331 	biodone(bio);
332 
333 	return 0;
334 }
335 
336 static int
337 _flakey_corrupt_buf(dm_target_flakey_config_t *tfc, struct bio *bio)
338 {
339 	struct buf *bp;
340 
341 	bp = bio->bio_buf;
342 
343 	if (bp->b_data == NULL)
344 		return 1;
345 	if (bp->b_error)
346 		return 1;  /* Don't corrupt on error */
347 	if (bp->b_bcount < tfc->corrupt_buf_byte)
348 		return 1;
349 	if ((bp->b_flags & tfc->corrupt_buf_flags) != tfc->corrupt_buf_flags)
350 		return 1;
351 
352 	bp->b_data[tfc->corrupt_buf_byte - 1] = tfc->corrupt_buf_value;
353 	dmdebug("bio=%p dir=%c offset=%ju Nth=%u value=%u\n",
354 		bio,
355 		FLAKEY_CORRUPT_DIR(tfc),
356 		bio->bio_offset,
357 		tfc->corrupt_buf_byte,
358 		tfc->corrupt_buf_value);
359 
360 	return 0;
361 }
362 
363 static char *
364 dm_target_flakey_table(void *target_config)
365 {
366 	dm_target_flakey_config_t *tfc;
367 	char *params, *p;
368 	int drop_writes;
369 
370 	tfc = target_config;
371 	KKASSERT(tfc != NULL);
372 
373 	drop_writes = tfc->drop_writes;
374 
375 	params = dm_alloc_string(DM_MAX_PARAMS_SIZE);
376 	p = params;
377 	p += ksnprintf(p, DM_MAX_PARAMS_SIZE, "%s %d %d %d %u ",
378 		tfc->pdev->udev_name, tfc->offset_time,
379 		tfc->up_int, tfc->down_int,
380 		drop_writes + (tfc->corrupt_buf_byte > 0) * 5);
381 
382 	if (drop_writes)
383 		p += ksnprintf(p, DM_MAX_PARAMS_SIZE, "drop_writes ");
384 
385 	if (tfc->corrupt_buf_byte)
386 		p += ksnprintf(p, DM_MAX_PARAMS_SIZE,
387 			"corrupt_bio_byte %u %c %u %u ",
388 			tfc->corrupt_buf_byte,
389 			FLAKEY_CORRUPT_DIR(tfc),
390 			tfc->corrupt_buf_value,
391 			tfc->corrupt_buf_flags);
392 	*(--p) = '\0';
393 
394 	return params;
395 }
396 
397 static int
398 dmtf_mod_handler(module_t mod, int type, void *unused)
399 {
400 	dm_target_t *dmt = NULL;
401 	int err = 0;
402 
403 	switch(type) {
404 	case MOD_LOAD:
405 		if ((dmt = dm_target_lookup("flakey")) != NULL) {
406 			dm_target_unbusy(dmt);
407 			return EEXIST;
408 		}
409 		dmt = dm_target_alloc("flakey");
410 		dmt->version[0] = 1;
411 		dmt->version[1] = 0;
412 		dmt->version[2] = 0;
413 		strlcpy(dmt->name, "flakey", DM_MAX_TYPE_NAME);
414 		dmt->init = &dm_target_flakey_init;
415 		dmt->destroy = &dm_target_flakey_destroy;
416 		dmt->strategy = &dm_target_flakey_strategy;
417 		dmt->table = &dm_target_flakey_table;
418 
419 		err = dm_target_insert(dmt);
420 		if (err == 0)
421 			kprintf("dm_target_flakey: Successfully initialized\n");
422 		break;
423 
424 	case MOD_UNLOAD:
425 		err = dm_target_remove("flakey");
426 		if (err == 0)
427 			kprintf("dm_target_flakey: unloaded\n");
428 		break;
429 	}
430 
431 	return err;
432 }
433 
434 DM_TARGET_MODULE(dm_target_flakey, dmtf_mod_handler);
435