blkdebug: Add events and rules
Block drivers can trigger a blkdebug event whenever they reach a place where it could be useful to inject an error for testing/debugging purposes. Rules are read from a blkdebug config file and describe which action is taken when an event is triggered. For now this is only injecting an error (with a few options) or changing the state (which is an integer). Rules can be declared to be active only in a specific state; this way later rules can distiguish on which path we came to trigger their event. Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
parent
25920d6ad6
commit
8b9b0cc2fd
12
block.c
12
block.c
@ -1535,6 +1535,18 @@ int bdrv_load_vmstate(BlockDriverState *bs, uint8_t *buf,
|
||||
return drv->bdrv_load_vmstate(bs, buf, pos, size);
|
||||
}
|
||||
|
||||
void bdrv_debug_event(BlockDriverState *bs, BlkDebugEvent event)
|
||||
{
|
||||
BlockDriver *drv = bs->drv;
|
||||
|
||||
if (!drv || !drv->bdrv_debug_event) {
|
||||
return;
|
||||
}
|
||||
|
||||
return drv->bdrv_debug_event(bs, event);
|
||||
|
||||
}
|
||||
|
||||
/**************************************************************/
|
||||
/* handling of snapshots */
|
||||
|
||||
|
9
block.h
9
block.h
@ -207,4 +207,13 @@ int bdrv_get_dirty(BlockDriverState *bs, int64_t sector);
|
||||
void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||
int nr_sectors);
|
||||
int64_t bdrv_get_dirty_count(BlockDriverState *bs);
|
||||
|
||||
|
||||
typedef enum {
|
||||
BLKDBG_EVENT_MAX,
|
||||
} BlkDebugEvent;
|
||||
|
||||
#define BLKDBG_EVENT(bs, evt) bdrv_debug_event(bs, evt)
|
||||
void bdrv_debug_event(BlockDriverState *bs, BlkDebugEvent event);
|
||||
|
||||
#endif
|
||||
|
250
block/blkdebug.c
250
block/blkdebug.c
@ -46,6 +46,7 @@ typedef struct BlkdebugVars {
|
||||
typedef struct BDRVBlkdebugState {
|
||||
BlockDriverState *hd;
|
||||
BlkdebugVars vars;
|
||||
QLIST_HEAD(list, BlkdebugRule) rules[BLKDBG_EVENT_MAX];
|
||||
} BDRVBlkdebugState;
|
||||
|
||||
typedef struct BlkdebugAIOCB {
|
||||
@ -61,16 +62,211 @@ static AIOPool blkdebug_aio_pool = {
|
||||
.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(inject_error_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] = {
|
||||
};
|
||||
|
||||
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:
|
||||
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:");
|
||||
|
||||
return bdrv_file_open(&s->hd, filename, flags);
|
||||
/* 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;
|
||||
|
||||
/* Open the backing file */
|
||||
ret = bdrv_file_open(&s->hd, filename, flags);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void error_callback_bh(void *opaque)
|
||||
@ -146,6 +342,16 @@ static BlockDriverAIOCB *blkdebug_aio_writev(BlockDriverState *bs,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bdrv_delete(s->hd);
|
||||
}
|
||||
|
||||
@ -162,6 +368,46 @@ static BlockDriverAIOCB *blkdebug_aio_flush(BlockDriverState *bs,
|
||||
return bdrv_aio_flush(s->hd, 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;
|
||||
|
||||
if (event < 0 || event >= BLKDBG_EVENT_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
QLIST_FOREACH(rule, &s->rules[event], next) {
|
||||
process_rule(bs, rule, &old_vars);
|
||||
}
|
||||
}
|
||||
|
||||
static BlockDriver bdrv_blkdebug = {
|
||||
.format_name = "blkdebug",
|
||||
.protocol_name = "blkdebug",
|
||||
@ -175,6 +421,8 @@ static BlockDriver bdrv_blkdebug = {
|
||||
.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)
|
||||
|
@ -120,6 +120,8 @@ struct BlockDriver {
|
||||
/* Returns number of errors in image, -errno for internal errors */
|
||||
int (*bdrv_check)(BlockDriverState* bs);
|
||||
|
||||
void (*bdrv_debug_event)(BlockDriverState *bs, BlkDebugEvent event);
|
||||
|
||||
/* Set if newly created images are not guaranteed to contain only zeros */
|
||||
int no_zero_init;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user