xref: /qemu/block/blkdebug.c (revision 43b48cfc)
1 /*
2  * Block protocol for I/O error injection
3  *
4  * Copyright (c) 2010 Kevin Wolf <kwolf@redhat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
25 #include "qemu/osdep.h"
26 #include "qemu-common.h"
27 #include "qemu/config-file.h"
28 #include "block/block_int.h"
29 #include "qemu/module.h"
30 #include "qapi/qmp/qbool.h"
31 #include "qapi/qmp/qdict.h"
32 #include "qapi/qmp/qint.h"
33 #include "qapi/qmp/qstring.h"
34 #include "sysemu/qtest.h"
35 
36 typedef struct BDRVBlkdebugState {
37     int state;
38     int new_state;
39 
40     QLIST_HEAD(, BlkdebugRule) rules[BLKDBG__MAX];
41     QSIMPLEQ_HEAD(, BlkdebugRule) active_rules;
42     QLIST_HEAD(, BlkdebugSuspendedReq) suspended_reqs;
43 } BDRVBlkdebugState;
44 
45 typedef struct BlkdebugAIOCB {
46     BlockAIOCB common;
47     QEMUBH *bh;
48     int ret;
49 } BlkdebugAIOCB;
50 
51 typedef struct BlkdebugSuspendedReq {
52     Coroutine *co;
53     char *tag;
54     QLIST_ENTRY(BlkdebugSuspendedReq) next;
55 } BlkdebugSuspendedReq;
56 
57 static const AIOCBInfo blkdebug_aiocb_info = {
58     .aiocb_size    = sizeof(BlkdebugAIOCB),
59 };
60 
61 enum {
62     ACTION_INJECT_ERROR,
63     ACTION_SET_STATE,
64     ACTION_SUSPEND,
65 };
66 
67 typedef struct BlkdebugRule {
68     BlkdebugEvent event;
69     int action;
70     int state;
71     union {
72         struct {
73             int error;
74             int immediately;
75             int once;
76             int64_t sector;
77         } inject;
78         struct {
79             int new_state;
80         } set_state;
81         struct {
82             char *tag;
83         } suspend;
84     } options;
85     QLIST_ENTRY(BlkdebugRule) next;
86     QSIMPLEQ_ENTRY(BlkdebugRule) active_next;
87 } BlkdebugRule;
88 
89 static QemuOptsList inject_error_opts = {
90     .name = "inject-error",
91     .head = QTAILQ_HEAD_INITIALIZER(inject_error_opts.head),
92     .desc = {
93         {
94             .name = "event",
95             .type = QEMU_OPT_STRING,
96         },
97         {
98             .name = "state",
99             .type = QEMU_OPT_NUMBER,
100         },
101         {
102             .name = "errno",
103             .type = QEMU_OPT_NUMBER,
104         },
105         {
106             .name = "sector",
107             .type = QEMU_OPT_NUMBER,
108         },
109         {
110             .name = "once",
111             .type = QEMU_OPT_BOOL,
112         },
113         {
114             .name = "immediately",
115             .type = QEMU_OPT_BOOL,
116         },
117         { /* end of list */ }
118     },
119 };
120 
121 static QemuOptsList set_state_opts = {
122     .name = "set-state",
123     .head = QTAILQ_HEAD_INITIALIZER(set_state_opts.head),
124     .desc = {
125         {
126             .name = "event",
127             .type = QEMU_OPT_STRING,
128         },
129         {
130             .name = "state",
131             .type = QEMU_OPT_NUMBER,
132         },
133         {
134             .name = "new_state",
135             .type = QEMU_OPT_NUMBER,
136         },
137         { /* end of list */ }
138     },
139 };
140 
141 static QemuOptsList *config_groups[] = {
142     &inject_error_opts,
143     &set_state_opts,
144     NULL
145 };
146 
147 static int get_event_by_name(const char *name, BlkdebugEvent *event)
148 {
149     int i;
150 
151     for (i = 0; i < BLKDBG__MAX; i++) {
152         if (!strcmp(BlkdebugEvent_lookup[i], name)) {
153             *event = i;
154             return 0;
155         }
156     }
157 
158     return -1;
159 }
160 
161 struct add_rule_data {
162     BDRVBlkdebugState *s;
163     int action;
164 };
165 
166 static int add_rule(void *opaque, QemuOpts *opts, Error **errp)
167 {
168     struct add_rule_data *d = opaque;
169     BDRVBlkdebugState *s = d->s;
170     const char* event_name;
171     BlkdebugEvent event;
172     struct BlkdebugRule *rule;
173 
174     /* Find the right event for the rule */
175     event_name = qemu_opt_get(opts, "event");
176     if (!event_name) {
177         error_setg(errp, "Missing event name for rule");
178         return -1;
179     } else if (get_event_by_name(event_name, &event) < 0) {
180         error_setg(errp, "Invalid event name \"%s\"", event_name);
181         return -1;
182     }
183 
184     /* Set attributes common for all actions */
185     rule = g_malloc0(sizeof(*rule));
186     *rule = (struct BlkdebugRule) {
187         .event  = event,
188         .action = d->action,
189         .state  = qemu_opt_get_number(opts, "state", 0),
190     };
191 
192     /* Parse action-specific options */
193     switch (d->action) {
194     case ACTION_INJECT_ERROR:
195         rule->options.inject.error = qemu_opt_get_number(opts, "errno", EIO);
196         rule->options.inject.once  = qemu_opt_get_bool(opts, "once", 0);
197         rule->options.inject.immediately =
198             qemu_opt_get_bool(opts, "immediately", 0);
199         rule->options.inject.sector = qemu_opt_get_number(opts, "sector", -1);
200         break;
201 
202     case ACTION_SET_STATE:
203         rule->options.set_state.new_state =
204             qemu_opt_get_number(opts, "new_state", 0);
205         break;
206 
207     case ACTION_SUSPEND:
208         rule->options.suspend.tag =
209             g_strdup(qemu_opt_get(opts, "tag"));
210         break;
211     };
212 
213     /* Add the rule */
214     QLIST_INSERT_HEAD(&s->rules[event], rule, next);
215 
216     return 0;
217 }
218 
219 static void remove_rule(BlkdebugRule *rule)
220 {
221     switch (rule->action) {
222     case ACTION_INJECT_ERROR:
223     case ACTION_SET_STATE:
224         break;
225     case ACTION_SUSPEND:
226         g_free(rule->options.suspend.tag);
227         break;
228     }
229 
230     QLIST_REMOVE(rule, next);
231     g_free(rule);
232 }
233 
234 static int read_config(BDRVBlkdebugState *s, const char *filename,
235                        QDict *options, Error **errp)
236 {
237     FILE *f = NULL;
238     int ret;
239     struct add_rule_data d;
240     Error *local_err = NULL;
241 
242     if (filename) {
243         f = fopen(filename, "r");
244         if (f == NULL) {
245             error_setg_errno(errp, errno, "Could not read blkdebug config file");
246             return -errno;
247         }
248 
249         ret = qemu_config_parse(f, config_groups, filename);
250         if (ret < 0) {
251             error_setg(errp, "Could not parse blkdebug config file");
252             ret = -EINVAL;
253             goto fail;
254         }
255     }
256 
257     qemu_config_parse_qdict(options, config_groups, &local_err);
258     if (local_err) {
259         error_propagate(errp, local_err);
260         ret = -EINVAL;
261         goto fail;
262     }
263 
264     d.s = s;
265     d.action = ACTION_INJECT_ERROR;
266     qemu_opts_foreach(&inject_error_opts, add_rule, &d, &local_err);
267     if (local_err) {
268         error_propagate(errp, local_err);
269         ret = -EINVAL;
270         goto fail;
271     }
272 
273     d.action = ACTION_SET_STATE;
274     qemu_opts_foreach(&set_state_opts, add_rule, &d, &local_err);
275     if (local_err) {
276         error_propagate(errp, local_err);
277         ret = -EINVAL;
278         goto fail;
279     }
280 
281     ret = 0;
282 fail:
283     qemu_opts_reset(&inject_error_opts);
284     qemu_opts_reset(&set_state_opts);
285     if (f) {
286         fclose(f);
287     }
288     return ret;
289 }
290 
291 /* Valid blkdebug filenames look like blkdebug:path/to/config:path/to/image */
292 static void blkdebug_parse_filename(const char *filename, QDict *options,
293                                     Error **errp)
294 {
295     const char *c;
296 
297     /* Parse the blkdebug: prefix */
298     if (!strstart(filename, "blkdebug:", &filename)) {
299         /* There was no prefix; therefore, all options have to be already
300            present in the QDict (except for the filename) */
301         qdict_put(options, "x-image", qstring_from_str(filename));
302         return;
303     }
304 
305     /* Parse config file path */
306     c = strchr(filename, ':');
307     if (c == NULL) {
308         error_setg(errp, "blkdebug requires both config file and image path");
309         return;
310     }
311 
312     if (c != filename) {
313         QString *config_path;
314         config_path = qstring_from_substr(filename, 0, c - filename - 1);
315         qdict_put(options, "config", config_path);
316     }
317 
318     /* TODO Allow multi-level nesting and set file.filename here */
319     filename = c + 1;
320     qdict_put(options, "x-image", qstring_from_str(filename));
321 }
322 
323 static QemuOptsList runtime_opts = {
324     .name = "blkdebug",
325     .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
326     .desc = {
327         {
328             .name = "config",
329             .type = QEMU_OPT_STRING,
330             .help = "Path to the configuration file",
331         },
332         {
333             .name = "x-image",
334             .type = QEMU_OPT_STRING,
335             .help = "[internal use only, will be removed]",
336         },
337         {
338             .name = "align",
339             .type = QEMU_OPT_SIZE,
340             .help = "Required alignment in bytes",
341         },
342         { /* end of list */ }
343     },
344 };
345 
346 static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
347                          Error **errp)
348 {
349     BDRVBlkdebugState *s = bs->opaque;
350     QemuOpts *opts;
351     Error *local_err = NULL;
352     const char *config;
353     uint64_t align;
354     int ret;
355 
356     opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
357     qemu_opts_absorb_qdict(opts, options, &local_err);
358     if (local_err) {
359         error_propagate(errp, local_err);
360         ret = -EINVAL;
361         goto out;
362     }
363 
364     /* Read rules from config file or command line options */
365     config = qemu_opt_get(opts, "config");
366     ret = read_config(s, config, options, errp);
367     if (ret) {
368         goto out;
369     }
370 
371     /* Set initial state */
372     s->state = 1;
373 
374     /* Open the image file */
375     bs->file = bdrv_open_child(qemu_opt_get(opts, "x-image"), options, "image",
376                                bs, &child_file, false, &local_err);
377     if (local_err) {
378         ret = -EINVAL;
379         error_propagate(errp, local_err);
380         goto out;
381     }
382 
383     /* Set request alignment */
384     align = qemu_opt_get_size(opts, "align", bs->request_alignment);
385     if (align > 0 && align < INT_MAX && !(align & (align - 1))) {
386         bs->request_alignment = align;
387     } else {
388         error_setg(errp, "Invalid alignment");
389         ret = -EINVAL;
390         goto fail_unref;
391     }
392 
393     ret = 0;
394     goto out;
395 
396 fail_unref:
397     bdrv_unref_child(bs, bs->file);
398 out:
399     qemu_opts_del(opts);
400     return ret;
401 }
402 
403 static void error_callback_bh(void *opaque)
404 {
405     struct BlkdebugAIOCB *acb = opaque;
406     qemu_bh_delete(acb->bh);
407     acb->common.cb(acb->common.opaque, acb->ret);
408     qemu_aio_unref(acb);
409 }
410 
411 static BlockAIOCB *inject_error(BlockDriverState *bs,
412     BlockCompletionFunc *cb, void *opaque, BlkdebugRule *rule)
413 {
414     BDRVBlkdebugState *s = bs->opaque;
415     int error = rule->options.inject.error;
416     struct BlkdebugAIOCB *acb;
417     QEMUBH *bh;
418     bool immediately = rule->options.inject.immediately;
419 
420     if (rule->options.inject.once) {
421         QSIMPLEQ_REMOVE(&s->active_rules, rule, BlkdebugRule, active_next);
422         remove_rule(rule);
423     }
424 
425     if (immediately) {
426         return NULL;
427     }
428 
429     acb = qemu_aio_get(&blkdebug_aiocb_info, bs, cb, opaque);
430     acb->ret = -error;
431 
432     bh = aio_bh_new(bdrv_get_aio_context(bs), error_callback_bh, acb);
433     acb->bh = bh;
434     qemu_bh_schedule(bh);
435 
436     return &acb->common;
437 }
438 
439 static BlockAIOCB *blkdebug_aio_readv(BlockDriverState *bs,
440     int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
441     BlockCompletionFunc *cb, void *opaque)
442 {
443     BDRVBlkdebugState *s = bs->opaque;
444     BlkdebugRule *rule = NULL;
445 
446     QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
447         if (rule->options.inject.sector == -1 ||
448             (rule->options.inject.sector >= sector_num &&
449              rule->options.inject.sector < sector_num + nb_sectors)) {
450             break;
451         }
452     }
453 
454     if (rule && rule->options.inject.error) {
455         return inject_error(bs, cb, opaque, rule);
456     }
457 
458     return bdrv_aio_readv(bs->file->bs, sector_num, qiov, nb_sectors,
459                           cb, opaque);
460 }
461 
462 static BlockAIOCB *blkdebug_aio_writev(BlockDriverState *bs,
463     int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
464     BlockCompletionFunc *cb, void *opaque)
465 {
466     BDRVBlkdebugState *s = bs->opaque;
467     BlkdebugRule *rule = NULL;
468 
469     QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
470         if (rule->options.inject.sector == -1 ||
471             (rule->options.inject.sector >= sector_num &&
472              rule->options.inject.sector < sector_num + nb_sectors)) {
473             break;
474         }
475     }
476 
477     if (rule && rule->options.inject.error) {
478         return inject_error(bs, cb, opaque, rule);
479     }
480 
481     return bdrv_aio_writev(bs->file->bs, sector_num, qiov, nb_sectors,
482                            cb, opaque);
483 }
484 
485 static BlockAIOCB *blkdebug_aio_flush(BlockDriverState *bs,
486     BlockCompletionFunc *cb, void *opaque)
487 {
488     BDRVBlkdebugState *s = bs->opaque;
489     BlkdebugRule *rule = NULL;
490 
491     QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
492         if (rule->options.inject.sector == -1) {
493             break;
494         }
495     }
496 
497     if (rule && rule->options.inject.error) {
498         return inject_error(bs, cb, opaque, rule);
499     }
500 
501     return bdrv_aio_flush(bs->file->bs, cb, opaque);
502 }
503 
504 
505 static void blkdebug_close(BlockDriverState *bs)
506 {
507     BDRVBlkdebugState *s = bs->opaque;
508     BlkdebugRule *rule, *next;
509     int i;
510 
511     for (i = 0; i < BLKDBG__MAX; i++) {
512         QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) {
513             remove_rule(rule);
514         }
515     }
516 }
517 
518 static void suspend_request(BlockDriverState *bs, BlkdebugRule *rule)
519 {
520     BDRVBlkdebugState *s = bs->opaque;
521     BlkdebugSuspendedReq r;
522 
523     r = (BlkdebugSuspendedReq) {
524         .co         = qemu_coroutine_self(),
525         .tag        = g_strdup(rule->options.suspend.tag),
526     };
527 
528     remove_rule(rule);
529     QLIST_INSERT_HEAD(&s->suspended_reqs, &r, next);
530 
531     if (!qtest_enabled()) {
532         printf("blkdebug: Suspended request '%s'\n", r.tag);
533     }
534     qemu_coroutine_yield();
535     if (!qtest_enabled()) {
536         printf("blkdebug: Resuming request '%s'\n", r.tag);
537     }
538 
539     QLIST_REMOVE(&r, next);
540     g_free(r.tag);
541 }
542 
543 static bool process_rule(BlockDriverState *bs, struct BlkdebugRule *rule,
544     bool injected)
545 {
546     BDRVBlkdebugState *s = bs->opaque;
547 
548     /* Only process rules for the current state */
549     if (rule->state && rule->state != s->state) {
550         return injected;
551     }
552 
553     /* Take the action */
554     switch (rule->action) {
555     case ACTION_INJECT_ERROR:
556         if (!injected) {
557             QSIMPLEQ_INIT(&s->active_rules);
558             injected = true;
559         }
560         QSIMPLEQ_INSERT_HEAD(&s->active_rules, rule, active_next);
561         break;
562 
563     case ACTION_SET_STATE:
564         s->new_state = rule->options.set_state.new_state;
565         break;
566 
567     case ACTION_SUSPEND:
568         suspend_request(bs, rule);
569         break;
570     }
571     return injected;
572 }
573 
574 static void blkdebug_debug_event(BlockDriverState *bs, BlkdebugEvent event)
575 {
576     BDRVBlkdebugState *s = bs->opaque;
577     struct BlkdebugRule *rule, *next;
578     bool injected;
579 
580     assert((int)event >= 0 && event < BLKDBG__MAX);
581 
582     injected = false;
583     s->new_state = s->state;
584     QLIST_FOREACH_SAFE(rule, &s->rules[event], next, next) {
585         injected = process_rule(bs, rule, injected);
586     }
587     s->state = s->new_state;
588 }
589 
590 static int blkdebug_debug_breakpoint(BlockDriverState *bs, const char *event,
591                                      const char *tag)
592 {
593     BDRVBlkdebugState *s = bs->opaque;
594     struct BlkdebugRule *rule;
595     BlkdebugEvent blkdebug_event;
596 
597     if (get_event_by_name(event, &blkdebug_event) < 0) {
598         return -ENOENT;
599     }
600 
601 
602     rule = g_malloc(sizeof(*rule));
603     *rule = (struct BlkdebugRule) {
604         .event  = blkdebug_event,
605         .action = ACTION_SUSPEND,
606         .state  = 0,
607         .options.suspend.tag = g_strdup(tag),
608     };
609 
610     QLIST_INSERT_HEAD(&s->rules[blkdebug_event], rule, next);
611 
612     return 0;
613 }
614 
615 static int blkdebug_debug_resume(BlockDriverState *bs, const char *tag)
616 {
617     BDRVBlkdebugState *s = bs->opaque;
618     BlkdebugSuspendedReq *r, *next;
619 
620     QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, next) {
621         if (!strcmp(r->tag, tag)) {
622             qemu_coroutine_enter(r->co, NULL);
623             return 0;
624         }
625     }
626     return -ENOENT;
627 }
628 
629 static int blkdebug_debug_remove_breakpoint(BlockDriverState *bs,
630                                             const char *tag)
631 {
632     BDRVBlkdebugState *s = bs->opaque;
633     BlkdebugSuspendedReq *r, *r_next;
634     BlkdebugRule *rule, *next;
635     int i, ret = -ENOENT;
636 
637     for (i = 0; i < BLKDBG__MAX; i++) {
638         QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) {
639             if (rule->action == ACTION_SUSPEND &&
640                 !strcmp(rule->options.suspend.tag, tag)) {
641                 remove_rule(rule);
642                 ret = 0;
643             }
644         }
645     }
646     QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, r_next) {
647         if (!strcmp(r->tag, tag)) {
648             qemu_coroutine_enter(r->co, NULL);
649             ret = 0;
650         }
651     }
652     return ret;
653 }
654 
655 static bool blkdebug_debug_is_suspended(BlockDriverState *bs, const char *tag)
656 {
657     BDRVBlkdebugState *s = bs->opaque;
658     BlkdebugSuspendedReq *r;
659 
660     QLIST_FOREACH(r, &s->suspended_reqs, next) {
661         if (!strcmp(r->tag, tag)) {
662             return true;
663         }
664     }
665     return false;
666 }
667 
668 static int64_t blkdebug_getlength(BlockDriverState *bs)
669 {
670     return bdrv_getlength(bs->file->bs);
671 }
672 
673 static int blkdebug_truncate(BlockDriverState *bs, int64_t offset)
674 {
675     return bdrv_truncate(bs->file->bs, offset);
676 }
677 
678 static void blkdebug_refresh_filename(BlockDriverState *bs, QDict *options)
679 {
680     QDict *opts;
681     const QDictEntry *e;
682     bool force_json = false;
683 
684     for (e = qdict_first(options); e; e = qdict_next(options, e)) {
685         if (strcmp(qdict_entry_key(e), "config") &&
686             strcmp(qdict_entry_key(e), "x-image"))
687         {
688             force_json = true;
689             break;
690         }
691     }
692 
693     if (force_json && !bs->file->bs->full_open_options) {
694         /* The config file cannot be recreated, so creating a plain filename
695          * is impossible */
696         return;
697     }
698 
699     if (!force_json && bs->file->bs->exact_filename[0]) {
700         snprintf(bs->exact_filename, sizeof(bs->exact_filename),
701                  "blkdebug:%s:%s",
702                  qdict_get_try_str(options, "config") ?: "",
703                  bs->file->bs->exact_filename);
704     }
705 
706     opts = qdict_new();
707     qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("blkdebug")));
708 
709     QINCREF(bs->file->bs->full_open_options);
710     qdict_put_obj(opts, "image", QOBJECT(bs->file->bs->full_open_options));
711 
712     for (e = qdict_first(options); e; e = qdict_next(options, e)) {
713         if (strcmp(qdict_entry_key(e), "x-image")) {
714             qobject_incref(qdict_entry_value(e));
715             qdict_put_obj(opts, qdict_entry_key(e), qdict_entry_value(e));
716         }
717     }
718 
719     bs->full_open_options = opts;
720 }
721 
722 static int blkdebug_reopen_prepare(BDRVReopenState *reopen_state,
723                                    BlockReopenQueue *queue, Error **errp)
724 {
725     return 0;
726 }
727 
728 static BlockDriver bdrv_blkdebug = {
729     .format_name            = "blkdebug",
730     .protocol_name          = "blkdebug",
731     .instance_size          = sizeof(BDRVBlkdebugState),
732 
733     .bdrv_parse_filename    = blkdebug_parse_filename,
734     .bdrv_file_open         = blkdebug_open,
735     .bdrv_close             = blkdebug_close,
736     .bdrv_reopen_prepare    = blkdebug_reopen_prepare,
737     .bdrv_getlength         = blkdebug_getlength,
738     .bdrv_truncate          = blkdebug_truncate,
739     .bdrv_refresh_filename  = blkdebug_refresh_filename,
740 
741     .bdrv_aio_readv         = blkdebug_aio_readv,
742     .bdrv_aio_writev        = blkdebug_aio_writev,
743     .bdrv_aio_flush         = blkdebug_aio_flush,
744 
745     .bdrv_debug_event           = blkdebug_debug_event,
746     .bdrv_debug_breakpoint      = blkdebug_debug_breakpoint,
747     .bdrv_debug_remove_breakpoint
748                                 = blkdebug_debug_remove_breakpoint,
749     .bdrv_debug_resume          = blkdebug_debug_resume,
750     .bdrv_debug_is_suspended    = blkdebug_debug_is_suspended,
751 };
752 
753 static void bdrv_blkdebug_init(void)
754 {
755     bdrv_register(&bdrv_blkdebug);
756 }
757 
758 block_init(bdrv_blkdebug_init);
759