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