mirror of https://github.com/xemu-project/xemu.git
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);
|
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 */
|
/* 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,
|
void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||||
int nr_sectors);
|
int nr_sectors);
|
||||||
int64_t bdrv_get_dirty_count(BlockDriverState *bs);
|
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
|
#endif
|
||||||
|
|
250
block/blkdebug.c
250
block/blkdebug.c
|
@ -46,6 +46,7 @@ typedef struct BlkdebugVars {
|
||||||
typedef struct BDRVBlkdebugState {
|
typedef struct BDRVBlkdebugState {
|
||||||
BlockDriverState *hd;
|
BlockDriverState *hd;
|
||||||
BlkdebugVars vars;
|
BlkdebugVars vars;
|
||||||
|
QLIST_HEAD(list, BlkdebugRule) rules[BLKDBG_EVENT_MAX];
|
||||||
} BDRVBlkdebugState;
|
} BDRVBlkdebugState;
|
||||||
|
|
||||||
typedef struct BlkdebugAIOCB {
|
typedef struct BlkdebugAIOCB {
|
||||||
|
@ -61,16 +62,211 @@ static AIOPool blkdebug_aio_pool = {
|
||||||
.cancel = blkdebug_aio_cancel,
|
.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)
|
static int blkdebug_open(BlockDriverState *bs, const char *filename, int flags)
|
||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
|
int ret;
|
||||||
|
char *config, *c;
|
||||||
|
|
||||||
|
/* Parse the blkdebug: prefix */
|
||||||
if (strncmp(filename, "blkdebug:", strlen("blkdebug:"))) {
|
if (strncmp(filename, "blkdebug:", strlen("blkdebug:"))) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
filename += strlen("blkdebug:");
|
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)
|
static void error_callback_bh(void *opaque)
|
||||||
|
@ -146,6 +342,16 @@ static BlockDriverAIOCB *blkdebug_aio_writev(BlockDriverState *bs,
|
||||||
static void blkdebug_close(BlockDriverState *bs)
|
static void blkdebug_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
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);
|
bdrv_delete(s->hd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +368,46 @@ static BlockDriverAIOCB *blkdebug_aio_flush(BlockDriverState *bs,
|
||||||
return bdrv_aio_flush(s->hd, cb, opaque);
|
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 = {
|
static BlockDriver bdrv_blkdebug = {
|
||||||
.format_name = "blkdebug",
|
.format_name = "blkdebug",
|
||||||
.protocol_name = "blkdebug",
|
.protocol_name = "blkdebug",
|
||||||
|
@ -175,6 +421,8 @@ static BlockDriver bdrv_blkdebug = {
|
||||||
.bdrv_aio_readv = blkdebug_aio_readv,
|
.bdrv_aio_readv = blkdebug_aio_readv,
|
||||||
.bdrv_aio_writev = blkdebug_aio_writev,
|
.bdrv_aio_writev = blkdebug_aio_writev,
|
||||||
.bdrv_aio_flush = blkdebug_aio_flush,
|
.bdrv_aio_flush = blkdebug_aio_flush,
|
||||||
|
|
||||||
|
.bdrv_debug_event = blkdebug_debug_event,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_blkdebug_init(void)
|
static void bdrv_blkdebug_init(void)
|
||||||
|
|
|
@ -120,6 +120,8 @@ struct BlockDriver {
|
||||||
/* Returns number of errors in image, -errno for internal errors */
|
/* Returns number of errors in image, -errno for internal errors */
|
||||||
int (*bdrv_check)(BlockDriverState* bs);
|
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 */
|
/* Set if newly created images are not guaranteed to contain only zeros */
|
||||||
int no_zero_init;
|
int no_zero_init;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue