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 <tkusumi@netbsd.org>
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
dm_target_flakey_init(dm_table_entry_t * table_en,int argc,char ** argv)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
_init_features(dm_target_flakey_config_t * tfc,int argc,char ** argv)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
dm_target_flakey_destroy(dm_table_entry_t * table_en)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
dm_target_flakey_strategy(dm_table_entry_t * table_en,struct buf * bp)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
_submit(dm_target_flakey_config_t * tfc,struct bio * bio)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
_flakey_eio_buf(struct buf * bp)260 _flakey_eio_buf(struct buf *bp)
261 {
262 bp->b_error = EIO;
263 bp->b_resid = 0;
264 }
265
266 static void
_flakey_read_iodone(struct bio * bio)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
_flakey_read(dm_target_flakey_config_t * tfc,struct buf * bp)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
_flakey_write(dm_target_flakey_config_t * tfc,struct buf * bp)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
_flakey_corrupt_buf(dm_target_flakey_config_t * tfc,struct bio * bio)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 *
dm_target_flakey_table(void * target_config)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
dmtf_mod_handler(module_t mod,int type,void * unused)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