|  | /* | 
|  | * Block protocol for I/O error injection | 
|  | * | 
|  | * Copyright (c) 2010 Kevin Wolf <kwolf@redhat.com> | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | * of this software and associated documentation files (the "Software"), to deal | 
|  | * in the Software without restriction, including without limitation the rights | 
|  | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | * copies of the Software, and to permit persons to whom the Software is | 
|  | * furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice shall be included in | 
|  | * all copies or substantial portions of the Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | 
|  | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | * THE SOFTWARE. | 
|  | */ | 
|  |  | 
|  | #include "qemu-common.h" | 
|  | #include "block_int.h" | 
|  | #include "module.h" | 
|  |  | 
|  | typedef struct BlkdebugVars { | 
|  | int state; | 
|  |  | 
|  | /* If inject_errno != 0, an error is injected for requests */ | 
|  | int inject_errno; | 
|  |  | 
|  | /* Decides if all future requests fail (false) or only the next one and | 
|  | * after the next request inject_errno is reset to 0 (true) */ | 
|  | bool inject_once; | 
|  |  | 
|  | /* Decides if aio_readv/writev fails right away (true) or returns an error | 
|  | * return value only in the callback (false) */ | 
|  | bool inject_immediately; | 
|  | } BlkdebugVars; | 
|  |  | 
|  | typedef struct BDRVBlkdebugState { | 
|  | BlkdebugVars vars; | 
|  | QLIST_HEAD(list, BlkdebugRule) rules[BLKDBG_EVENT_MAX]; | 
|  | } BDRVBlkdebugState; | 
|  |  | 
|  | typedef struct BlkdebugAIOCB { | 
|  | BlockDriverAIOCB common; | 
|  | QEMUBH *bh; | 
|  | int ret; | 
|  | } BlkdebugAIOCB; | 
|  |  | 
|  | static void blkdebug_aio_cancel(BlockDriverAIOCB *blockacb); | 
|  |  | 
|  | static AIOPool blkdebug_aio_pool = { | 
|  | .aiocb_size = sizeof(BlkdebugAIOCB), | 
|  | .cancel     = blkdebug_aio_cancel, | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | ACTION_INJECT_ERROR, | 
|  | ACTION_SET_STATE, | 
|  | }; | 
|  |  | 
|  | typedef struct BlkdebugRule { | 
|  | BlkDebugEvent event; | 
|  | int action; | 
|  | int state; | 
|  | union { | 
|  | struct { | 
|  | int error; | 
|  | int immediately; | 
|  | int once; | 
|  | } inject; | 
|  | struct { | 
|  | int new_state; | 
|  | } set_state; | 
|  | } options; | 
|  | QLIST_ENTRY(BlkdebugRule) next; | 
|  | } BlkdebugRule; | 
|  |  | 
|  | static QemuOptsList inject_error_opts = { | 
|  | .name = "inject-error", | 
|  | .head = QTAILQ_HEAD_INITIALIZER(inject_error_opts.head), | 
|  | .desc = { | 
|  | { | 
|  | .name = "event", | 
|  | .type = QEMU_OPT_STRING, | 
|  | }, | 
|  | { | 
|  | .name = "state", | 
|  | .type = QEMU_OPT_NUMBER, | 
|  | }, | 
|  | { | 
|  | .name = "errno", | 
|  | .type = QEMU_OPT_NUMBER, | 
|  | }, | 
|  | { | 
|  | .name = "once", | 
|  | .type = QEMU_OPT_BOOL, | 
|  | }, | 
|  | { | 
|  | .name = "immediately", | 
|  | .type = QEMU_OPT_BOOL, | 
|  | }, | 
|  | { /* end of list */ } | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static QemuOptsList set_state_opts = { | 
|  | .name = "set-state", | 
|  | .head = QTAILQ_HEAD_INITIALIZER(set_state_opts.head), | 
|  | .desc = { | 
|  | { | 
|  | .name = "event", | 
|  | .type = QEMU_OPT_STRING, | 
|  | }, | 
|  | { | 
|  | .name = "state", | 
|  | .type = QEMU_OPT_NUMBER, | 
|  | }, | 
|  | { | 
|  | .name = "new_state", | 
|  | .type = QEMU_OPT_NUMBER, | 
|  | }, | 
|  | { /* end of list */ } | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static QemuOptsList *config_groups[] = { | 
|  | &inject_error_opts, | 
|  | &set_state_opts, | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | static const char *event_names[BLKDBG_EVENT_MAX] = { | 
|  | [BLKDBG_L1_UPDATE]                      = "l1_update", | 
|  | [BLKDBG_L1_GROW_ALLOC_TABLE]            = "l1_grow.alloc_table", | 
|  | [BLKDBG_L1_GROW_WRITE_TABLE]            = "l1_grow.write_table", | 
|  | [BLKDBG_L1_GROW_ACTIVATE_TABLE]         = "l1_grow.activate_table", | 
|  |  | 
|  | [BLKDBG_L2_LOAD]                        = "l2_load", | 
|  | [BLKDBG_L2_UPDATE]                      = "l2_update", | 
|  | [BLKDBG_L2_UPDATE_COMPRESSED]           = "l2_update_compressed", | 
|  | [BLKDBG_L2_ALLOC_COW_READ]              = "l2_alloc.cow_read", | 
|  | [BLKDBG_L2_ALLOC_WRITE]                 = "l2_alloc.write", | 
|  |  | 
|  | [BLKDBG_READ]                           = "read", | 
|  | [BLKDBG_READ_AIO]                       = "read_aio", | 
|  | [BLKDBG_READ_BACKING]                   = "read_backing", | 
|  | [BLKDBG_READ_BACKING_AIO]               = "read_backing_aio", | 
|  | [BLKDBG_READ_COMPRESSED]                = "read_compressed", | 
|  |  | 
|  | [BLKDBG_WRITE_AIO]                      = "write_aio", | 
|  | [BLKDBG_WRITE_COMPRESSED]               = "write_compressed", | 
|  |  | 
|  | [BLKDBG_VMSTATE_LOAD]                   = "vmstate_load", | 
|  | [BLKDBG_VMSTATE_SAVE]                   = "vmstate_save", | 
|  |  | 
|  | [BLKDBG_COW_READ]                       = "cow_read", | 
|  | [BLKDBG_COW_WRITE]                      = "cow_write", | 
|  |  | 
|  | [BLKDBG_REFTABLE_LOAD]                  = "reftable_load", | 
|  | [BLKDBG_REFTABLE_GROW]                  = "reftable_grow", | 
|  |  | 
|  | [BLKDBG_REFBLOCK_LOAD]                  = "refblock_load", | 
|  | [BLKDBG_REFBLOCK_UPDATE]                = "refblock_update", | 
|  | [BLKDBG_REFBLOCK_UPDATE_PART]           = "refblock_update_part", | 
|  | [BLKDBG_REFBLOCK_ALLOC]                 = "refblock_alloc", | 
|  | [BLKDBG_REFBLOCK_ALLOC_HOOKUP]          = "refblock_alloc.hookup", | 
|  | [BLKDBG_REFBLOCK_ALLOC_WRITE]           = "refblock_alloc.write", | 
|  | [BLKDBG_REFBLOCK_ALLOC_WRITE_BLOCKS]    = "refblock_alloc.write_blocks", | 
|  | [BLKDBG_REFBLOCK_ALLOC_WRITE_TABLE]     = "refblock_alloc.write_table", | 
|  | [BLKDBG_REFBLOCK_ALLOC_SWITCH_TABLE]    = "refblock_alloc.switch_table", | 
|  |  | 
|  | [BLKDBG_CLUSTER_ALLOC]                  = "cluster_alloc", | 
|  | [BLKDBG_CLUSTER_ALLOC_BYTES]            = "cluster_alloc_bytes", | 
|  | [BLKDBG_CLUSTER_FREE]                   = "cluster_free", | 
|  | }; | 
|  |  | 
|  | static int get_event_by_name(const char *name, BlkDebugEvent *event) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < BLKDBG_EVENT_MAX; i++) { | 
|  | if (!strcmp(event_names[i], name)) { | 
|  | *event = i; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | struct add_rule_data { | 
|  | BDRVBlkdebugState *s; | 
|  | int action; | 
|  | }; | 
|  |  | 
|  | static int add_rule(QemuOpts *opts, void *opaque) | 
|  | { | 
|  | struct add_rule_data *d = opaque; | 
|  | BDRVBlkdebugState *s = d->s; | 
|  | const char* event_name; | 
|  | BlkDebugEvent event; | 
|  | struct BlkdebugRule *rule; | 
|  |  | 
|  | /* Find the right event for the rule */ | 
|  | event_name = qemu_opt_get(opts, "event"); | 
|  | if (!event_name || get_event_by_name(event_name, &event) < 0) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* Set attributes common for all actions */ | 
|  | rule = qemu_mallocz(sizeof(*rule)); | 
|  | *rule = (struct BlkdebugRule) { | 
|  | .event  = event, | 
|  | .action = d->action, | 
|  | .state  = qemu_opt_get_number(opts, "state", 0), | 
|  | }; | 
|  |  | 
|  | /* Parse action-specific options */ | 
|  | switch (d->action) { | 
|  | case ACTION_INJECT_ERROR: | 
|  | rule->options.inject.error = qemu_opt_get_number(opts, "errno", EIO); | 
|  | rule->options.inject.once  = qemu_opt_get_bool(opts, "once", 0); | 
|  | rule->options.inject.immediately = | 
|  | qemu_opt_get_bool(opts, "immediately", 0); | 
|  | break; | 
|  |  | 
|  | case ACTION_SET_STATE: | 
|  | rule->options.set_state.new_state = | 
|  | qemu_opt_get_number(opts, "new_state", 0); | 
|  | break; | 
|  | }; | 
|  |  | 
|  | /* Add the rule */ | 
|  | QLIST_INSERT_HEAD(&s->rules[event], rule, next); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int read_config(BDRVBlkdebugState *s, const char *filename) | 
|  | { | 
|  | FILE *f; | 
|  | int ret; | 
|  | struct add_rule_data d; | 
|  |  | 
|  | f = fopen(filename, "r"); | 
|  | if (f == NULL) { | 
|  | return -errno; | 
|  | } | 
|  |  | 
|  | ret = qemu_config_parse(f, config_groups, filename); | 
|  | if (ret < 0) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | d.s = s; | 
|  | d.action = ACTION_INJECT_ERROR; | 
|  | qemu_opts_foreach(&inject_error_opts, add_rule, &d, 0); | 
|  |  | 
|  | d.action = ACTION_SET_STATE; | 
|  | qemu_opts_foreach(&set_state_opts, add_rule, &d, 0); | 
|  |  | 
|  | ret = 0; | 
|  | fail: | 
|  | qemu_opts_reset(&inject_error_opts); | 
|  | qemu_opts_reset(&set_state_opts); | 
|  | fclose(f); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Valid blkdebug filenames look like blkdebug:path/to/config:path/to/image */ | 
|  | static int blkdebug_open(BlockDriverState *bs, const char *filename, int flags) | 
|  | { | 
|  | BDRVBlkdebugState *s = bs->opaque; | 
|  | int ret; | 
|  | char *config, *c; | 
|  |  | 
|  | /* Parse the blkdebug: prefix */ | 
|  | if (strncmp(filename, "blkdebug:", strlen("blkdebug:"))) { | 
|  | return -EINVAL; | 
|  | } | 
|  | filename += strlen("blkdebug:"); | 
|  |  | 
|  | /* Read rules from config file */ | 
|  | c = strchr(filename, ':'); | 
|  | if (c == NULL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | config = strdup(filename); | 
|  | config[c - filename] = '\0'; | 
|  | ret = read_config(s, config); | 
|  | free(config); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | filename = c + 1; | 
|  |  | 
|  | /* Set initial state */ | 
|  | s->vars.state = 1; | 
|  |  | 
|  | /* Open the backing file */ | 
|  | ret = bdrv_file_open(&bs->file, filename, flags); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void error_callback_bh(void *opaque) | 
|  | { | 
|  | struct BlkdebugAIOCB *acb = opaque; | 
|  | qemu_bh_delete(acb->bh); | 
|  | acb->common.cb(acb->common.opaque, acb->ret); | 
|  | qemu_aio_release(acb); | 
|  | } | 
|  |  | 
|  | static void blkdebug_aio_cancel(BlockDriverAIOCB *blockacb) | 
|  | { | 
|  | BlkdebugAIOCB *acb = container_of(blockacb, BlkdebugAIOCB, common); | 
|  | qemu_aio_release(acb); | 
|  | } | 
|  |  | 
|  | static BlockDriverAIOCB *inject_error(BlockDriverState *bs, | 
|  | BlockDriverCompletionFunc *cb, void *opaque) | 
|  | { | 
|  | BDRVBlkdebugState *s = bs->opaque; | 
|  | int error = s->vars.inject_errno; | 
|  | struct BlkdebugAIOCB *acb; | 
|  | QEMUBH *bh; | 
|  |  | 
|  | if (s->vars.inject_once) { | 
|  | s->vars.inject_errno = 0; | 
|  | } | 
|  |  | 
|  | if (s->vars.inject_immediately) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | acb = qemu_aio_get(&blkdebug_aio_pool, bs, cb, opaque); | 
|  | acb->ret = -error; | 
|  |  | 
|  | bh = qemu_bh_new(error_callback_bh, acb); | 
|  | acb->bh = bh; | 
|  | qemu_bh_schedule(bh); | 
|  |  | 
|  | return &acb->common; | 
|  | } | 
|  |  | 
|  | static BlockDriverAIOCB *blkdebug_aio_readv(BlockDriverState *bs, | 
|  | int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, | 
|  | BlockDriverCompletionFunc *cb, void *opaque) | 
|  | { | 
|  | BDRVBlkdebugState *s = bs->opaque; | 
|  |  | 
|  | if (s->vars.inject_errno) { | 
|  | return inject_error(bs, cb, opaque); | 
|  | } | 
|  |  | 
|  | BlockDriverAIOCB *acb = | 
|  | bdrv_aio_readv(bs->file, sector_num, qiov, nb_sectors, cb, opaque); | 
|  | return acb; | 
|  | } | 
|  |  | 
|  | static BlockDriverAIOCB *blkdebug_aio_writev(BlockDriverState *bs, | 
|  | int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, | 
|  | BlockDriverCompletionFunc *cb, void *opaque) | 
|  | { | 
|  | BDRVBlkdebugState *s = bs->opaque; | 
|  |  | 
|  | if (s->vars.inject_errno) { | 
|  | return inject_error(bs, cb, opaque); | 
|  | } | 
|  |  | 
|  | BlockDriverAIOCB *acb = | 
|  | bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors, cb, opaque); | 
|  | return acb; | 
|  | } | 
|  |  | 
|  | static void blkdebug_close(BlockDriverState *bs) | 
|  | { | 
|  | BDRVBlkdebugState *s = bs->opaque; | 
|  | BlkdebugRule *rule, *next; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < BLKDBG_EVENT_MAX; i++) { | 
|  | QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) { | 
|  | QLIST_REMOVE(rule, next); | 
|  | qemu_free(rule); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static int blkdebug_flush(BlockDriverState *bs) | 
|  | { | 
|  | return bdrv_flush(bs->file); | 
|  | } | 
|  |  | 
|  | static BlockDriverAIOCB *blkdebug_aio_flush(BlockDriverState *bs, | 
|  | BlockDriverCompletionFunc *cb, void *opaque) | 
|  | { | 
|  | return bdrv_aio_flush(bs->file, cb, opaque); | 
|  | } | 
|  |  | 
|  | static void process_rule(BlockDriverState *bs, struct BlkdebugRule *rule, | 
|  | BlkdebugVars *old_vars) | 
|  | { | 
|  | BDRVBlkdebugState *s = bs->opaque; | 
|  | BlkdebugVars *vars = &s->vars; | 
|  |  | 
|  | /* Only process rules for the current state */ | 
|  | if (rule->state && rule->state != old_vars->state) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Take the action */ | 
|  | switch (rule->action) { | 
|  | case ACTION_INJECT_ERROR: | 
|  | vars->inject_errno       = rule->options.inject.error; | 
|  | vars->inject_once        = rule->options.inject.once; | 
|  | vars->inject_immediately = rule->options.inject.immediately; | 
|  | break; | 
|  |  | 
|  | case ACTION_SET_STATE: | 
|  | vars->state              = rule->options.set_state.new_state; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void blkdebug_debug_event(BlockDriverState *bs, BlkDebugEvent event) | 
|  | { | 
|  | BDRVBlkdebugState *s = bs->opaque; | 
|  | struct BlkdebugRule *rule; | 
|  | BlkdebugVars old_vars = s->vars; | 
|  |  | 
|  | assert((int)event >= 0 && event < BLKDBG_EVENT_MAX); | 
|  |  | 
|  | QLIST_FOREACH(rule, &s->rules[event], next) { | 
|  | process_rule(bs, rule, &old_vars); | 
|  | } | 
|  | } | 
|  |  | 
|  | static BlockDriver bdrv_blkdebug = { | 
|  | .format_name        = "blkdebug", | 
|  | .protocol_name      = "blkdebug", | 
|  |  | 
|  | .instance_size      = sizeof(BDRVBlkdebugState), | 
|  |  | 
|  | .bdrv_file_open     = blkdebug_open, | 
|  | .bdrv_close         = blkdebug_close, | 
|  | .bdrv_flush         = blkdebug_flush, | 
|  |  | 
|  | .bdrv_aio_readv     = blkdebug_aio_readv, | 
|  | .bdrv_aio_writev    = blkdebug_aio_writev, | 
|  | .bdrv_aio_flush     = blkdebug_aio_flush, | 
|  |  | 
|  | .bdrv_debug_event   = blkdebug_debug_event, | 
|  | }; | 
|  |  | 
|  | static void bdrv_blkdebug_init(void) | 
|  | { | 
|  | bdrv_register(&bdrv_blkdebug); | 
|  | } | 
|  |  | 
|  | block_init(bdrv_blkdebug_init); |