sys/dev/disk/dm: Remove dm/targets/ directory and move its entries to dm/
[dragonfly.git] / sys / dev / disk / dm / flakey / dm_target_flakey.c
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, DM_FLAKEY_DEV, 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);