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