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