Merge remote-tracking branch 'kwolf/for-anthony' into staging
# By Kevin Wolf (16) and Ian Main (2) # Via Kevin Wolf * kwolf/for-anthony: Add tests for sync modes 'TOP' and 'NONE' Implement sync modes for drive-backup. Implement qdict_flatten() blockdev: Split up 'cache' option blockdev: Rename 'readonly' option to 'read-only' qcow2: Use dashes instead of underscores in options blockdev: Rename I/O throttling options for QMP QemuOpts: Add qemu_opt_unset() block: Allow "driver" option on the top level qapi: Anonymous unions qapi.py: Maintain a list of union types qapi: Add consume argument to qmp_input_get_object() qapi: Flat unions with arbitrary discriminator qapi: Add visitor for implicit structs docs: Document QAPI union types qapi-visit.py: Implement 'base' for unions qapi-visit.py: Split off generate_visit_struct_fields() qapi-types.py: Implement 'base' for unions Message-id: 1374870032-31672-1-git-send-email-kwolf@redhat.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
This commit is contained in:
commit
405c97c3a5
7
block.c
7
block.c
@ -970,6 +970,7 @@ int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options,
|
||||
char tmp_filename[PATH_MAX + 1];
|
||||
BlockDriverState *file = NULL;
|
||||
QDict *file_options = NULL;
|
||||
const char *drvname;
|
||||
|
||||
/* NULL means an empty set of options */
|
||||
if (options == NULL) {
|
||||
@ -1059,6 +1060,12 @@ int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options,
|
||||
}
|
||||
|
||||
/* Find the right image format driver */
|
||||
drvname = qdict_get_try_str(options, "driver");
|
||||
if (drvname) {
|
||||
drv = bdrv_find_whitelisted_format(drvname, !(flags & BDRV_O_RDWR));
|
||||
qdict_del(options, "driver");
|
||||
}
|
||||
|
||||
if (!drv) {
|
||||
ret = find_image_format(file, filename, &drv);
|
||||
}
|
||||
|
107
block/backup.c
107
block/backup.c
@ -37,6 +37,7 @@ typedef struct CowRequest {
|
||||
typedef struct BackupBlockJob {
|
||||
BlockJob common;
|
||||
BlockDriverState *target;
|
||||
MirrorSyncMode sync_mode;
|
||||
RateLimit limit;
|
||||
BlockdevOnError on_source_error;
|
||||
BlockdevOnError on_target_error;
|
||||
@ -247,40 +248,83 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
|
||||
bdrv_add_before_write_notifier(bs, &before_write);
|
||||
|
||||
for (; start < end; start++) {
|
||||
bool error_is_read;
|
||||
|
||||
if (block_job_is_cancelled(&job->common)) {
|
||||
break;
|
||||
if (job->sync_mode == MIRROR_SYNC_MODE_NONE) {
|
||||
while (!block_job_is_cancelled(&job->common)) {
|
||||
/* Yield until the job is cancelled. We just let our before_write
|
||||
* notify callback service CoW requests. */
|
||||
job->common.busy = false;
|
||||
qemu_coroutine_yield();
|
||||
job->common.busy = true;
|
||||
}
|
||||
} else {
|
||||
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
||||
for (; start < end; start++) {
|
||||
bool error_is_read;
|
||||
|
||||
/* we need to yield so that qemu_aio_flush() returns.
|
||||
* (without, VM does not reboot)
|
||||
*/
|
||||
if (job->common.speed) {
|
||||
uint64_t delay_ns = ratelimit_calculate_delay(
|
||||
&job->limit, job->sectors_read);
|
||||
job->sectors_read = 0;
|
||||
block_job_sleep_ns(&job->common, rt_clock, delay_ns);
|
||||
} else {
|
||||
block_job_sleep_ns(&job->common, rt_clock, 0);
|
||||
}
|
||||
|
||||
if (block_job_is_cancelled(&job->common)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = backup_do_cow(bs, start * BACKUP_SECTORS_PER_CLUSTER,
|
||||
BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
|
||||
if (ret < 0) {
|
||||
/* Depending on error action, fail now or retry cluster */
|
||||
BlockErrorAction action =
|
||||
backup_error_action(job, error_is_read, -ret);
|
||||
if (action == BDRV_ACTION_REPORT) {
|
||||
if (block_job_is_cancelled(&job->common)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* we need to yield so that qemu_aio_flush() returns.
|
||||
* (without, VM does not reboot)
|
||||
*/
|
||||
if (job->common.speed) {
|
||||
uint64_t delay_ns = ratelimit_calculate_delay(
|
||||
&job->limit, job->sectors_read);
|
||||
job->sectors_read = 0;
|
||||
block_job_sleep_ns(&job->common, rt_clock, delay_ns);
|
||||
} else {
|
||||
start--;
|
||||
continue;
|
||||
block_job_sleep_ns(&job->common, rt_clock, 0);
|
||||
}
|
||||
|
||||
if (block_job_is_cancelled(&job->common)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
|
||||
int i, n;
|
||||
int alloced = 0;
|
||||
|
||||
/* Check to see if these blocks are already in the
|
||||
* backing file. */
|
||||
|
||||
for (i = 0; i < BACKUP_SECTORS_PER_CLUSTER;) {
|
||||
/* bdrv_co_is_allocated() only returns true/false based
|
||||
* on the first set of sectors it comes accross that
|
||||
* are are all in the same state.
|
||||
* For that reason we must verify each sector in the
|
||||
* backup cluster length. We end up copying more than
|
||||
* needed but at some point that is always the case. */
|
||||
alloced =
|
||||
bdrv_co_is_allocated(bs,
|
||||
start * BACKUP_SECTORS_PER_CLUSTER + i,
|
||||
BACKUP_SECTORS_PER_CLUSTER - i, &n);
|
||||
i += n;
|
||||
|
||||
if (alloced == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the above loop never found any sectors that are in
|
||||
* the topmost image, skip this backup. */
|
||||
if (alloced == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* FULL sync mode we copy the whole drive. */
|
||||
ret = backup_do_cow(bs, start * BACKUP_SECTORS_PER_CLUSTER,
|
||||
BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
|
||||
if (ret < 0) {
|
||||
/* Depending on error action, fail now or retry cluster */
|
||||
BlockErrorAction action =
|
||||
backup_error_action(job, error_is_read, -ret);
|
||||
if (action == BDRV_ACTION_REPORT) {
|
||||
break;
|
||||
} else {
|
||||
start--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -300,7 +344,7 @@ static void coroutine_fn backup_run(void *opaque)
|
||||
}
|
||||
|
||||
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||
int64_t speed,
|
||||
int64_t speed, MirrorSyncMode sync_mode,
|
||||
BlockdevOnError on_source_error,
|
||||
BlockdevOnError on_target_error,
|
||||
BlockDriverCompletionFunc *cb, void *opaque,
|
||||
@ -335,6 +379,7 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||
job->on_source_error = on_source_error;
|
||||
job->on_target_error = on_target_error;
|
||||
job->target = target;
|
||||
job->sync_mode = sync_mode;
|
||||
job->common.len = len;
|
||||
job->common.co = qemu_coroutine_create(backup_run);
|
||||
qemu_coroutine_enter(job->common.co, job);
|
||||
|
@ -291,7 +291,7 @@ static QemuOptsList qcow2_runtime_opts = {
|
||||
.head = QTAILQ_HEAD_INITIALIZER(qcow2_runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "lazy_refcounts",
|
||||
.name = QCOW2_OPT_LAZY_REFCOUNTS,
|
||||
.type = QEMU_OPT_BOOL,
|
||||
.help = "Postpone refcount updates",
|
||||
},
|
||||
|
@ -59,10 +59,10 @@
|
||||
#define DEFAULT_CLUSTER_SIZE 65536
|
||||
|
||||
|
||||
#define QCOW2_OPT_LAZY_REFCOUNTS "lazy_refcounts"
|
||||
#define QCOW2_OPT_DISCARD_REQUEST "pass_discard_request"
|
||||
#define QCOW2_OPT_DISCARD_SNAPSHOT "pass_discard_snapshot"
|
||||
#define QCOW2_OPT_DISCARD_OTHER "pass_discard_other"
|
||||
#define QCOW2_OPT_LAZY_REFCOUNTS "lazy-refcounts"
|
||||
#define QCOW2_OPT_DISCARD_REQUEST "pass-discard-request"
|
||||
#define QCOW2_OPT_DISCARD_SNAPSHOT "pass-discard-snapshot"
|
||||
#define QCOW2_OPT_DISCARD_OTHER "pass-discard-other"
|
||||
|
||||
typedef struct QCowHeader {
|
||||
uint32_t magic;
|
||||
|
168
blockdev.c
168
blockdev.c
@ -312,7 +312,8 @@ static bool do_check_io_limits(BlockIOLimit *io_limits, Error **errp)
|
||||
return true;
|
||||
}
|
||||
|
||||
DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
static DriveInfo *blockdev_init(QemuOpts *all_opts,
|
||||
BlockInterfaceType block_default_type)
|
||||
{
|
||||
const char *buf;
|
||||
const char *file = NULL;
|
||||
@ -322,7 +323,6 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
enum { MEDIA_DISK, MEDIA_CDROM } media;
|
||||
int bus_id, unit_id;
|
||||
int cyls, heads, secs, translation;
|
||||
BlockDriver *drv = NULL;
|
||||
int max_devs;
|
||||
int index;
|
||||
int ro = 0;
|
||||
@ -338,6 +338,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
QemuOpts *opts;
|
||||
QDict *bs_opts;
|
||||
const char *id;
|
||||
bool has_driver_specific_opts;
|
||||
|
||||
translation = BIOS_ATA_TRANSLATION_AUTO;
|
||||
media = MEDIA_DISK;
|
||||
@ -365,6 +366,8 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
qdict_del(bs_opts, "id");
|
||||
}
|
||||
|
||||
has_driver_specific_opts = !!qdict_size(bs_opts);
|
||||
|
||||
/* extract parameters */
|
||||
bus_id = qemu_opt_get_number(opts, "bus", 0);
|
||||
unit_id = qemu_opt_get_number(opts, "unit", -1);
|
||||
@ -375,7 +378,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
secs = qemu_opt_get_number(opts, "secs", 0);
|
||||
|
||||
snapshot = qemu_opt_get_bool(opts, "snapshot", 0);
|
||||
ro = qemu_opt_get_bool(opts, "readonly", 0);
|
||||
ro = qemu_opt_get_bool(opts, "read-only", 0);
|
||||
copy_on_read = qemu_opt_get_bool(opts, "copy-on-read", false);
|
||||
|
||||
file = qemu_opt_get(opts, "file");
|
||||
@ -449,12 +452,15 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
}
|
||||
}
|
||||
|
||||
bdrv_flags |= BDRV_O_CACHE_WB;
|
||||
if ((buf = qemu_opt_get(opts, "cache")) != NULL) {
|
||||
if (bdrv_parse_cache_flags(buf, &bdrv_flags) != 0) {
|
||||
error_report("invalid cache option");
|
||||
return NULL;
|
||||
}
|
||||
bdrv_flags = 0;
|
||||
if (qemu_opt_get_bool(opts, "cache.writeback", true)) {
|
||||
bdrv_flags |= BDRV_O_CACHE_WB;
|
||||
}
|
||||
if (qemu_opt_get_bool(opts, "cache.direct", false)) {
|
||||
bdrv_flags |= BDRV_O_NOCACHE;
|
||||
}
|
||||
if (qemu_opt_get_bool(opts, "cache.no-flush", true)) {
|
||||
bdrv_flags |= BDRV_O_NO_FLUSH;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LINUX_AIO
|
||||
@ -477,26 +483,23 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
error_printf("\n");
|
||||
return NULL;
|
||||
}
|
||||
drv = bdrv_find_whitelisted_format(buf, ro);
|
||||
if (!drv) {
|
||||
error_report("'%s' invalid format", buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qdict_put(bs_opts, "driver", qstring_from_str(buf));
|
||||
}
|
||||
|
||||
/* disk I/O throttling */
|
||||
io_limits.bps[BLOCK_IO_LIMIT_TOTAL] =
|
||||
qemu_opt_get_number(opts, "bps", 0);
|
||||
qemu_opt_get_number(opts, "throttling.bps-total", 0);
|
||||
io_limits.bps[BLOCK_IO_LIMIT_READ] =
|
||||
qemu_opt_get_number(opts, "bps_rd", 0);
|
||||
qemu_opt_get_number(opts, "throttling.bps-read", 0);
|
||||
io_limits.bps[BLOCK_IO_LIMIT_WRITE] =
|
||||
qemu_opt_get_number(opts, "bps_wr", 0);
|
||||
qemu_opt_get_number(opts, "throttling.bps-write", 0);
|
||||
io_limits.iops[BLOCK_IO_LIMIT_TOTAL] =
|
||||
qemu_opt_get_number(opts, "iops", 0);
|
||||
qemu_opt_get_number(opts, "throttling.iops-total", 0);
|
||||
io_limits.iops[BLOCK_IO_LIMIT_READ] =
|
||||
qemu_opt_get_number(opts, "iops_rd", 0);
|
||||
qemu_opt_get_number(opts, "throttling.iops-read", 0);
|
||||
io_limits.iops[BLOCK_IO_LIMIT_WRITE] =
|
||||
qemu_opt_get_number(opts, "iops_wr", 0);
|
||||
qemu_opt_get_number(opts, "throttling.iops-write", 0);
|
||||
|
||||
if (!do_check_io_limits(&io_limits, &error)) {
|
||||
error_report("%s", error_get_pretty(error));
|
||||
@ -658,7 +661,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
abort();
|
||||
}
|
||||
if (!file || !*file) {
|
||||
if (qdict_size(bs_opts)) {
|
||||
if (has_driver_specific_opts) {
|
||||
file = NULL;
|
||||
} else {
|
||||
return dinfo;
|
||||
@ -684,7 +687,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
} else if (ro == 1) {
|
||||
if (type != IF_SCSI && type != IF_VIRTIO && type != IF_FLOPPY &&
|
||||
type != IF_NONE && type != IF_PFLASH) {
|
||||
error_report("readonly not supported by this bus type");
|
||||
error_report("read-only not supported by this bus type");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
@ -692,16 +695,16 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
bdrv_flags |= ro ? 0 : BDRV_O_RDWR;
|
||||
|
||||
if (ro && copy_on_read) {
|
||||
error_report("warning: disabling copy_on_read on readonly drive");
|
||||
error_report("warning: disabling copy_on_read on read-only drive");
|
||||
}
|
||||
|
||||
ret = bdrv_open(dinfo->bdrv, file, bs_opts, bdrv_flags, drv);
|
||||
bs_opts = NULL;
|
||||
QINCREF(bs_opts);
|
||||
ret = bdrv_open(dinfo->bdrv, file, bs_opts, bdrv_flags, NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret == -EMEDIUMTYPE) {
|
||||
error_report("could not open disk image %s: not in %s format",
|
||||
file ?: dinfo->id, drv->format_name);
|
||||
file ?: dinfo->id, qdict_get_str(bs_opts, "driver"));
|
||||
} else {
|
||||
error_report("could not open disk image %s: %s",
|
||||
file ?: dinfo->id, strerror(-ret));
|
||||
@ -712,6 +715,7 @@ DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
if (bdrv_key_required(dinfo->bdrv))
|
||||
autostart = 0;
|
||||
|
||||
QDECREF(bs_opts);
|
||||
qemu_opts_del(opts);
|
||||
|
||||
return dinfo;
|
||||
@ -726,6 +730,60 @@ err:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void qemu_opt_rename(QemuOpts *opts, const char *from, const char *to)
|
||||
{
|
||||
const char *value;
|
||||
|
||||
value = qemu_opt_get(opts, from);
|
||||
if (value) {
|
||||
qemu_opt_set(opts, to, value);
|
||||
qemu_opt_unset(opts, from);
|
||||
}
|
||||
}
|
||||
|
||||
DriveInfo *drive_init(QemuOpts *all_opts, BlockInterfaceType block_default_type)
|
||||
{
|
||||
const char *value;
|
||||
|
||||
/* Change legacy command line options into QMP ones */
|
||||
qemu_opt_rename(all_opts, "iops", "throttling.iops-total");
|
||||
qemu_opt_rename(all_opts, "iops_rd", "throttling.iops-read");
|
||||
qemu_opt_rename(all_opts, "iops_wr", "throttling.iops-write");
|
||||
|
||||
qemu_opt_rename(all_opts, "bps", "throttling.bps-total");
|
||||
qemu_opt_rename(all_opts, "bps_rd", "throttling.bps-read");
|
||||
qemu_opt_rename(all_opts, "bps_wr", "throttling.bps-write");
|
||||
|
||||
qemu_opt_rename(all_opts, "readonly", "read-only");
|
||||
|
||||
value = qemu_opt_get(all_opts, "cache");
|
||||
if (value) {
|
||||
int flags = 0;
|
||||
|
||||
if (bdrv_parse_cache_flags(value, &flags) != 0) {
|
||||
error_report("invalid cache option");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Specific options take precedence */
|
||||
if (!qemu_opt_get(all_opts, "cache.writeback")) {
|
||||
qemu_opt_set_bool(all_opts, "cache.writeback",
|
||||
!!(flags & BDRV_O_CACHE_WB));
|
||||
}
|
||||
if (!qemu_opt_get(all_opts, "cache.direct")) {
|
||||
qemu_opt_set_bool(all_opts, "cache.direct",
|
||||
!!(flags & BDRV_O_NOCACHE));
|
||||
}
|
||||
if (!qemu_opt_get(all_opts, "cache.no-flush")) {
|
||||
qemu_opt_set_bool(all_opts, "cache.no-flush",
|
||||
!!(flags & BDRV_O_NO_FLUSH));
|
||||
}
|
||||
qemu_opt_unset(all_opts, "cache");
|
||||
}
|
||||
|
||||
return blockdev_init(all_opts, block_default_type);
|
||||
}
|
||||
|
||||
void do_commit(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *device = qdict_get_str(qdict, "device");
|
||||
@ -1431,16 +1489,13 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
BlockDriverState *target_bs;
|
||||
BlockDriverState *source = NULL;
|
||||
BlockDriver *drv = NULL;
|
||||
Error *local_err = NULL;
|
||||
int flags;
|
||||
int64_t size;
|
||||
int ret;
|
||||
|
||||
if (sync != MIRROR_SYNC_MODE_FULL) {
|
||||
error_setg(errp, "only sync mode 'full' is currently supported");
|
||||
return;
|
||||
}
|
||||
if (!has_speed) {
|
||||
speed = 0;
|
||||
}
|
||||
@ -1483,6 +1538,18 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||
|
||||
flags = bs->open_flags | BDRV_O_RDWR;
|
||||
|
||||
/* See if we have a backing HD we can use to create our new image
|
||||
* on top of. */
|
||||
if (sync == MIRROR_SYNC_MODE_TOP) {
|
||||
source = bs->backing_hd;
|
||||
if (!source) {
|
||||
sync = MIRROR_SYNC_MODE_FULL;
|
||||
}
|
||||
}
|
||||
if (sync == MIRROR_SYNC_MODE_NONE) {
|
||||
source = bs;
|
||||
}
|
||||
|
||||
size = bdrv_getlength(bs);
|
||||
if (size < 0) {
|
||||
error_setg_errno(errp, -size, "bdrv_getlength failed");
|
||||
@ -1491,8 +1558,14 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||
|
||||
if (mode != NEW_IMAGE_MODE_EXISTING) {
|
||||
assert(format && drv);
|
||||
bdrv_img_create(target, format,
|
||||
NULL, NULL, NULL, size, flags, &local_err, false);
|
||||
if (source) {
|
||||
bdrv_img_create(target, format, source->filename,
|
||||
source->drv->format_name, NULL,
|
||||
size, flags, &local_err, false);
|
||||
} else {
|
||||
bdrv_img_create(target, format, NULL, NULL, NULL,
|
||||
size, flags, &local_err, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (error_is_set(&local_err)) {
|
||||
@ -1508,7 +1581,7 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||
return;
|
||||
}
|
||||
|
||||
backup_start(bs, target_bs, speed, on_source_error, on_target_error,
|
||||
backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
|
||||
block_job_cb, bs, &local_err);
|
||||
if (local_err != NULL) {
|
||||
bdrv_delete(target_bs);
|
||||
@ -1822,10 +1895,17 @@ QemuOptsList qemu_common_drive_opts = {
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "discard operation (ignore/off, unmap/on)",
|
||||
},{
|
||||
.name = "cache",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "host cache usage (none, writeback, writethrough, "
|
||||
"directsync, unsafe)",
|
||||
.name = "cache.writeback",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
.help = "enables writeback mode for any caches",
|
||||
},{
|
||||
.name = "cache.direct",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
.help = "enables use of O_DIRECT (bypass the host page cache)",
|
||||
},{
|
||||
.name = "cache.no-flush",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
.help = "ignore any flush requests for the device",
|
||||
},{
|
||||
.name = "aio",
|
||||
.type = QEMU_OPT_STRING,
|
||||
@ -1851,31 +1931,31 @@ QemuOptsList qemu_common_drive_opts = {
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "pci address (virtio only)",
|
||||
},{
|
||||
.name = "readonly",
|
||||
.name = "read-only",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
.help = "open drive file as read-only",
|
||||
},{
|
||||
.name = "iops",
|
||||
.name = "throttling.iops-total",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "limit total I/O operations per second",
|
||||
},{
|
||||
.name = "iops_rd",
|
||||
.name = "throttling.iops-read",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "limit read operations per second",
|
||||
},{
|
||||
.name = "iops_wr",
|
||||
.name = "throttling.iops-write",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "limit write operations per second",
|
||||
},{
|
||||
.name = "bps",
|
||||
.name = "throttling.bps-total",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "limit total bytes per second",
|
||||
},{
|
||||
.name = "bps_rd",
|
||||
.name = "throttling.bps-read",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "limit read bytes per second",
|
||||
},{
|
||||
.name = "bps_wr",
|
||||
.name = "throttling.bps-write",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "limit write bytes per second",
|
||||
},{
|
||||
|
@ -34,9 +34,15 @@ OrderedDicts so that ordering is preserved.
|
||||
There are two basic syntaxes used, type definitions and command definitions.
|
||||
|
||||
The first syntax defines a type and is represented by a dictionary. There are
|
||||
two kinds of types that are supported: complex user-defined types, and enums.
|
||||
three kinds of user-defined types that are supported: complex types,
|
||||
enumeration types and union types.
|
||||
|
||||
A complex type is a dictionary containing a single key who's value is a
|
||||
Generally speaking, types definitions should always use CamelCase for the type
|
||||
names. Command names should be all lower case with words separated by a hyphen.
|
||||
|
||||
=== Complex types ===
|
||||
|
||||
A complex type is a dictionary containing a single key whose value is a
|
||||
dictionary. This corresponds to a struct in C or an Object in JSON. An
|
||||
example of a complex type is:
|
||||
|
||||
@ -47,13 +53,104 @@ The use of '*' as a prefix to the name means the member is optional. Optional
|
||||
members should always be added to the end of the dictionary to preserve
|
||||
backwards compatibility.
|
||||
|
||||
An enumeration type is a dictionary containing a single key who's value is a
|
||||
=== Enumeration types ===
|
||||
|
||||
An enumeration type is a dictionary containing a single key whose value is a
|
||||
list of strings. An example enumeration is:
|
||||
|
||||
{ 'enum': 'MyEnum', 'data': [ 'value1', 'value2', 'value3' ] }
|
||||
|
||||
Generally speaking, complex types and enums should always use CamelCase for
|
||||
the type names.
|
||||
=== Union types ===
|
||||
|
||||
Union types are used to let the user choose between several different data
|
||||
types. A union type is defined using a dictionary as explained in the
|
||||
following paragraphs.
|
||||
|
||||
|
||||
A simple union type defines a mapping from discriminator values to data types
|
||||
like in this example:
|
||||
|
||||
{ 'type': 'FileOptions', 'data': { 'filename': 'str' } }
|
||||
{ 'type': 'Qcow2Options',
|
||||
'data': { 'backing-file': 'str', 'lazy-refcounts': 'bool' } }
|
||||
|
||||
{ 'union': 'BlockdevOptions',
|
||||
'data': { 'file': 'FileOptions',
|
||||
'qcow2': 'Qcow2Options' } }
|
||||
|
||||
In the QMP wire format, a simple union is represented by a dictionary that
|
||||
contains the 'type' field as a discriminator, and a 'data' field that is of the
|
||||
specified data type corresponding to the discriminator value:
|
||||
|
||||
{ "type": "qcow2", "data" : { "backing-file": "/some/place/my-image",
|
||||
"lazy-refcounts": true } }
|
||||
|
||||
|
||||
A union definition can specify a complex type as its base. In this case, the
|
||||
fields of the complex type are included as top-level fields of the union
|
||||
dictionary in the QMP wire format. An example definition is:
|
||||
|
||||
{ 'type': 'BlockdevCommonOptions', 'data': { 'readonly': 'bool' } }
|
||||
{ 'union': 'BlockdevOptions',
|
||||
'base': 'BlockdevCommonOptions',
|
||||
'data': { 'raw': 'RawOptions',
|
||||
'qcow2': 'Qcow2Options' } }
|
||||
|
||||
And it looks like this on the wire:
|
||||
|
||||
{ "type": "qcow2",
|
||||
"readonly": false,
|
||||
"data" : { "backing-file": "/some/place/my-image",
|
||||
"lazy-refcounts": true } }
|
||||
|
||||
|
||||
Flat union types avoid the nesting on the wire. They are used whenever a
|
||||
specific field of the base type is declared as the discriminator ('type' is
|
||||
then no longer generated). The discriminator must always be a string field.
|
||||
The above example can then be modified as follows:
|
||||
|
||||
{ 'type': 'BlockdevCommonOptions',
|
||||
'data': { 'driver': 'str', 'readonly': 'bool' } }
|
||||
{ 'union': 'BlockdevOptions',
|
||||
'base': 'BlockdevCommonOptions',
|
||||
'discriminator': 'driver',
|
||||
'data': { 'raw': 'RawOptions',
|
||||
'qcow2': 'Qcow2Options' } }
|
||||
|
||||
Resulting in this JSON object:
|
||||
|
||||
{ "driver": "qcow2",
|
||||
"readonly": false,
|
||||
"backing-file": "/some/place/my-image",
|
||||
"lazy-refcounts": true }
|
||||
|
||||
|
||||
A special type of unions are anonymous unions. They don't form a dictionary in
|
||||
the wire format but allow the direct use of different types in their place. As
|
||||
they aren't structured, they don't have any explicit discriminator but use
|
||||
the (QObject) data type of their value as an implicit discriminator. This means
|
||||
that they are restricted to using only one discriminator value per QObject
|
||||
type. For example, you cannot have two different complex types in an anonymous
|
||||
union, or two different integer types.
|
||||
|
||||
Anonymous unions are declared using an empty dictionary as their discriminator.
|
||||
The discriminator values never appear on the wire, they are only used in the
|
||||
generated C code. Anonymous unions cannot have a base type.
|
||||
|
||||
{ 'union': 'BlockRef',
|
||||
'discriminator': {},
|
||||
'data': { 'definition': 'BlockdevOptions',
|
||||
'reference': 'str' } }
|
||||
|
||||
This example allows using both of the following example objects:
|
||||
|
||||
{ "file": "my_existing_block_device_id" }
|
||||
{ "file": { "driver": "file",
|
||||
"readonly": false,
|
||||
'filename': "/tmp/mydisk.qcow2" } }
|
||||
|
||||
|
||||
=== Commands ===
|
||||
|
||||
Commands are defined by using a list containing three members. The first
|
||||
member is the command name, the second member is a dictionary containing
|
||||
@ -65,8 +162,6 @@ An example command is:
|
||||
'data': { 'arg1': 'str', '*arg2': 'str' },
|
||||
'returns': 'str' }
|
||||
|
||||
Command names should be all lower case with words separated by a hyphen.
|
||||
|
||||
|
||||
== Code generation ==
|
||||
|
||||
|
@ -404,6 +404,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||
* @bs: Block device to operate on.
|
||||
* @target: Block device to write to.
|
||||
* @speed: The maximum speed, in bytes per second, or 0 for unlimited.
|
||||
* @sync_mode: What parts of the disk image should be copied to the destination.
|
||||
* @on_source_error: The action to take upon error reading from the source.
|
||||
* @on_target_error: The action to take upon error writing to the target.
|
||||
* @cb: Completion function for the job.
|
||||
@ -413,7 +414,8 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
|
||||
* until the job is cancelled or manually completed.
|
||||
*/
|
||||
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||
int64_t speed, BlockdevOnError on_source_error,
|
||||
int64_t speed, MirrorSyncMode sync_mode,
|
||||
BlockdevOnError on_source_error,
|
||||
BlockdevOnError on_target_error,
|
||||
BlockDriverCompletionFunc *cb, void *opaque,
|
||||
Error **errp);
|
||||
|
@ -65,5 +65,6 @@ int qdict_get_try_bool(const QDict *qdict, const char *key, int def_value);
|
||||
const char *qdict_get_try_str(const QDict *qdict, const char *key);
|
||||
|
||||
QDict *qdict_clone_shallow(const QDict *src);
|
||||
void qdict_flatten(QDict *qdict);
|
||||
|
||||
#endif /* QDICT_H */
|
||||
|
@ -44,6 +44,7 @@ typedef enum {
|
||||
QTYPE_QFLOAT,
|
||||
QTYPE_QBOOL,
|
||||
QTYPE_QERROR,
|
||||
QTYPE_MAX,
|
||||
} qtype_code;
|
||||
|
||||
struct QObject;
|
||||
|
@ -22,12 +22,18 @@ struct Visitor
|
||||
const char *name, size_t size, Error **errp);
|
||||
void (*end_struct)(Visitor *v, Error **errp);
|
||||
|
||||
void (*start_implicit_struct)(Visitor *v, void **obj, size_t size,
|
||||
Error **errp);
|
||||
void (*end_implicit_struct)(Visitor *v, Error **errp);
|
||||
|
||||
void (*start_list)(Visitor *v, const char *name, Error **errp);
|
||||
GenericList *(*next_list)(Visitor *v, GenericList **list, Error **errp);
|
||||
void (*end_list)(Visitor *v, Error **errp);
|
||||
|
||||
void (*type_enum)(Visitor *v, int *obj, const char *strings[],
|
||||
const char *kind, const char *name, Error **errp);
|
||||
void (*get_next_type)(Visitor *v, int *kind, const int *qobjects,
|
||||
const char *name, Error **errp);
|
||||
|
||||
void (*type_int)(Visitor *v, int64_t *obj, const char *name, Error **errp);
|
||||
void (*type_bool)(Visitor *v, bool *obj, const char *name, Error **errp);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#ifndef QAPI_VISITOR_CORE_H
|
||||
#define QAPI_VISITOR_CORE_H
|
||||
|
||||
#include "qapi/qmp/qobject.h"
|
||||
#include "qapi/error.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
@ -33,12 +34,17 @@ void visit_end_handle(Visitor *v, Error **errp);
|
||||
void visit_start_struct(Visitor *v, void **obj, const char *kind,
|
||||
const char *name, size_t size, Error **errp);
|
||||
void visit_end_struct(Visitor *v, Error **errp);
|
||||
void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
|
||||
Error **errp);
|
||||
void visit_end_implicit_struct(Visitor *v, Error **errp);
|
||||
void visit_start_list(Visitor *v, const char *name, Error **errp);
|
||||
GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp);
|
||||
void visit_end_list(Visitor *v, Error **errp);
|
||||
void visit_start_optional(Visitor *v, bool *present, const char *name,
|
||||
Error **errp);
|
||||
void visit_end_optional(Visitor *v, Error **errp);
|
||||
void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
|
||||
const char *name, Error **errp);
|
||||
void visit_type_enum(Visitor *v, int *obj, const char *strings[],
|
||||
const char *kind, const char *name, Error **errp);
|
||||
void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp);
|
||||
|
@ -120,6 +120,7 @@ bool qemu_opt_has_help_opt(QemuOpts *opts);
|
||||
bool qemu_opt_get_bool(QemuOpts *opts, const char *name, bool defval);
|
||||
uint64_t qemu_opt_get_number(QemuOpts *opts, const char *name, uint64_t defval);
|
||||
uint64_t qemu_opt_get_size(QemuOpts *opts, const char *name, uint64_t defval);
|
||||
int qemu_opt_unset(QemuOpts *opts, const char *name);
|
||||
int qemu_opt_set(QemuOpts *opts, const char *name, const char *value);
|
||||
void qemu_opt_set_err(QemuOpts *opts, const char *name, const char *value,
|
||||
Error **errp);
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/qobject.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "qapi/visitor-impl.h"
|
||||
@ -45,6 +46,22 @@ void visit_end_struct(Visitor *v, Error **errp)
|
||||
v->end_struct(v, errp);
|
||||
}
|
||||
|
||||
void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
|
||||
Error **errp)
|
||||
{
|
||||
if (!error_is_set(errp) && v->start_implicit_struct) {
|
||||
v->start_implicit_struct(v, obj, size, errp);
|
||||
}
|
||||
}
|
||||
|
||||
void visit_end_implicit_struct(Visitor *v, Error **errp)
|
||||
{
|
||||
assert(!error_is_set(errp));
|
||||
if (v->end_implicit_struct) {
|
||||
v->end_implicit_struct(v, errp);
|
||||
}
|
||||
}
|
||||
|
||||
void visit_start_list(Visitor *v, const char *name, Error **errp)
|
||||
{
|
||||
if (!error_is_set(errp)) {
|
||||
@ -82,6 +99,14 @@ void visit_end_optional(Visitor *v, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
|
||||
const char *name, Error **errp)
|
||||
{
|
||||
if (!error_is_set(errp) && v->get_next_type) {
|
||||
v->get_next_type(v, obj, qtypes, name, errp);
|
||||
}
|
||||
}
|
||||
|
||||
void visit_type_enum(Visitor *v, int *obj, const char *strings[],
|
||||
const char *kind, const char *name, Error **errp)
|
||||
{
|
||||
|
@ -41,13 +41,14 @@ static QmpInputVisitor *to_qiv(Visitor *v)
|
||||
}
|
||||
|
||||
static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
|
||||
const char *name)
|
||||
const char *name,
|
||||
bool consume)
|
||||
{
|
||||
QObject *qobj = qiv->stack[qiv->nb_stack - 1].obj;
|
||||
|
||||
if (qobj) {
|
||||
if (name && qobject_type(qobj) == QTYPE_QDICT) {
|
||||
if (qiv->stack[qiv->nb_stack - 1].h) {
|
||||
if (qiv->stack[qiv->nb_stack - 1].h && consume) {
|
||||
g_hash_table_remove(qiv->stack[qiv->nb_stack - 1].h, name);
|
||||
}
|
||||
return qdict_get(qobject_to_qdict(qobj), name);
|
||||
@ -117,7 +118,7 @@ static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind,
|
||||
const char *name, size_t size, Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, true);
|
||||
Error *err = NULL;
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QDICT) {
|
||||
@ -144,10 +145,22 @@ static void qmp_input_end_struct(Visitor *v, Error **errp)
|
||||
qmp_input_pop(qiv, errp);
|
||||
}
|
||||
|
||||
static void qmp_input_start_implicit_struct(Visitor *v, void **obj,
|
||||
size_t size, Error **errp)
|
||||
{
|
||||
if (obj) {
|
||||
*obj = g_malloc0(size);
|
||||
}
|
||||
}
|
||||
|
||||
static void qmp_input_end_implicit_struct(Visitor *v, Error **errp)
|
||||
{
|
||||
}
|
||||
|
||||
static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, true);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QLIST) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
@ -195,11 +208,24 @@ static void qmp_input_end_list(Visitor *v, Error **errp)
|
||||
qmp_input_pop(qiv, errp);
|
||||
}
|
||||
|
||||
static void qmp_input_get_next_type(Visitor *v, int *kind, const int *qobjects,
|
||||
const char *name, Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, false);
|
||||
|
||||
if (!qobj) {
|
||||
error_set(errp, QERR_MISSING_PARAMETER, name ? name : "null");
|
||||
return;
|
||||
}
|
||||
*kind = qobjects[qobject_type(qobj)];
|
||||
}
|
||||
|
||||
static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, true);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QINT) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
@ -214,7 +240,7 @@ static void qmp_input_type_bool(Visitor *v, bool *obj, const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, true);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QBOOL) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
@ -229,7 +255,7 @@ static void qmp_input_type_str(Visitor *v, char **obj, const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, true);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QSTRING) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
@ -244,7 +270,7 @@ static void qmp_input_type_number(Visitor *v, double *obj, const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, true);
|
||||
|
||||
if (!qobj || (qobject_type(qobj) != QTYPE_QFLOAT &&
|
||||
qobject_type(qobj) != QTYPE_QINT)) {
|
||||
@ -264,7 +290,7 @@ static void qmp_input_start_optional(Visitor *v, bool *present,
|
||||
const char *name, Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name, true);
|
||||
|
||||
if (!qobj) {
|
||||
*present = false;
|
||||
@ -293,6 +319,8 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
|
||||
|
||||
v->visitor.start_struct = qmp_input_start_struct;
|
||||
v->visitor.end_struct = qmp_input_end_struct;
|
||||
v->visitor.start_implicit_struct = qmp_input_start_implicit_struct;
|
||||
v->visitor.end_implicit_struct = qmp_input_end_implicit_struct;
|
||||
v->visitor.start_list = qmp_input_start_list;
|
||||
v->visitor.next_list = qmp_input_next_list;
|
||||
v->visitor.end_list = qmp_input_end_list;
|
||||
@ -302,6 +330,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
|
||||
v->visitor.type_str = qmp_input_type_str;
|
||||
v->visitor.type_number = qmp_input_type_number;
|
||||
v->visitor.start_optional = qmp_input_start_optional;
|
||||
v->visitor.get_next_type = qmp_input_get_next_type;
|
||||
|
||||
qmp_input_push(v, obj, NULL);
|
||||
qobject_incref(obj);
|
||||
|
@ -960,6 +960,7 @@ Arguments:
|
||||
|
||||
Example:
|
||||
-> { "execute": "drive-backup", "arguments": { "device": "drive0",
|
||||
"sync": "full",
|
||||
"target": "backup.img" } }
|
||||
<- { "return": {} }
|
||||
EQMP
|
||||
|
@ -476,3 +476,54 @@ static void qdict_destroy_obj(QObject *obj)
|
||||
|
||||
g_free(qdict);
|
||||
}
|
||||
|
||||
static void qdict_do_flatten(QDict *qdict, QDict *target, const char *prefix)
|
||||
{
|
||||
QObject *value;
|
||||
const QDictEntry *entry, *next;
|
||||
const char *new_key;
|
||||
bool delete;
|
||||
|
||||
entry = qdict_first(qdict);
|
||||
|
||||
while (entry != NULL) {
|
||||
|
||||
next = qdict_next(qdict, entry);
|
||||
value = qdict_entry_value(entry);
|
||||
new_key = NULL;
|
||||
delete = false;
|
||||
|
||||
if (prefix) {
|
||||
qobject_incref(value);
|
||||
new_key = g_strdup_printf("%s.%s", prefix, entry->key);
|
||||
qdict_put_obj(target, new_key, value);
|
||||
delete = true;
|
||||
}
|
||||
|
||||
if (qobject_type(value) == QTYPE_QDICT) {
|
||||
qdict_do_flatten(qobject_to_qdict(value), target,
|
||||
new_key ? new_key : entry->key);
|
||||
delete = true;
|
||||
}
|
||||
|
||||
if (delete) {
|
||||
qdict_del(qdict, entry->key);
|
||||
|
||||
/* Restart loop after modifying the iterated QDict */
|
||||
entry = qdict_first(qdict);
|
||||
continue;
|
||||
}
|
||||
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* qdict_flatten(): For each nested QDict with key x, all fields with key y
|
||||
* are moved to this QDict and their key is renamed to "x.y". This operation
|
||||
* is applied recursively for nested QDicts.
|
||||
*/
|
||||
void qdict_flatten(QDict *qdict)
|
||||
{
|
||||
qdict_do_flatten(qdict, qdict, NULL);
|
||||
}
|
||||
|
@ -260,6 +260,8 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent)
|
||||
/* XXX: should QError be emitted? */
|
||||
case QTYPE_NONE:
|
||||
break;
|
||||
case QTYPE_MAX:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,48 @@ typedef enum %(name)s
|
||||
|
||||
return lookup_decl + enum_decl
|
||||
|
||||
def generate_union(name, typeinfo):
|
||||
def generate_anon_union_qtypes(expr):
|
||||
|
||||
name = expr['union']
|
||||
members = expr['data']
|
||||
|
||||
ret = mcgen('''
|
||||
const int %(name)s_qtypes[QTYPE_MAX] = {
|
||||
''',
|
||||
name=name)
|
||||
|
||||
for key in members:
|
||||
qapi_type = members[key]
|
||||
if builtin_type_qtypes.has_key(qapi_type):
|
||||
qtype = builtin_type_qtypes[qapi_type]
|
||||
elif find_struct(qapi_type):
|
||||
qtype = "QTYPE_QDICT"
|
||||
elif find_union(qapi_type):
|
||||
qtype = "QTYPE_QDICT"
|
||||
else:
|
||||
assert False, "Invalid anonymous union member"
|
||||
|
||||
ret += mcgen('''
|
||||
[ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s,
|
||||
''',
|
||||
qtype = qtype,
|
||||
abbrev = de_camel_case(name).upper(),
|
||||
enum = c_fun(de_camel_case(key),False).upper())
|
||||
|
||||
ret += mcgen('''
|
||||
};
|
||||
''')
|
||||
return ret
|
||||
|
||||
|
||||
def generate_union(expr):
|
||||
|
||||
name = expr['union']
|
||||
typeinfo = expr['data']
|
||||
|
||||
base = expr.get('base')
|
||||
discriminator = expr.get('discriminator')
|
||||
|
||||
ret = mcgen('''
|
||||
struct %(name)s
|
||||
{
|
||||
@ -169,8 +210,26 @@ struct %(name)s
|
||||
|
||||
ret += mcgen('''
|
||||
};
|
||||
''')
|
||||
|
||||
if base:
|
||||
base_fields = find_struct(base)['data']
|
||||
if discriminator:
|
||||
base_fields = base_fields.copy()
|
||||
del base_fields[discriminator]
|
||||
ret += generate_struct_fields(base_fields)
|
||||
else:
|
||||
assert not discriminator
|
||||
|
||||
ret += mcgen('''
|
||||
};
|
||||
''')
|
||||
if discriminator == {}:
|
||||
ret += mcgen('''
|
||||
extern const int %(name)s_qtypes[];
|
||||
''',
|
||||
name=name)
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
@ -323,6 +382,8 @@ for expr in exprs:
|
||||
ret += generate_fwd_struct(expr['union'], expr['data']) + "\n"
|
||||
ret += generate_enum('%sKind' % expr['union'], expr['data'].keys())
|
||||
fdef.write(generate_enum_lookup('%sKind' % expr['union'], expr['data'].keys()))
|
||||
if expr.get('discriminator') == {}:
|
||||
fdef.write(generate_anon_union_qtypes(expr))
|
||||
else:
|
||||
continue
|
||||
fdecl.write(ret)
|
||||
@ -352,7 +413,7 @@ for expr in exprs:
|
||||
ret += generate_type_cleanup_decl(expr['type'])
|
||||
fdef.write(generate_type_cleanup(expr['type']) + "\n")
|
||||
elif expr.has_key('union'):
|
||||
ret += generate_union(expr['union'], expr['data'])
|
||||
ret += generate_union(expr)
|
||||
ret += generate_type_cleanup_decl(expr['union'] + "List")
|
||||
fdef.write(generate_type_cleanup(expr['union'] + "List") + "\n")
|
||||
ret += generate_type_cleanup_decl(expr['union'])
|
||||
|
@ -17,34 +17,31 @@ import os
|
||||
import getopt
|
||||
import errno
|
||||
|
||||
def generate_visit_struct_body(field_prefix, name, members):
|
||||
ret = mcgen('''
|
||||
if (!error_is_set(errp)) {
|
||||
''')
|
||||
push_indent()
|
||||
def generate_visit_struct_fields(name, field_prefix, fn_prefix, members):
|
||||
substructs = []
|
||||
ret = ''
|
||||
full_name = name if not fn_prefix else "%s_%s" % (name, fn_prefix)
|
||||
|
||||
if len(field_prefix):
|
||||
field_prefix = field_prefix + "."
|
||||
ret += mcgen('''
|
||||
Error **errp = &err; /* from outer scope */
|
||||
Error *err = NULL;
|
||||
visit_start_struct(m, NULL, "", "%(name)s", 0, &err);
|
||||
''',
|
||||
name=name)
|
||||
else:
|
||||
ret += mcgen('''
|
||||
Error *err = NULL;
|
||||
visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
|
||||
''',
|
||||
name=name)
|
||||
for argname, argentry, optional, structured in parse_args(members):
|
||||
if structured:
|
||||
if not fn_prefix:
|
||||
nested_fn_prefix = argname
|
||||
else:
|
||||
nested_fn_prefix = "%s_%s" % (fn_prefix, argname)
|
||||
|
||||
nested_field_prefix = "%s%s." % (field_prefix, argname)
|
||||
ret += generate_visit_struct_fields(name, nested_field_prefix,
|
||||
nested_fn_prefix, argentry)
|
||||
|
||||
ret += mcgen('''
|
||||
if (!err) {
|
||||
if (!obj || *obj) {
|
||||
''')
|
||||
|
||||
static void visit_type_%(full_name)s_fields(Visitor *m, %(name)s ** obj, Error **errp)
|
||||
{
|
||||
Error *err = NULL;
|
||||
''',
|
||||
name=name, full_name=full_name)
|
||||
push_indent()
|
||||
push_indent()
|
||||
|
||||
for argname, argentry, optional, structured in parse_args(members):
|
||||
if optional:
|
||||
ret += mcgen('''
|
||||
@ -56,7 +53,7 @@ if (obj && (*obj)->%(prefix)shas_%(c_name)s) {
|
||||
push_indent()
|
||||
|
||||
if structured:
|
||||
ret += generate_visit_struct_body(field_prefix + argname, argname, argentry)
|
||||
ret += generate_visit_struct_body(full_name, argname, argentry)
|
||||
else:
|
||||
ret += mcgen('''
|
||||
visit_type_%(type)s(m, obj ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", &err);
|
||||
@ -76,11 +73,43 @@ visit_end_optional(m, &err);
|
||||
ret += mcgen('''
|
||||
|
||||
error_propagate(errp, err);
|
||||
err = NULL;
|
||||
}
|
||||
''')
|
||||
return ret
|
||||
|
||||
|
||||
def generate_visit_struct_body(field_prefix, name, members):
|
||||
ret = mcgen('''
|
||||
if (!error_is_set(errp)) {
|
||||
''')
|
||||
push_indent()
|
||||
|
||||
full_name = name if not field_prefix else "%s_%s" % (field_prefix, name)
|
||||
|
||||
if len(field_prefix):
|
||||
ret += mcgen('''
|
||||
Error **errp = &err; /* from outer scope */
|
||||
Error *err = NULL;
|
||||
visit_start_struct(m, NULL, "", "%(name)s", 0, &err);
|
||||
''',
|
||||
name=name)
|
||||
else:
|
||||
ret += mcgen('''
|
||||
Error *err = NULL;
|
||||
visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
|
||||
''',
|
||||
name=name)
|
||||
|
||||
ret += mcgen('''
|
||||
if (!err) {
|
||||
if (!obj || *obj) {
|
||||
visit_type_%(name)s_fields(m, obj, &err);
|
||||
error_propagate(errp, err);
|
||||
err = NULL;
|
||||
}
|
||||
''',
|
||||
name=full_name)
|
||||
|
||||
pop_indent()
|
||||
pop_indent()
|
||||
ret += mcgen('''
|
||||
/* Always call end_struct if start_struct succeeded. */
|
||||
@ -92,7 +121,9 @@ visit_end_optional(m, &err);
|
||||
return ret
|
||||
|
||||
def generate_visit_struct(name, members):
|
||||
ret = mcgen('''
|
||||
ret = generate_visit_struct_fields(name, "", "", members)
|
||||
|
||||
ret += mcgen('''
|
||||
|
||||
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
|
||||
{
|
||||
@ -145,9 +176,70 @@ void visit_type_%(name)s(Visitor *m, %(name)s * obj, const char *name, Error **e
|
||||
''',
|
||||
name=name)
|
||||
|
||||
def generate_visit_union(name, members):
|
||||
def generate_visit_anon_union(name, members):
|
||||
ret = mcgen('''
|
||||
|
||||
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
|
||||
{
|
||||
Error *err = NULL;
|
||||
|
||||
if (!error_is_set(errp)) {
|
||||
visit_start_implicit_struct(m, (void**) obj, sizeof(%(name)s), &err);
|
||||
visit_get_next_type(m, (int*) &(*obj)->kind, %(name)s_qtypes, name, &err);
|
||||
switch ((*obj)->kind) {
|
||||
''',
|
||||
name=name)
|
||||
|
||||
for key in members:
|
||||
assert (members[key] in builtin_types
|
||||
or find_struct(members[key])
|
||||
or find_union(members[key])), "Invalid anonymous union member"
|
||||
|
||||
ret += mcgen('''
|
||||
case %(abbrev)s_KIND_%(enum)s:
|
||||
visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, name, &err);
|
||||
break;
|
||||
''',
|
||||
abbrev = de_camel_case(name).upper(),
|
||||
enum = c_fun(de_camel_case(key),False).upper(),
|
||||
c_type = type_name(members[key]),
|
||||
c_name = c_fun(key))
|
||||
|
||||
ret += mcgen('''
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
error_propagate(errp, err);
|
||||
err = NULL;
|
||||
visit_end_implicit_struct(m, &err);
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def generate_visit_union(expr):
|
||||
|
||||
name = expr['union']
|
||||
members = expr['data']
|
||||
|
||||
base = expr.get('base')
|
||||
discriminator = expr.get('discriminator')
|
||||
|
||||
if discriminator == {}:
|
||||
assert not base
|
||||
return generate_visit_anon_union(name, members)
|
||||
|
||||
ret = generate_visit_enum('%sKind' % name, members.keys())
|
||||
|
||||
if base:
|
||||
base_fields = find_struct(base)['data']
|
||||
if discriminator:
|
||||
base_fields = base_fields.copy()
|
||||
del base_fields[discriminator]
|
||||
ret += generate_visit_struct_fields(name, "", "", base_fields)
|
||||
|
||||
ret += mcgen('''
|
||||
|
||||
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
|
||||
@ -158,18 +250,43 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
|
||||
visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
|
||||
if (!err) {
|
||||
if (obj && *obj) {
|
||||
visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
|
||||
if (!err) {
|
||||
switch ((*obj)->kind) {
|
||||
''',
|
||||
name=name)
|
||||
|
||||
|
||||
push_indent()
|
||||
push_indent()
|
||||
push_indent()
|
||||
|
||||
if base:
|
||||
ret += mcgen('''
|
||||
visit_type_%(name)s_fields(m, obj, &err);
|
||||
''',
|
||||
name=name)
|
||||
|
||||
pop_indent()
|
||||
ret += mcgen('''
|
||||
visit_type_%(name)sKind(m, &(*obj)->kind, "%(type)s", &err);
|
||||
if (!err) {
|
||||
switch ((*obj)->kind) {
|
||||
''',
|
||||
name=name, type="type" if not discriminator else discriminator)
|
||||
|
||||
for key in members:
|
||||
if not discriminator:
|
||||
fmt = 'visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);'
|
||||
else:
|
||||
fmt = '''visit_start_implicit_struct(m, (void**) &(*obj)->%(c_name)s, sizeof(%(c_type)s), &err);
|
||||
if (!err) {
|
||||
visit_type_%(c_type)s_fields(m, &(*obj)->%(c_name)s, &err);
|
||||
error_propagate(errp, err);
|
||||
err = NULL;
|
||||
visit_end_implicit_struct(m, &err);
|
||||
}'''
|
||||
|
||||
ret += mcgen('''
|
||||
case %(abbrev)s_KIND_%(enum)s:
|
||||
visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);
|
||||
''' + fmt + '''
|
||||
break;
|
||||
''',
|
||||
abbrev = de_camel_case(name).upper(),
|
||||
@ -362,7 +479,7 @@ for expr in exprs:
|
||||
ret = generate_declaration(expr['type'], expr['data'])
|
||||
fdecl.write(ret)
|
||||
elif expr.has_key('union'):
|
||||
ret = generate_visit_union(expr['union'], expr['data'])
|
||||
ret = generate_visit_union(expr)
|
||||
ret += generate_visit_list(expr['union'], expr['data'])
|
||||
fdef.write(ret)
|
||||
|
||||
|
@ -17,6 +17,21 @@ builtin_types = [
|
||||
'uint8', 'uint16', 'uint32', 'uint64'
|
||||
]
|
||||
|
||||
builtin_type_qtypes = {
|
||||
'str': 'QTYPE_QSTRING',
|
||||
'int': 'QTYPE_QINT',
|
||||
'number': 'QTYPE_QFLOAT',
|
||||
'bool': 'QTYPE_QBOOL',
|
||||
'int8': 'QTYPE_QINT',
|
||||
'int16': 'QTYPE_QINT',
|
||||
'int32': 'QTYPE_QINT',
|
||||
'int64': 'QTYPE_QINT',
|
||||
'uint8': 'QTYPE_QINT',
|
||||
'uint16': 'QTYPE_QINT',
|
||||
'uint32': 'QTYPE_QINT',
|
||||
'uint64': 'QTYPE_QINT',
|
||||
}
|
||||
|
||||
def tokenize(data):
|
||||
while len(data):
|
||||
ch = data[0]
|
||||
@ -105,6 +120,7 @@ def parse_schema(fp):
|
||||
if expr_eval.has_key('enum'):
|
||||
add_enum(expr_eval['enum'])
|
||||
elif expr_eval.has_key('union'):
|
||||
add_union(expr_eval)
|
||||
add_enum('%sKind' % expr_eval['union'])
|
||||
elif expr_eval.has_key('type'):
|
||||
add_struct(expr_eval)
|
||||
@ -188,6 +204,7 @@ def type_name(name):
|
||||
|
||||
enum_types = []
|
||||
struct_types = []
|
||||
union_types = []
|
||||
|
||||
def add_struct(definition):
|
||||
global struct_types
|
||||
@ -200,6 +217,17 @@ def find_struct(name):
|
||||
return struct
|
||||
return None
|
||||
|
||||
def add_union(definition):
|
||||
global union_types
|
||||
union_types.append(definition)
|
||||
|
||||
def find_union(name):
|
||||
global union_types
|
||||
for union in union_types:
|
||||
if union['union'] == name:
|
||||
return union
|
||||
return None
|
||||
|
||||
def add_enum(name):
|
||||
global enum_types
|
||||
enum_types.append(name)
|
||||
|
@ -72,11 +72,11 @@ echo
|
||||
echo === Enable and disable lazy refcounting on the command line, plus some invalid values ===
|
||||
echo
|
||||
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=on
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=off
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=42
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=foo
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy-refcounts=on
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy-refcounts=off
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy-refcounts=
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy-refcounts=42
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy-refcounts=foo
|
||||
|
||||
|
||||
echo
|
||||
@ -85,8 +85,8 @@ echo
|
||||
|
||||
_make_test_img -ocompat=0.10 $size
|
||||
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=on
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=off
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy-refcounts=on
|
||||
run_qemu -drive file=$TEST_IMG,format=qcow2,lazy-refcounts=off
|
||||
|
||||
echo
|
||||
echo === No medium ===
|
||||
|
@ -22,35 +22,35 @@ QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo: could not
|
||||
|
||||
=== Enable and disable lazy refcounting on the command line, plus some invalid values ===
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=on
|
||||
QEMU 1.5.50 monitor - type 'help' for more information
|
||||
(qemu) q[K[Dqu[K[D[Dqui[K[D[D[Dquit[K
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=off
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=off
|
||||
QEMU 1.5.50 monitor - type 'help' for more information
|
||||
(qemu) q[K[Dqu[K[D[Dqui[K[D[D[Dquit[K
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: Parameter 'lazy_refcounts' expects 'on' or 'off'
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=: Parameter 'lazy-refcounts' expects 'on' or 'off'
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: Parameter 'lazy_refcounts' expects 'on' or 'off'
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=42
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=42: Parameter 'lazy-refcounts' expects 'on' or 'off'
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=42: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: Parameter 'lazy_refcounts' expects 'on' or 'off'
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=foo
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=foo: Parameter 'lazy-refcounts' expects 'on' or 'off'
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
|
||||
|
||||
=== With version 2 images enabling lazy refcounts must fail ===
|
||||
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: Lazy refcounts require a qcow2 image with at least qemu 1.1 compatibility level
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=on
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=on: Lazy refcounts require a qcow2 image with at least qemu 1.1 compatibility level
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=off
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy-refcounts=off
|
||||
QEMU 1.5.50 monitor - type 'help' for more information
|
||||
(qemu) q[K[Dqu[K[D[Dqui[K[D[D[Dquit[K
|
||||
|
||||
@ -137,7 +137,7 @@ QEMU 1.5.50 monitor - type 'help' for more information
|
||||
(qemu) q[K[Dqu[K[D[Dqui[K[D[D[Dquit[K
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on: readonly not supported by this bus type
|
||||
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on: read-only not supported by this bus type
|
||||
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,if=virtio,readonly=on
|
||||
QEMU 1.5.50 monitor - type 'help' for more information
|
||||
|
@ -97,6 +97,12 @@ class TestSingleDrive(iotests.QMPTestCase):
|
||||
target=target_img, sync='full', mode='existing')
|
||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||
|
||||
def test_invalid_format(self):
|
||||
result = self.vm.qmp('drive-backup', device='drive0',
|
||||
target=target_img, sync='full',
|
||||
format='spaghetti-noodles')
|
||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||
|
||||
def test_device_not_found(self):
|
||||
result = self.vm.qmp('drive-backup', device='nonexistent',
|
||||
target=target_img, sync='full')
|
||||
|
@ -1,5 +1,5 @@
|
||||
.............
|
||||
..............
|
||||
----------------------------------------------------------------------
|
||||
Ran 13 tests
|
||||
Ran 14 tests
|
||||
|
||||
OK
|
||||
|
94
tests/qemu-iotests/056
Executable file
94
tests/qemu-iotests/056
Executable file
@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Tests for drive-backup
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat, Inc.
|
||||
#
|
||||
# Based on 041.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import time
|
||||
import os
|
||||
import iotests
|
||||
from iotests import qemu_img, qemu_io, create_image
|
||||
|
||||
backing_img = os.path.join(iotests.test_dir, 'backing.img')
|
||||
test_img = os.path.join(iotests.test_dir, 'test.img')
|
||||
target_img = os.path.join(iotests.test_dir, 'target.img')
|
||||
|
||||
class TestSyncModesNoneAndTop(iotests.QMPTestCase):
|
||||
image_len = 64 * 1024 * 1024 # MB
|
||||
|
||||
def setUp(self):
|
||||
create_image(backing_img, TestSyncModesNoneAndTop.image_len)
|
||||
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
|
||||
qemu_io('-c', 'write -P0x41 0 512', test_img)
|
||||
qemu_io('-c', 'write -P0xd5 1M 32k', test_img)
|
||||
qemu_io('-c', 'write -P0xdc 32M 124k', test_img)
|
||||
qemu_io('-c', 'write -P0xdc 67043328 64k', test_img)
|
||||
self.vm = iotests.VM().add_drive(test_img)
|
||||
self.vm.launch()
|
||||
|
||||
def tearDown(self):
|
||||
self.vm.shutdown()
|
||||
os.remove(test_img)
|
||||
os.remove(backing_img)
|
||||
try:
|
||||
os.remove(target_img)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_complete_top(self):
|
||||
self.assert_no_active_block_jobs()
|
||||
result = self.vm.qmp('drive-backup', device='drive0', sync='top',
|
||||
format=iotests.imgfmt, target=target_img)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# Custom completed check as we are not copying all data.
|
||||
completed = False
|
||||
while not completed:
|
||||
for event in self.vm.get_qmp_events(wait=True):
|
||||
if event['event'] == 'BLOCK_JOB_COMPLETED':
|
||||
self.assert_qmp(event, 'data/device', 'drive0')
|
||||
self.assert_qmp_absent(event, 'data/error')
|
||||
completed = True
|
||||
|
||||
self.assert_no_active_block_jobs()
|
||||
self.vm.shutdown()
|
||||
self.assertTrue(iotests.compare_images(test_img, target_img),
|
||||
'target image does not match source after backup')
|
||||
|
||||
def test_cancel_sync_none(self):
|
||||
self.assert_no_active_block_jobs()
|
||||
|
||||
result = self.vm.qmp('drive-backup', device='drive0',
|
||||
sync='none', target=target_img)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
time.sleep(1)
|
||||
self.vm.hmp_qemu_io('drive0', 'write -P0x5e 0 512')
|
||||
self.vm.hmp_qemu_io('drive0', 'aio_flush')
|
||||
# Verify that the original contents exist in the target image.
|
||||
|
||||
event = self.cancel_and_wait()
|
||||
self.assert_qmp(event, 'data/type', 'backup')
|
||||
|
||||
self.vm.shutdown()
|
||||
time.sleep(1)
|
||||
self.assertEqual(-1, qemu_io('-c', 'read -P0x41 0 512', target_img).find("verification failed"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
iotests.main(supported_fmts=['qcow2', 'qed'])
|
5
tests/qemu-iotests/056.out
Normal file
5
tests/qemu-iotests/056.out
Normal file
@ -0,0 +1,5 @@
|
||||
..
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests
|
||||
|
||||
OK
|
@ -62,3 +62,4 @@
|
||||
053 rw auto
|
||||
054 rw auto
|
||||
055 rw auto
|
||||
056 rw auto backing
|
||||
|
@ -95,6 +95,11 @@ class VM(object):
|
||||
self._num_drives += 1
|
||||
return self
|
||||
|
||||
def hmp_qemu_io(self, drive, cmd):
|
||||
'''Write to a given drive using an HMP command'''
|
||||
return self.qmp('human-monitor-command',
|
||||
command_line='qemu-io %s "%s"' % (drive, cmd))
|
||||
|
||||
def add_fd(self, fd, fdset, opaque, opts=''):
|
||||
'''Pass a file descriptor to the VM'''
|
||||
options = ['fd=%d' % fd,
|
||||
|
@ -593,6 +593,20 @@ static const QemuOptDesc *find_desc_by_name(const QemuOptDesc *desc,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int qemu_opt_unset(QemuOpts *opts, const char *name)
|
||||
{
|
||||
QemuOpt *opt = qemu_opt_find(opts, name);
|
||||
|
||||
assert(opts_accepts_any(opts));
|
||||
|
||||
if (opt == NULL) {
|
||||
return -1;
|
||||
} else {
|
||||
qemu_opt_del(opt);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void opt_set(QemuOpts *opts, const char *name, const char *value,
|
||||
bool prepend, Error **errp)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user