M: Alexander Graf <agraf@suse.de>
S: Maintained
F: target-s390x/kvm.c
+F: target-s390x/ioinst.[ch]
+F: target-s390x/machine.c
F: hw/intc/s390_flic.c
F: hw/intc/s390_flic_kvm.c
F: include/hw/s390x/s390_flic.h
+F: gdb-xml/s390*.xml
+T: git git://github.com/cohuck/qemu.git s390-next
+T: git git://github.com/borntraeger/qemu.git s390-next
X86
M: Paolo Bonzini <pbonzini@redhat.com>
M: Alexander Graf <agraf@suse.de>
S: Supported
F: hw/char/sclp*.[hc]
-F: hw/s390x/s390-virtio-ccw.c
-F: hw/s390x/css.[hc]
-F: hw/s390x/sclp*.[hc]
-F: hw/s390x/ipl*.[hc]
-F: hw/s390x/*pci*.[hc]
-F: hw/s390x/s390-skeys*.c
+F: hw/s390x/
+X: hw/s390x/s390-virtio-bus.[ch]
F: include/hw/s390x/
F: pc-bios/s390-ccw/
-T: git git://github.com/cohuck/qemu virtio-ccw-upstr
+F: hw/watchdog/wdt_diag288.c
+T: git git://github.com/cohuck/qemu.git s390-next
+T: git git://github.com/borntraeger/qemu.git s390-next
UniCore32 Machines
-------------
M: Christian Borntraeger <borntraeger@de.ibm.com>
S: Supported
F: hw/s390x/virtio-ccw.[hc]
-T: git git://github.com/cohuck/qemu virtio-ccw-upstr
+T: git git://github.com/cohuck/qemu.git s390-next
+T: git git://github.com/borntraeger/qemu.git s390-next
virtio-input
M: Gerd Hoffmann <kraxel@redhat.com>
#define NOT_DONE 0x7fffffff /* used while emulated sync operation in progress */
-static QTAILQ_HEAD(, BlockDriverState) bdrv_states =
- QTAILQ_HEAD_INITIALIZER(bdrv_states);
+struct BdrvStates bdrv_states = QTAILQ_HEAD_INITIALIZER(bdrv_states);
static QTAILQ_HEAD(, BlockDriverState) graph_bdrv_states =
QTAILQ_HEAD_INITIALIZER(graph_bdrv_states);
BlockDriverState *bs;
BlockDriver *drv = NULL;
const char *drvname;
+ const char *backing;
Error *local_err = NULL;
int snapshot_flags = 0;
assert(drvname || !(flags & BDRV_O_PROTOCOL));
+ backing = qdict_get_try_str(options, "backing");
+ if (backing && *backing == '\0') {
+ flags |= BDRV_O_NO_BACKING;
+ qdict_del(options, "backing");
+ }
+
bs->open_flags = flags;
bs->options = options;
options = qdict_clone_shallow(options);
ret = bdrv_flush(reopen_state->bs);
if (ret) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR, "Error (%s) flushing drive",
- strerror(-ret));
+ error_setg_errno(errp, -ret, "Error flushing drive");
goto error;
}
}
/* Disable I/O limits and drain all pending throttled requests */
- if (bs->io_limits_enabled) {
+ if (bs->throttle_state) {
bdrv_io_limits_disable(bs);
}
blk = blk_by_name(device);
if (blk) {
- if (!blk_bs(blk)) {
+ bs = blk_bs(blk);
+ if (!bs) {
error_setg(errp, "Device '%s' has no medium", device);
- return NULL;
}
- return blk_bs(blk);
+ return bs;
}
}
baf->detach_aio_context(baf->opaque);
}
- if (bs->io_limits_enabled) {
+ if (bs->throttle_state) {
throttle_timers_detach_aio_context(&bs->throttle_timers);
}
if (bs->drv->bdrv_detach_aio_context) {
if (bs->drv->bdrv_attach_aio_context) {
bs->drv->bdrv_attach_aio_context(bs, new_context);
}
- if (bs->io_limits_enabled) {
+ if (bs->throttle_state) {
throttle_timers_attach_aio_context(&bs->throttle_timers, new_context);
}
g_free(dinfo);
}
+int blk_get_refcnt(BlockBackend *blk)
+{
+ return blk ? blk->refcnt : 0;
+}
+
/*
* Increment @blk's reference count.
* @blk must not be null.
}
}
+/*
+ * Disassociates the currently associated BlockDriverState from @blk.
+ */
+void blk_remove_bs(BlockBackend *blk)
+{
+ blk_update_root_state(blk);
+
+ blk->bs->blk = NULL;
+ bdrv_unref(blk->bs);
+ blk->bs = NULL;
+}
+
/*
* Associates a new BlockDriverState with @blk.
*/
void blk_dev_change_media_cb(BlockBackend *blk, bool load)
{
if (blk->dev_ops && blk->dev_ops->change_media_cb) {
- bool tray_was_closed = !blk_dev_is_tray_open(blk);
+ bool tray_was_open, tray_is_open;
+ tray_was_open = blk_dev_is_tray_open(blk);
blk->dev_ops->change_media_cb(blk->dev_opaque, load);
- if (tray_was_closed) {
- /* tray open */
- qapi_event_send_device_tray_moved(blk_name(blk),
- true, &error_abort);
- }
- if (load) {
- /* tray close */
- qapi_event_send_device_tray_moved(blk_name(blk),
- false, &error_abort);
+ tray_is_open = blk_dev_is_tray_open(blk);
+
+ if (tray_was_open != tray_is_open) {
+ qapi_event_send_device_tray_moved(blk_name(blk), tray_is_open,
+ &error_abort);
}
}
}
}
}
+/*
+ * Applies the information in the root state to the given BlockDriverState. This
+ * does not include the flags which have to be specified for bdrv_open(), use
+ * blk_get_open_flags_from_root_state() to inquire them.
+ */
+void blk_apply_root_state(BlockBackend *blk, BlockDriverState *bs)
+{
+ bs->detect_zeroes = blk->root_state.detect_zeroes;
+ if (blk->root_state.throttle_group) {
+ bdrv_io_limits_enable(bs, blk->root_state.throttle_group);
+ }
+}
+
+/*
+ * Returns the flags to be used for bdrv_open() of a BlockDriverState which is
+ * supposed to inherit the root state.
+ */
+int blk_get_open_flags_from_root_state(BlockBackend *blk)
+{
+ int bs_flags;
+
+ bs_flags = blk->root_state.read_only ? 0 : BDRV_O_RDWR;
+ bs_flags |= blk->root_state.open_flags & ~BDRV_O_RDWR;
+
+ return bs_flags;
+}
+
BlockBackendRootState *blk_get_root_state(BlockBackend *blk)
{
return &blk->root_state;
orig_overlay_flags = bdrv_get_flags(overlay_bs);
/* convert base & overlay_bs to r/w, if necessary */
- if (!(orig_base_flags & BDRV_O_RDWR)) {
- reopen_queue = bdrv_reopen_queue(reopen_queue, base, NULL,
- orig_base_flags | BDRV_O_RDWR);
- }
if (!(orig_overlay_flags & BDRV_O_RDWR)) {
reopen_queue = bdrv_reopen_queue(reopen_queue, overlay_bs, NULL,
orig_overlay_flags | BDRV_O_RDWR);
}
+ if (!(orig_base_flags & BDRV_O_RDWR)) {
+ reopen_queue = bdrv_reopen_queue(reopen_queue, base, NULL,
+ orig_base_flags | BDRV_O_RDWR);
+ }
if (reopen_queue) {
bdrv_reopen_multiple(reopen_queue, &local_err);
if (local_err != NULL) {
int64_t sector_num, int nb_sectors, BdrvRequestFlags flags)
{
int ret;
- GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
+ GlusterAIOCB acb;
BDRVGlusterState *s = bs->opaque;
off_t size = nb_sectors * BDRV_SECTOR_SIZE;
off_t offset = sector_num * BDRV_SECTOR_SIZE;
- acb->size = size;
- acb->ret = 0;
- acb->coroutine = qemu_coroutine_self();
- acb->aio_context = bdrv_get_aio_context(bs);
+ acb.size = size;
+ acb.ret = 0;
+ acb.coroutine = qemu_coroutine_self();
+ acb.aio_context = bdrv_get_aio_context(bs);
- ret = glfs_zerofill_async(s->fd, offset, size, &gluster_finish_aiocb, acb);
+ ret = glfs_zerofill_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
if (ret < 0) {
- ret = -errno;
- goto out;
+ return -errno;
}
qemu_coroutine_yield();
- ret = acb->ret;
-
-out:
- g_slice_free(GlusterAIOCB, acb);
- return ret;
+ return acb.ret;
}
static inline bool gluster_supports_zerofill(void)
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov, int write)
{
int ret;
- GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
+ GlusterAIOCB acb;
BDRVGlusterState *s = bs->opaque;
size_t size = nb_sectors * BDRV_SECTOR_SIZE;
off_t offset = sector_num * BDRV_SECTOR_SIZE;
- acb->size = size;
- acb->ret = 0;
- acb->coroutine = qemu_coroutine_self();
- acb->aio_context = bdrv_get_aio_context(bs);
+ acb.size = size;
+ acb.ret = 0;
+ acb.coroutine = qemu_coroutine_self();
+ acb.aio_context = bdrv_get_aio_context(bs);
if (write) {
ret = glfs_pwritev_async(s->fd, qiov->iov, qiov->niov, offset, 0,
- &gluster_finish_aiocb, acb);
+ gluster_finish_aiocb, &acb);
} else {
ret = glfs_preadv_async(s->fd, qiov->iov, qiov->niov, offset, 0,
- &gluster_finish_aiocb, acb);
+ gluster_finish_aiocb, &acb);
}
if (ret < 0) {
- ret = -errno;
- goto out;
+ return -errno;
}
qemu_coroutine_yield();
- ret = acb->ret;
-
-out:
- g_slice_free(GlusterAIOCB, acb);
- return ret;
+ return acb.ret;
}
static int qemu_gluster_truncate(BlockDriverState *bs, int64_t offset)
static coroutine_fn int qemu_gluster_co_flush_to_disk(BlockDriverState *bs)
{
int ret;
- GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
+ GlusterAIOCB acb;
BDRVGlusterState *s = bs->opaque;
- acb->size = 0;
- acb->ret = 0;
- acb->coroutine = qemu_coroutine_self();
- acb->aio_context = bdrv_get_aio_context(bs);
+ acb.size = 0;
+ acb.ret = 0;
+ acb.coroutine = qemu_coroutine_self();
+ acb.aio_context = bdrv_get_aio_context(bs);
- ret = glfs_fsync_async(s->fd, &gluster_finish_aiocb, acb);
+ ret = glfs_fsync_async(s->fd, gluster_finish_aiocb, &acb);
if (ret < 0) {
- ret = -errno;
- goto out;
+ return -errno;
}
qemu_coroutine_yield();
- ret = acb->ret;
-
-out:
- g_slice_free(GlusterAIOCB, acb);
- return ret;
+ return acb.ret;
}
#ifdef CONFIG_GLUSTERFS_DISCARD
int64_t sector_num, int nb_sectors)
{
int ret;
- GlusterAIOCB *acb = g_slice_new(GlusterAIOCB);
+ GlusterAIOCB acb;
BDRVGlusterState *s = bs->opaque;
size_t size = nb_sectors * BDRV_SECTOR_SIZE;
off_t offset = sector_num * BDRV_SECTOR_SIZE;
- acb->size = 0;
- acb->ret = 0;
- acb->coroutine = qemu_coroutine_self();
- acb->aio_context = bdrv_get_aio_context(bs);
+ acb.size = 0;
+ acb.ret = 0;
+ acb.coroutine = qemu_coroutine_self();
+ acb.aio_context = bdrv_get_aio_context(bs);
- ret = glfs_discard_async(s->fd, offset, size, &gluster_finish_aiocb, acb);
+ ret = glfs_discard_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
if (ret < 0) {
- ret = -errno;
- goto out;
+ return -errno;
}
qemu_coroutine_yield();
- ret = acb->ret;
-
-out:
- g_slice_free(GlusterAIOCB, acb);
- return ret;
+ return acb.ret;
}
#endif
aio_context_release(replace_aio_context);
}
g_free(s->replaces);
+ bdrv_op_unblock_all(s->target, s->common.blocker);
bdrv_unref(s->target);
block_job_completed(&s->common, data->ret);
g_free(data);
block_job_release(bs);
return;
}
+
+ bdrv_op_block_all(s->target, s->common.blocker);
+
bdrv_set_enable_write_cache(s->target, true);
if (s->target->blk) {
blk_set_on_error(s->target->blk, on_target_error, on_target_error);
info->backing_file_depth = bdrv_get_backing_file_depth(bs);
info->detect_zeroes = bs->detect_zeroes;
- if (bs->io_limits_enabled) {
+ if (bs->throttle_state) {
ThrottleConfig cfg;
throttle_group_get_config(bs, &cfg);
if (!offset)
return 0;
- assert(qcow2_get_cluster_type(first_entry) != QCOW2_CLUSTER_COMPRESSED);
+ assert(qcow2_get_cluster_type(first_entry) == QCOW2_CLUSTER_NORMAL);
for (i = 0; i < nb_clusters; i++) {
uint64_t l2_entry = be64_to_cpu(l2_table[i]) & mask;
return i;
}
-static int count_contiguous_free_clusters(int nb_clusters, uint64_t *l2_table)
+static int count_contiguous_clusters_by_type(int nb_clusters,
+ uint64_t *l2_table,
+ int wanted_type)
{
int i;
for (i = 0; i < nb_clusters; i++) {
int type = qcow2_get_cluster_type(be64_to_cpu(l2_table[i]));
- if (type != QCOW2_CLUSTER_UNALLOCATED) {
+ if (type != wanted_type) {
break;
}
}
ret = -EIO;
goto fail;
}
- c = count_contiguous_clusters(nb_clusters, s->cluster_size,
- &l2_table[l2_index], QCOW_OFLAG_ZERO);
+ c = count_contiguous_clusters_by_type(nb_clusters, &l2_table[l2_index],
+ QCOW2_CLUSTER_ZERO);
*cluster_offset = 0;
break;
case QCOW2_CLUSTER_UNALLOCATED:
/* how many empty clusters ? */
- c = count_contiguous_free_clusters(nb_clusters, &l2_table[l2_index]);
+ c = count_contiguous_clusters_by_type(nb_clusters, &l2_table[l2_index],
+ QCOW2_CLUSTER_UNALLOCATED);
*cluster_offset = 0;
break;
case QCOW2_CLUSTER_NORMAL:
}
/* Hook up the new refcount table in the qcow2 header */
- uint8_t data[12];
- cpu_to_be64w((uint64_t*)data, table_offset);
- cpu_to_be32w((uint32_t*)(data + 8), table_clusters);
+ struct QEMU_PACKED {
+ uint64_t d64;
+ uint32_t d32;
+ } data;
+ cpu_to_be64w(&data.d64, table_offset);
+ cpu_to_be32w(&data.d32, table_clusters);
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_SWITCH_TABLE);
ret = bdrv_pwrite_sync(bs->file->bs,
offsetof(QCowHeader, refcount_table_offset),
- data, sizeof(data));
+ &data, sizeof(data));
if (ret < 0) {
goto fail_table;
}
* list, destroying the timers and setting the throttle_state pointer
* to NULL.
*
+ * The BlockDriverState must not have pending throttled requests, so
+ * the caller has to drain them first.
+ *
* The group will be destroyed if it's empty after this operation.
*
* @bs: the BlockDriverState to remove
ThrottleGroup *tg = container_of(bs->throttle_state, ThrottleGroup, ts);
int i;
+ assert(bs->pending_reqs[0] == 0 && bs->pending_reqs[1] == 0);
+ assert(qemu_co_queue_empty(&bs->throttled_reqs[0]));
+ assert(qemu_co_queue_empty(&bs->throttled_reqs[1]));
+
qemu_mutex_lock(&tg->lock);
for (i = 0; i < 2; i++) {
if (tg->tokens[i] == bs) {
bool has_format, const char *format,
bool has_mode, NewImageMode mode, Error **errp)
{
- BlockdevSnapshot snapshot = {
+ BlockdevSnapshotSync snapshot = {
.has_device = has_device,
.device = (char *) device,
.has_node_name = has_node_name,
&snapshot, errp);
}
+void qmp_blockdev_snapshot(const char *node, const char *overlay,
+ Error **errp)
+{
+ BlockdevSnapshot snapshot_data = {
+ .node = (char *) node,
+ .overlay = (char *) overlay
+ };
+
+ blockdev_do_action(TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT,
+ &snapshot_data, errp);
+}
+
void qmp_blockdev_snapshot_internal_sync(const char *device,
const char *name,
Error **errp)
static void external_snapshot_prepare(BlkTransactionState *common,
Error **errp)
{
- int flags, ret;
- QDict *options;
+ int flags = 0, ret;
+ QDict *options = NULL;
Error *local_err = NULL;
- bool has_device = false;
+ /* Device and node name of the image to generate the snapshot from */
const char *device;
- bool has_node_name = false;
const char *node_name;
- bool has_snapshot_node_name = false;
- const char *snapshot_node_name;
+ /* Reference to the new image (for 'blockdev-snapshot') */
+ const char *snapshot_ref;
+ /* File name of the new image (for 'blockdev-snapshot-sync') */
const char *new_image_file;
- const char *format = "qcow2";
- enum NewImageMode mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
ExternalSnapshotState *state =
DO_UPCAST(ExternalSnapshotState, common, common);
TransactionAction *action = common->action;
- /* get parameters */
- g_assert(action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC);
-
- has_device = action->u.blockdev_snapshot_sync->has_device;
- device = action->u.blockdev_snapshot_sync->device;
- has_node_name = action->u.blockdev_snapshot_sync->has_node_name;
- node_name = action->u.blockdev_snapshot_sync->node_name;
- has_snapshot_node_name =
- action->u.blockdev_snapshot_sync->has_snapshot_node_name;
- snapshot_node_name = action->u.blockdev_snapshot_sync->snapshot_node_name;
-
- new_image_file = action->u.blockdev_snapshot_sync->snapshot_file;
- if (action->u.blockdev_snapshot_sync->has_format) {
- format = action->u.blockdev_snapshot_sync->format;
- }
- if (action->u.blockdev_snapshot_sync->has_mode) {
- mode = action->u.blockdev_snapshot_sync->mode;
+ /* 'blockdev-snapshot' and 'blockdev-snapshot-sync' have similar
+ * purpose but a different set of parameters */
+ switch (action->type) {
+ case TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT:
+ {
+ BlockdevSnapshot *s = action->u.blockdev_snapshot;
+ device = s->node;
+ node_name = s->node;
+ new_image_file = NULL;
+ snapshot_ref = s->overlay;
+ }
+ break;
+ case TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC:
+ {
+ BlockdevSnapshotSync *s = action->u.blockdev_snapshot_sync;
+ device = s->has_device ? s->device : NULL;
+ node_name = s->has_node_name ? s->node_name : NULL;
+ new_image_file = s->snapshot_file;
+ snapshot_ref = NULL;
+ }
+ break;
+ default:
+ g_assert_not_reached();
}
/* start processing */
- state->old_bs = bdrv_lookup_bs(has_device ? device : NULL,
- has_node_name ? node_name : NULL,
- &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- return;
- }
-
- if (has_node_name && !has_snapshot_node_name) {
- error_setg(errp, "New snapshot node name missing");
- return;
- }
-
- if (has_snapshot_node_name && bdrv_find_node(snapshot_node_name)) {
- error_setg(errp, "New snapshot node name already existing");
+ state->old_bs = bdrv_lookup_bs(device, node_name, errp);
+ if (!state->old_bs) {
return;
}
return;
}
- flags = state->old_bs->open_flags;
+ if (action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC) {
+ BlockdevSnapshotSync *s = action->u.blockdev_snapshot_sync;
+ const char *format = s->has_format ? s->format : "qcow2";
+ enum NewImageMode mode;
+ const char *snapshot_node_name =
+ s->has_snapshot_node_name ? s->snapshot_node_name : NULL;
- /* create new image w/backing file */
- if (mode != NEW_IMAGE_MODE_EXISTING) {
- bdrv_img_create(new_image_file, format,
- state->old_bs->filename,
- state->old_bs->drv->format_name,
- NULL, -1, flags, &local_err, false);
- if (local_err) {
- error_propagate(errp, local_err);
+ if (node_name && !snapshot_node_name) {
+ error_setg(errp, "New snapshot node name missing");
return;
}
- }
- options = qdict_new();
- if (has_snapshot_node_name) {
- qdict_put(options, "node-name",
- qstring_from_str(snapshot_node_name));
+ if (snapshot_node_name &&
+ bdrv_lookup_bs(snapshot_node_name, snapshot_node_name, NULL)) {
+ error_setg(errp, "New snapshot node name already in use");
+ return;
+ }
+
+ flags = state->old_bs->open_flags;
+
+ /* create new image w/backing file */
+ mode = s->has_mode ? s->mode : NEW_IMAGE_MODE_ABSOLUTE_PATHS;
+ if (mode != NEW_IMAGE_MODE_EXISTING) {
+ bdrv_img_create(new_image_file, format,
+ state->old_bs->filename,
+ state->old_bs->drv->format_name,
+ NULL, -1, flags, &local_err, false);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ }
+
+ options = qdict_new();
+ if (s->has_snapshot_node_name) {
+ qdict_put(options, "node-name",
+ qstring_from_str(snapshot_node_name));
+ }
+ qdict_put(options, "driver", qstring_from_str(format));
+
+ flags |= BDRV_O_NO_BACKING;
}
- qdict_put(options, "driver", qstring_from_str(format));
- /* TODO Inherit bs->options or only take explicit options with an
- * extended QMP command? */
assert(state->new_bs == NULL);
- ret = bdrv_open(&state->new_bs, new_image_file, NULL, options,
- flags | BDRV_O_NO_BACKING, &local_err);
+ ret = bdrv_open(&state->new_bs, new_image_file, snapshot_ref, options,
+ flags, errp);
/* We will manually add the backing_hd field to the bs later */
if (ret != 0) {
- error_propagate(errp, local_err);
+ return;
+ }
+
+ if (state->new_bs->blk != NULL) {
+ error_setg(errp, "The snapshot is already in use by %s",
+ blk_name(state->new_bs->blk));
+ return;
+ }
+
+ if (bdrv_op_is_blocked(state->new_bs, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT,
+ errp)) {
+ return;
+ }
+
+ if (state->new_bs->backing != NULL) {
+ error_setg(errp, "The snapshot already has a backing image");
+ return;
+ }
+
+ if (!state->new_bs->drv->supports_backing) {
+ error_setg(errp, "The snapshot does not support backing images");
}
}
}
static const BdrvActionOps actions[] = {
+ [TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT] = {
+ .instance_size = sizeof(ExternalSnapshotState),
+ .prepare = external_snapshot_prepare,
+ .commit = external_snapshot_commit,
+ .abort = external_snapshot_abort,
+ },
[TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC] = {
.instance_size = sizeof(ExternalSnapshotState),
.prepare = external_snapshot_prepare,
}
}
+void qmp_eject(const char *device, bool has_force, bool force, Error **errp)
+{
+ Error *local_err = NULL;
+
+ qmp_blockdev_open_tray(device, has_force, force, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ qmp_blockdev_remove_medium(device, errp);
+}
-static void eject_device(BlockBackend *blk, int force, Error **errp)
+void qmp_block_passwd(bool has_device, const char *device,
+ bool has_node_name, const char *node_name,
+ const char *password, Error **errp)
{
- BlockDriverState *bs = blk_bs(blk);
+ Error *local_err = NULL;
+ BlockDriverState *bs;
AioContext *aio_context;
- if (!bs) {
- /* No medium inserted, so there is nothing to do */
+ bs = bdrv_lookup_bs(has_device ? device : NULL,
+ has_node_name ? node_name : NULL,
+ &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
return;
}
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
- if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_EJECT, errp)) {
- goto out;
+ bdrv_add_key(bs, password, errp);
+
+ aio_context_release(aio_context);
+}
+
+void qmp_blockdev_open_tray(const char *device, bool has_force, bool force,
+ Error **errp)
+{
+ BlockBackend *blk;
+ bool locked;
+
+ if (!has_force) {
+ force = false;
}
+
+ blk = blk_by_name(device);
+ if (!blk) {
+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
+ "Device '%s' not found", device);
+ return;
+ }
+
if (!blk_dev_has_removable_media(blk)) {
- error_setg(errp, "Device '%s' is not removable",
- bdrv_get_device_name(bs));
- goto out;
+ error_setg(errp, "Device '%s' is not removable", device);
+ return;
}
- if (blk_dev_is_medium_locked(blk) && !blk_dev_is_tray_open(blk)) {
- blk_dev_eject_request(blk, force);
- if (!force) {
- error_setg(errp, "Device '%s' is locked",
- bdrv_get_device_name(bs));
- goto out;
- }
+ if (blk_dev_is_tray_open(blk)) {
+ return;
}
- bdrv_close(bs);
+ locked = blk_dev_is_medium_locked(blk);
+ if (locked) {
+ blk_dev_eject_request(blk, force);
+ }
-out:
- aio_context_release(aio_context);
+ if (!locked || force) {
+ blk_dev_change_media_cb(blk, false);
+ }
}
-void qmp_eject(const char *device, bool has_force, bool force, Error **errp)
+void qmp_blockdev_close_tray(const char *device, Error **errp)
{
BlockBackend *blk;
return;
}
- eject_device(blk, force, errp);
+ if (!blk_dev_has_removable_media(blk)) {
+ error_setg(errp, "Device '%s' is not removable", device);
+ return;
+ }
+
+ if (!blk_dev_is_tray_open(blk)) {
+ return;
+ }
+
+ blk_dev_change_media_cb(blk, true);
}
-void qmp_block_passwd(bool has_device, const char *device,
- bool has_node_name, const char *node_name,
- const char *password, Error **errp)
+void qmp_blockdev_remove_medium(const char *device, Error **errp)
{
- Error *local_err = NULL;
+ BlockBackend *blk;
BlockDriverState *bs;
AioContext *aio_context;
+ bool has_device;
- bs = bdrv_lookup_bs(has_device ? device : NULL,
- has_node_name ? node_name : NULL,
- &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
+ blk = blk_by_name(device);
+ if (!blk) {
+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
+ "Device '%s' not found", device);
+ return;
+ }
+
+ /* For BBs without a device, we can exchange the BDS tree at will */
+ has_device = blk_get_attached_dev(blk);
+
+ if (has_device && !blk_dev_has_removable_media(blk)) {
+ error_setg(errp, "Device '%s' is not removable", device);
+ return;
+ }
+
+ if (has_device && !blk_dev_is_tray_open(blk)) {
+ error_setg(errp, "Tray of device '%s' is not open", device);
+ return;
+ }
+
+ bs = blk_bs(blk);
+ if (!bs) {
return;
}
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
- bdrv_add_key(bs, password, errp);
+ if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_EJECT, errp)) {
+ goto out;
+ }
+
+ /* This follows the convention established by bdrv_make_anon() */
+ if (bs->device_list.tqe_prev) {
+ QTAILQ_REMOVE(&bdrv_states, bs, device_list);
+ bs->device_list.tqe_prev = NULL;
+ }
+ blk_remove_bs(blk);
+
+out:
aio_context_release(aio_context);
}
-/* Assumes AioContext is held */
-static void qmp_bdrv_open_encrypted(BlockDriverState **pbs,
- const char *filename,
- int bdrv_flags, const char *format,
- const char *password, Error **errp)
+static void qmp_blockdev_insert_anon_medium(const char *device,
+ BlockDriverState *bs, Error **errp)
{
- Error *local_err = NULL;
- QDict *options = NULL;
- int ret;
+ BlockBackend *blk;
+ bool has_device;
- if (format) {
- options = qdict_new();
- qdict_put(options, "driver", qstring_from_str(format));
+ blk = blk_by_name(device);
+ if (!blk) {
+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
+ "Device '%s' not found", device);
+ return;
}
- ret = bdrv_open(pbs, filename, NULL, options, bdrv_flags, &local_err);
- if (ret < 0) {
- error_propagate(errp, local_err);
+ /* For BBs without a device, we can exchange the BDS tree at will */
+ has_device = blk_get_attached_dev(blk);
+
+ if (has_device && !blk_dev_has_removable_media(blk)) {
+ error_setg(errp, "Device '%s' is not removable", device);
return;
}
- bdrv_add_key(*pbs, password, errp);
+ if (has_device && !blk_dev_is_tray_open(blk)) {
+ error_setg(errp, "Tray of device '%s' is not open", device);
+ return;
+ }
+
+ if (blk_bs(blk)) {
+ error_setg(errp, "There already is a medium in device '%s'", device);
+ return;
+ }
+
+ blk_insert_bs(blk, bs);
+
+ QTAILQ_INSERT_TAIL(&bdrv_states, bs, device_list);
}
-void qmp_change_blockdev(const char *device, const char *filename,
- const char *format, Error **errp)
+void qmp_blockdev_insert_medium(const char *device, const char *node_name,
+ Error **errp)
{
- BlockBackend *blk;
BlockDriverState *bs;
- AioContext *aio_context;
- int bdrv_flags;
- bool new_bs;
+
+ bs = bdrv_find_node(node_name);
+ if (!bs) {
+ error_setg(errp, "Node '%s' not found", node_name);
+ return;
+ }
+
+ if (bs->blk) {
+ error_setg(errp, "Node '%s' is already in use by '%s'", node_name,
+ blk_name(bs->blk));
+ return;
+ }
+
+ qmp_blockdev_insert_anon_medium(device, bs, errp);
+}
+
+void qmp_blockdev_change_medium(const char *device, const char *filename,
+ bool has_format, const char *format,
+ bool has_read_only,
+ BlockdevChangeReadOnlyMode read_only,
+ Error **errp)
+{
+ BlockBackend *blk;
+ BlockDriverState *medium_bs = NULL;
+ int bdrv_flags, ret;
+ QDict *options = NULL;
Error *err = NULL;
blk = blk_by_name(device);
if (!blk) {
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
"Device '%s' not found", device);
- return;
+ goto fail;
}
- bs = blk_bs(blk);
- new_bs = !bs;
- aio_context = blk_get_aio_context(blk);
- aio_context_acquire(aio_context);
+ if (blk_bs(blk)) {
+ blk_update_root_state(blk);
+ }
+
+ bdrv_flags = blk_get_open_flags_from_root_state(blk);
+
+ if (!has_read_only) {
+ read_only = BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN;
+ }
+
+ switch (read_only) {
+ case BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN:
+ break;
+
+ case BLOCKDEV_CHANGE_READ_ONLY_MODE_READ_ONLY:
+ bdrv_flags &= ~BDRV_O_RDWR;
+ break;
+
+ case BLOCKDEV_CHANGE_READ_ONLY_MODE_READ_WRITE:
+ bdrv_flags |= BDRV_O_RDWR;
+ break;
- eject_device(blk, 0, &err);
+ default:
+ abort();
+ }
+
+ if (has_format) {
+ options = qdict_new();
+ qdict_put(options, "driver", qstring_from_str(format));
+ }
+
+ assert(!medium_bs);
+ ret = bdrv_open(&medium_bs, filename, NULL, options, bdrv_flags, errp);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ blk_apply_root_state(blk, medium_bs);
+
+ bdrv_add_key(medium_bs, NULL, &err);
if (err) {
error_propagate(errp, err);
- goto out;
+ goto fail;
}
- bdrv_flags = blk_is_read_only(blk) ? 0 : BDRV_O_RDWR;
- bdrv_flags |= blk_get_root_state(blk)->open_flags & ~BDRV_O_RDWR;
+ qmp_blockdev_open_tray(device, false, false, &err);
+ if (err) {
+ error_propagate(errp, err);
+ goto fail;
+ }
- qmp_bdrv_open_encrypted(&bs, filename, bdrv_flags, format, NULL, &err);
+ qmp_blockdev_remove_medium(device, &err);
if (err) {
error_propagate(errp, err);
- goto out;
+ goto fail;
}
- if (new_bs) {
- blk_insert_bs(blk, bs);
- /* Has been sent automatically by bdrv_open() if blk_bs(blk) was not
- * NULL */
- blk_dev_change_media_cb(blk, true);
+ qmp_blockdev_insert_anon_medium(device, medium_bs, &err);
+ if (err) {
+ error_propagate(errp, err);
+ goto fail;
}
-out:
- aio_context_release(aio_context);
+ qmp_blockdev_close_tray(device, errp);
+
+fail:
+ /* If the medium has been inserted, the device has its own reference, so
+ * ours must be relinquished; and if it has not been inserted successfully,
+ * the reference must be relinquished anyway */
+ bdrv_unref(medium_bs);
}
/* throttling disk I/O limits */
if (throttle_enabled(&cfg)) {
/* Enable I/O limits if they're not enabled yet, otherwise
* just update the throttling group. */
- if (!bs->io_limits_enabled) {
+ if (!bs->throttle_state) {
bdrv_io_limits_enable(bs, has_group ? group : device);
} else if (has_group) {
bdrv_io_limits_update_group(bs, group);
}
/* Set the new throttling configuration */
bdrv_set_io_limits(bs, &cfg);
- } else if (bs->io_limits_enabled) {
+ } else if (bs->throttle_state) {
/* If all throttling settings are set to 0, disable I/O limits */
bdrv_io_limits_disable(bs);
}
qmp_output_visitor_cleanup(ov);
}
+void qmp_x_blockdev_del(bool has_id, const char *id,
+ bool has_node_name, const char *node_name, Error **errp)
+{
+ AioContext *aio_context;
+ BlockBackend *blk;
+ BlockDriverState *bs;
+
+ if (has_id && has_node_name) {
+ error_setg(errp, "Only one of id and node-name must be specified");
+ return;
+ } else if (!has_id && !has_node_name) {
+ error_setg(errp, "No block device specified");
+ return;
+ }
+
+ if (has_id) {
+ blk = blk_by_name(id);
+ if (!blk) {
+ error_setg(errp, "Cannot find block backend %s", id);
+ return;
+ }
+ if (blk_get_refcnt(blk) > 1) {
+ error_setg(errp, "Block backend %s is in use", id);
+ return;
+ }
+ bs = blk_bs(blk);
+ aio_context = blk_get_aio_context(blk);
+ } else {
+ bs = bdrv_find_node(node_name);
+ if (!bs) {
+ error_setg(errp, "Cannot find node %s", node_name);
+ return;
+ }
+ blk = bs->blk;
+ if (blk) {
+ error_setg(errp, "Node %s is in use by %s",
+ node_name, blk_name(blk));
+ return;
+ }
+ aio_context = bdrv_get_aio_context(bs);
+ }
+
+ aio_context_acquire(aio_context);
+
+ if (bs) {
+ if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_DRIVE_DEL, errp)) {
+ goto out;
+ }
+
+ if (bs->refcnt > 1 || !QLIST_EMPTY(&bs->parents)) {
+ error_setg(errp, "Block device %s is in use",
+ bdrv_get_device_or_node_name(bs));
+ goto out;
+ }
+ }
+
+ if (blk) {
+ blk_unref(blk);
+ } else {
+ bdrv_unref(bs);
+ }
+
+out:
+ aio_context_release(aio_context);
+}
+
BlockJobInfoList *qmp_query_block_jobs(Error **errp)
{
BlockJobInfoList *head = NULL, **p_next = &head;
=== Errors ===
QMP commands should use the error interface exported by the error.h header
-file. Basically, errors are set by calling the error_set() function.
+file. Basically, most errors are set by calling the error_setg() function.
Let's say we don't accept the string "message" to contain the word "love". If
it does contain it, we want the "hello-world" command to return an error:
{
if (has_message) {
if (strstr(message, "love")) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "the word 'love' is not allowed");
+ error_setg(errp, "the word 'love' is not allowed");
return;
}
printf("%s\n", message);
}
}
-The first argument to the error_set() function is the Error pointer to pointer,
-which is passed to all QMP functions. The second argument is a ErrorClass
-value, which should be ERROR_CLASS_GENERIC_ERROR most of the time (more
-details about error classes are given below). The third argument is a human
+The first argument to the error_setg() function is the Error pointer
+to pointer, which is passed to all QMP functions. The next argument is a human
description of the error, this is a free-form printf-like string.
Let's test the example above. Build qemu, run it as defined in the "Testing"
}
}
-As a general rule, all QMP errors should use ERROR_CLASS_GENERIC_ERROR. There
-are two exceptions to this rule:
+As a general rule, all QMP errors should use ERROR_CLASS_GENERIC_ERROR
+(done by default when using error_setg()). There are two exceptions to
+this rule:
1. A non-generic ErrorClass value exists* for the failure you want to report
(eg. DeviceNotFound)
want to report, hence you have to add a new ErrorClass value so that they
can check for it
-If the failure you want to report doesn't fall in one of the two cases above,
-just report ERROR_CLASS_GENERIC_ERROR.
+If the failure you want to report falls into one of the two cases above,
+use error_set() with a second argument of an ErrorClass value.
* All existing ErrorClass values are defined in the qapi-schema.json file
{
.name = "change",
- .args_type = "device:B,target:F,arg:s?",
- .params = "device filename [format]",
+ .args_type = "device:B,target:F,arg:s?,read-only-mode:s?",
+ .params = "device filename [format [read-only-mode]]",
.help = "change a removable medium, optional format",
.mhandler.cmd = hmp_change,
},
Change the configuration of a device.
@table @option
-@item change @var{diskdevice} @var{filename} [@var{format}]
+@item change @var{diskdevice} @var{filename} [@var{format} [@var{read-only-mode}]]
Change the medium for a removable disk device to point to @var{filename}. eg
@example
@var{format} is optional.
+@var{read-only-mode} may be used to change the read-only status of the device.
+It accepts the following values:
+
+@table @var
+@item retain
+Retains the current status; this is the default.
+
+@item read-only
+Makes the device read-only.
+
+@item read-write
+Makes the device writable.
+@end table
+
@item change vnc @var{display},@var{options}
Change the configuration of the VNC server. The valid syntax for @var{display}
and @var{options} are described at @ref{sec_invocation}. eg
#include "qapi/opts-visitor.h"
#include "qapi/qmp/qerror.h"
#include "qapi/string-output-visitor.h"
+#include "qapi/util.h"
#include "qapi-visit.h"
#include "ui/console.h"
#include "block/qapi.h"
const char *device = qdict_get_str(qdict, "device");
const char *target = qdict_get_str(qdict, "target");
const char *arg = qdict_get_try_str(qdict, "arg");
+ const char *read_only = qdict_get_try_str(qdict, "read-only-mode");
+ BlockdevChangeReadOnlyMode read_only_mode = 0;
Error *err = NULL;
- if (strcmp(device, "vnc") == 0 &&
- (strcmp(target, "passwd") == 0 ||
- strcmp(target, "password") == 0)) {
- if (!arg) {
- monitor_read_password(mon, hmp_change_read_arg, NULL);
+ if (strcmp(device, "vnc") == 0) {
+ if (read_only) {
+ monitor_printf(mon,
+ "Parameter 'read-only-mode' is invalid for VNC\n");
return;
}
- }
+ if (strcmp(target, "passwd") == 0 ||
+ strcmp(target, "password") == 0) {
+ if (!arg) {
+ monitor_read_password(mon, hmp_change_read_arg, NULL);
+ return;
+ }
+ }
+ qmp_change("vnc", target, !!arg, arg, &err);
+ } else {
+ if (read_only) {
+ read_only_mode =
+ qapi_enum_parse(BlockdevChangeReadOnlyMode_lookup,
+ read_only, BLOCKDEV_CHANGE_READ_ONLY_MODE_MAX,
+ BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN, &err);
+ if (err) {
+ hmp_handle_error(mon, &err);
+ return;
+ }
+ }
- qmp_change(device, target, !!arg, arg, &err);
- if (err &&
- error_get_class(err) == ERROR_CLASS_DEVICE_ENCRYPTED) {
- error_free(err);
- monitor_read_block_device_key(mon, device, NULL, NULL);
- return;
+ qmp_blockdev_change_medium(device, target, !!arg, arg,
+ !!read_only, read_only_mode, &err);
+ if (err &&
+ error_get_class(err) == ERROR_CLASS_DEVICE_ENCRYPTED) {
+ error_free(err);
+ monitor_read_block_device_key(mon, device, NULL, NULL);
+ return;
+ }
}
+
hmp_handle_error(mon, &err);
}
qdev_walk_children(dev, NULL, NULL, qdev_reset_one, qbus_reset_one, NULL);
}
+void qdev_reset_all_fn(void *opaque)
+{
+ qdev_reset_all(DEVICE(opaque));
+}
+
void qbus_reset_all(BusState *bus)
{
qbus_walk_children(bus, NULL, NULL, qdev_reset_one, qbus_reset_one, NULL);
return;
}
if (value > (1ULL << 32)) {
- error_set(&error, ERROR_CLASS_GENERIC_ERROR,
- "Machine option 'max-ram-below-4g=%"PRIu64
- "' expects size less than or equal to 4G", value);
+ error_setg(&error,
+ "Machine option 'max-ram-below-4g=%"PRIu64
+ "' expects size less than or equal to 4G", value);
error_propagate(errp, error);
return;
}
r = rocker_find(name);
if (!r) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "rocker %s not found", name);
+ error_setg(errp, "rocker %s not found", name);
return NULL;
}
r = rocker_find(name);
if (!r) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "rocker %s not found", name);
+ error_setg(errp, "rocker %s not found", name);
return NULL;
}
r = rocker_find(name);
if (!r) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "rocker %s not found", name);
+ error_setg(errp, "rocker %s not found", name);
return NULL;
}
w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
if (!w) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "rocker %s doesn't have OF-DPA world", name);
+ error_setg(errp, "rocker %s doesn't have OF-DPA world", name);
return NULL;
}
r = rocker_find(name);
if (!r) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "rocker %s not found", name);
+ error_setg(errp, "rocker %s not found", name);
return NULL;
}
w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
if (!w) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "rocker %s doesn't have OF-DPA world", name);
+ error_setg(errp, "rocker %s doesn't have OF-DPA world", name);
return NULL;
}
/* If a unit check is pending, copy sense data. */
if ((s->dstat & SCSW_DSTAT_UNIT_CHECK) &&
(p->chars & PMCW_CHARS_MASK_CSENSE)) {
+ int i;
+
irb.scsw.flags |= SCSW_FLAGS_MASK_ESWF | SCSW_FLAGS_MASK_ECTL;
+ /* Attention: sense_data is already BE! */
memcpy(irb.ecw, sch->sense_data, sizeof(sch->sense_data));
+ for (i = 0; i < ARRAY_SIZE(irb.ecw); i++) {
+ irb.ecw[i] = be32_to_cpu(irb.ecw[i]);
+ }
irb.esw[1] = 0x01000000 | (sizeof(sch->sense_data) << 8);
}
}
#include "cpu.h"
#include "elf.h"
#include "hw/loader.h"
-#include "hw/sysbus.h"
#include "hw/s390x/virtio-ccw.h"
#include "hw/s390x/css.h"
#include "ipl.h"
#define ZIPL_IMAGE_START 0x009000UL
#define IPL_PSW_MASK (PSW_MASK_32 | PSW_MASK_64)
-#define TYPE_S390_IPL "s390-ipl"
-#define S390_IPL(obj) \
- OBJECT_CHECK(S390IPLState, (obj), TYPE_S390_IPL)
-#if 0
-#define S390_IPL_CLASS(klass) \
- OBJECT_CLASS_CHECK(S390IPLState, (klass), TYPE_S390_IPL)
-#define S390_IPL_GET_CLASS(obj) \
- OBJECT_GET_CLASS(S390IPLState, (obj), TYPE_S390_IPL)
-#endif
-
-typedef struct S390IPLClass {
- /*< private >*/
- SysBusDeviceClass parent_class;
- /*< public >*/
-
- void (*parent_reset) (SysBusDevice *dev);
-} S390IPLClass;
-
-typedef struct S390IPLState {
- /*< private >*/
- SysBusDevice parent_obj;
- uint64_t start_addr;
- uint64_t bios_start_addr;
- bool enforce_bios;
- IplParameterBlock iplb;
- bool iplb_valid;
- bool reipl_requested;
-
- /*< public >*/
- char *kernel;
- char *initrd;
- char *cmdline;
- char *firmware;
- uint8_t cssid;
- uint8_t ssid;
- uint16_t devno;
-} S390IPLState;
-
static const VMStateDescription vmstate_iplb = {
.name = "ipl/iplb",
.version_id = 0,
return srcaddr + dstaddr;
}
-static int s390_ipl_init(SysBusDevice *dev)
+static void s390_ipl_realize(DeviceState *dev, Error **errp)
{
S390IPLState *ipl = S390_IPL(dev);
uint64_t pentry = KERN_IMAGE_START;
int kernel_size;
+ Error *l_err = NULL;
int bios_size;
char *bios_filename;
bios_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
if (bios_filename == NULL) {
- hw_error("could not find stage1 bootloader\n");
+ error_setg(&l_err, "could not find stage1 bootloader\n");
+ goto error;
}
bios_size = load_elf(bios_filename, bios_translate_addr, &fwbase,
g_free(bios_filename);
if (bios_size == -1) {
- hw_error("could not load bootloader '%s'\n", bios_name);
+ error_setg(&l_err, "could not load bootloader '%s'\n", bios_name);
+ goto error;
}
/* default boot target is the bios */
kernel_size = load_image_targphys(ipl->kernel, 0, ram_size);
}
if (kernel_size < 0) {
- fprintf(stderr, "could not load kernel '%s'\n", ipl->kernel);
- return -1;
+ error_setg(&l_err, "could not load kernel '%s'\n", ipl->kernel);
+ goto error;
}
/*
* Is it a Linux kernel (starting at 0x10000)? If yes, we fill in the
initrd_size = load_image_targphys(ipl->initrd, initrd_offset,
ram_size - initrd_offset);
if (initrd_size == -1) {
- fprintf(stderr, "qemu: could not load initrd '%s'\n",
- ipl->initrd);
- exit(1);
+ error_setg(&l_err, "could not load initrd '%s'\n", ipl->initrd);
+ goto error;
}
/*
stq_p(rom_ptr(INITRD_PARM_SIZE), initrd_size);
}
}
- return 0;
+ qemu_register_reset(qdev_reset_all_fn, dev);
+error:
+ error_propagate(errp, l_err);
}
static Property s390_ipl_properties[] = {
static void s390_ipl_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
- SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
- k->init = s390_ipl_init;
+ dc->realize = s390_ipl_realize;
dc->props = s390_ipl_properties;
dc->reset = s390_ipl_reset;
dc->vmsd = &vmstate_ipl;
static const TypeInfo s390_ipl_info = {
.class_init = s390_ipl_class_init,
- .parent = TYPE_SYS_BUS_DEVICE,
- .name = "s390-ipl",
+ .parent = TYPE_DEVICE,
+ .name = TYPE_S390_IPL,
.instance_size = sizeof(S390IPLState),
};
#ifndef HW_S390_IPL_H
#define HW_S390_IPL_H
+#include "hw/qdev.h"
#include "cpu.h"
typedef struct IplParameterBlock {
IplParameterBlock *s390_ipl_get_iplb(void);
void s390_reipl_request(void);
+#define TYPE_S390_IPL "s390-ipl"
+#define S390_IPL(obj) OBJECT_CHECK(S390IPLState, (obj), TYPE_S390_IPL)
+
+struct S390IPLState {
+ /*< private >*/
+ DeviceState parent_obj;
+ uint64_t start_addr;
+ uint64_t bios_start_addr;
+ bool enforce_bios;
+ IplParameterBlock iplb;
+ bool iplb_valid;
+ bool reipl_requested;
+
+ /*< public >*/
+ char *kernel;
+ char *initrd;
+ char *cmdline;
+ char *firmware;
+ uint8_t cssid;
+ uint8_t ssid;
+ uint16_t devno;
+};
+typedef struct S390IPLState S390IPLState;
+
#endif
uint64_t pte;
uint32_t flags;
S390PCIBusDevice *pbdev = container_of(iommu, S390PCIBusDevice, mr);
- S390pciState *s = S390_PCI_HOST_BRIDGE(pci_device_root_bus(pbdev->pdev)
- ->qbus.parent);
+ S390pciState *s;
IOMMUTLBEntry ret = {
.target_as = &address_space_memory,
.iova = 0,
.perm = IOMMU_NONE,
};
+ if (!pbdev->configured || !pbdev->pdev) {
+ return ret;
+ }
+
DPRINTF("iommu trans addr 0x%" PRIx64 "\n", addr);
+ s = S390_PCI_HOST_BRIDGE(pci_device_root_bus(pbdev->pdev)->qbus.parent);
/* s390 does not have an APIC mapped to main storage so we use
* a separate AddressSpace only for msix notifications
*/
MemoryRegion *ram = g_new(MemoryRegion, 1);
/* allocate RAM for core */
- memory_region_init_ram(ram, NULL, "s390.ram", mem_size, &error_fatal);
- vmstate_register_ram_global(ram);
+ memory_region_allocate_system_memory(ram, NULL, "s390.ram", mem_size);
memory_region_add_subregion(sysmem, 0, ram);
/* Initialize storage key device */
#include "hw/boards.h"
#include "hw/loader.h"
#include "hw/virtio/virtio.h"
-#include "hw/sysbus.h"
#include "sysemu/kvm.h"
#include "exec/address-spaces.h"
const char *firmware,
bool enforce_bios)
{
- DeviceState *dev;
+ Object *new = object_new(TYPE_S390_IPL);
+ DeviceState *dev = DEVICE(new);
- dev = qdev_create(NULL, "s390-ipl");
if (kernel_filename) {
qdev_prop_set_string(dev, "kernel", kernel_filename);
}
qdev_prop_set_string(dev, "cmdline", kernel_cmdline);
qdev_prop_set_string(dev, "firmware", firmware);
qdev_prop_set_bit(dev, "enforce_bios", enforce_bios);
- object_property_add_child(qdev_get_machine(), "s390-ipl",
- OBJECT(dev), NULL);
+ object_property_add_child(qdev_get_machine(), TYPE_S390_IPL,
+ new, NULL);
+ object_unref(new);
qdev_init_nofail(dev);
}
hwaddr virtio_region_len;
hwaddr virtio_region_start;
+ error_printf("WARNING\n"
+ "The s390-virtio machine (non-ccw) is deprecated.\n"
+ "It will be removed in 2.6. Please use s390-ccw-virtio\n");
+
if (machine->ram_slots) {
error_report("Memory hotplug not supported by the selected machine.");
exit(EXIT_FAILURE);
NMIClass *nc = NMI_CLASS(oc);
mc->alias = "s390";
- mc->desc = "VirtIO based S390 machine";
+ mc->desc = "VirtIO based S390 machine (deprecated)";
mc->init = s390_init;
mc->reset = s390_machine_reset;
mc->block_default_type = IF_VIRTIO;
/* number of in-flight serialising requests */
unsigned int serialising_in_flight;
- /* I/O throttling */
+ /* I/O throttling.
+ * throttle_state tells us if this BDS has I/O limits configured.
+ * io_limits_enabled tells us if they are currently being
+ * enforced, but it can be temporarily set to false */
CoQueue throttled_reqs[2];
bool io_limits_enabled;
/* The following fields are protected by the ThrottleGroup lock.
extern BlockDriver bdrv_raw;
extern BlockDriver bdrv_qcow2;
+extern QTAILQ_HEAD(BdrvStates, BlockDriverState) bdrv_states;
+
/**
* bdrv_setup_io_funcs:
*
void *opaque);
void qdev_reset_all(DeviceState *dev);
+void qdev_reset_all_fn(void *opaque);
/**
* @qbus_reset_all:
BlockBackend *blk_new_open(const char *name, const char *filename,
const char *reference, QDict *options, int flags,
Error **errp);
+int blk_get_refcnt(BlockBackend *blk);
void blk_ref(BlockBackend *blk);
void blk_unref(BlockBackend *blk);
const char *blk_name(BlockBackend *blk);
BlockBackend *blk_next(BlockBackend *blk);
BlockDriverState *blk_bs(BlockBackend *blk);
+void blk_remove_bs(BlockBackend *blk);
void blk_insert_bs(BlockBackend *blk, BlockDriverState *bs);
void blk_hide_on_behalf_of_hmp_drive_del(BlockBackend *blk);
BlockAcctStats *blk_get_stats(BlockBackend *blk);
BlockBackendRootState *blk_get_root_state(BlockBackend *blk);
void blk_update_root_state(BlockBackend *blk);
+void blk_apply_root_state(BlockBackend *blk, BlockDriverState *bs);
+int blk_get_open_flags_from_root_state(BlockBackend *blk);
void *blk_aio_get(const AIOCBInfo *aiocb_info, BlockBackend *blk,
BlockCompletionFunc *cb, void *opaque);
/* device-hotplug */
-void qmp_change_blockdev(const char *device, const char *filename,
- const char *format, Error **errp);
void hmp_commit(Monitor *mon, const QDict *qdict);
void hmp_drive_del(Monitor *mon, const QDict *qdict);
#endif
zipl_run(prog_table_entry); /* no return */
}
+/***********************************************************************
+ * IPL El Torito ISO9660 image or DVD
+ */
+
+static bool is_iso_bc_entry_compatible(IsoBcSection *s)
+{
+ uint8_t *magic_sec = (uint8_t *)(sec + ISO_SECTOR_SIZE);
+
+ if (s->unused || !s->sector_count) {
+ return false;
+ }
+ read_iso_sector(bswap32(s->load_rba), magic_sec,
+ "Failed to read image sector 0");
+
+ /* Checking bytes 8 - 32 for S390 Linux magic */
+ return !_memcmp(magic_sec + 8, linux_s390_magic, 24);
+}
+
+/* Location of the current sector of the directory */
+static uint32_t sec_loc[ISO9660_MAX_DIR_DEPTH];
+/* Offset in the current sector of the directory */
+static uint32_t sec_offset[ISO9660_MAX_DIR_DEPTH];
+/* Remained directory space in bytes */
+static uint32_t dir_rem[ISO9660_MAX_DIR_DEPTH];
+
+static inline uint32_t iso_get_file_size(uint32_t load_rba)
+{
+ IsoVolDesc *vd = (IsoVolDesc *)sec;
+ IsoDirHdr *cur_record = &vd->vd.primary.rootdir;
+ uint8_t *temp = sec + ISO_SECTOR_SIZE;
+ int level = 0;
+
+ read_iso_sector(ISO_PRIMARY_VD_SECTOR, sec,
+ "Failed to read ISO primary descriptor");
+ sec_loc[0] = iso_733_to_u32(cur_record->ext_loc);
+ dir_rem[0] = 0;
+ sec_offset[0] = 0;
+
+ while (level >= 0) {
+ IPL_assert(sec_offset[level] <= ISO_SECTOR_SIZE,
+ "Directory tree structure violation");
+
+ cur_record = (IsoDirHdr *)(temp + sec_offset[level]);
+
+ if (sec_offset[level] == 0) {
+ read_iso_sector(sec_loc[level], temp,
+ "Failed to read ISO directory");
+ if (dir_rem[level] == 0) {
+ /* Skip self and parent records */
+ dir_rem[level] = iso_733_to_u32(cur_record->data_len) -
+ cur_record->dr_len;
+ sec_offset[level] += cur_record->dr_len;
+
+ cur_record = (IsoDirHdr *)(temp + sec_offset[level]);
+ dir_rem[level] -= cur_record->dr_len;
+ sec_offset[level] += cur_record->dr_len;
+ continue;
+ }
+ }
+
+ if (!cur_record->dr_len || sec_offset[level] == ISO_SECTOR_SIZE) {
+ /* Zero-padding and/or the end of current sector */
+ dir_rem[level] -= ISO_SECTOR_SIZE - sec_offset[level];
+ sec_offset[level] = 0;
+ sec_loc[level]++;
+ } else {
+ /* The directory record is valid */
+ if (load_rba == iso_733_to_u32(cur_record->ext_loc)) {
+ return iso_733_to_u32(cur_record->data_len);
+ }
+
+ dir_rem[level] -= cur_record->dr_len;
+ sec_offset[level] += cur_record->dr_len;
+
+ if (cur_record->file_flags & 0x2) {
+ /* Subdirectory */
+ if (level == ISO9660_MAX_DIR_DEPTH - 1) {
+ sclp_print("ISO-9660 directory depth limit exceeded\n");
+ } else {
+ level++;
+ sec_loc[level] = iso_733_to_u32(cur_record->ext_loc);
+ sec_offset[level] = 0;
+ dir_rem[level] = 0;
+ continue;
+ }
+ }
+ }
+
+ if (dir_rem[level] == 0) {
+ /* Nothing remaining */
+ level--;
+ read_iso_sector(sec_loc[level], temp,
+ "Failed to read ISO directory");
+ }
+ }
+
+ return 0;
+}
+
+static void load_iso_bc_entry(IsoBcSection *load)
+{
+ IsoBcSection s = *load;
+ /*
+ * According to spec, extent for each file
+ * is padded and ISO_SECTOR_SIZE bytes aligned
+ */
+ uint32_t blks_to_load = bswap16(s.sector_count) >> ET_SECTOR_SHIFT;
+ uint32_t real_size = iso_get_file_size(bswap32(s.load_rba));
+
+ if (real_size) {
+ /* Round up blocks to load */
+ blks_to_load = (real_size + ISO_SECTOR_SIZE - 1) / ISO_SECTOR_SIZE;
+ sclp_print("ISO boot image size verified\n");
+ } else {
+ sclp_print("ISO boot image size could not be verified\n");
+ }
+
+ read_iso_boot_image(bswap32(s.load_rba),
+ (void *)((uint64_t)bswap16(s.load_segment)),
+ blks_to_load);
+
+ /* Trying to get PSW at zero address */
+ if (*((uint64_t *)0) & IPL_PSW_MASK) {
+ jump_to_IPL_code((*((uint64_t *)0)) & 0x7fffffff);
+ }
+
+ /* Try default linux start address */
+ jump_to_IPL_code(KERN_IMAGE_START);
+}
+
+static uint32_t find_iso_bc(void)
+{
+ IsoVolDesc *vd = (IsoVolDesc *)sec;
+ uint32_t block_num = ISO_PRIMARY_VD_SECTOR;
+
+ if (virtio_read_many(block_num++, sec, 1)) {
+ /* If primary vd cannot be read, there is no boot catalog */
+ return 0;
+ }
+
+ while (is_iso_vd_valid(vd) && vd->type != VOL_DESC_TERMINATOR) {
+ if (vd->type == VOL_DESC_TYPE_BOOT) {
+ IsoVdElTorito *et = &vd->vd.boot;
+
+ if (!_memcmp(&et->el_torito[0], el_torito_magic, 32)) {
+ return bswap32(et->bc_offset);
+ }
+ }
+ read_iso_sector(block_num++, sec,
+ "Failed to read ISO volume descriptor");
+ }
+
+ return 0;
+}
+
+static IsoBcSection *find_iso_bc_entry(void)
+{
+ IsoBcEntry *e = (IsoBcEntry *)sec;
+ uint32_t offset = find_iso_bc();
+ int i;
+
+ if (!offset) {
+ return NULL;
+ }
+
+ read_iso_sector(offset, sec, "Failed to read El Torito boot catalog");
+
+ if (!is_iso_bc_valid(e)) {
+ /* The validation entry is mandatory */
+ virtio_panic("No valid boot catalog found!\n");
+ return NULL;
+ }
+
+ /*
+ * Each entry has 32 bytes size, so one sector cannot contain > 64 entries.
+ * We consider only boot catalogs with no more than 64 entries.
+ */
+ for (i = 1; i < ISO_BC_ENTRY_PER_SECTOR; i++) {
+ if (e[i].id == ISO_BC_BOOTABLE_SECTION) {
+ if (is_iso_bc_entry_compatible(&e[i].body.sect)) {
+ return &e[i].body.sect;
+ }
+ }
+ }
+
+ virtio_panic("No suitable boot entry found on ISO-9660 media!\n");
+
+ return NULL;
+}
+
+static void ipl_iso_el_torito(void)
+{
+ IsoBcSection *s = find_iso_bc_entry();
+
+ if (s) {
+ load_iso_bc_entry(s);
+ /* no return */
+ }
+}
+
/***********************************************************************
* IPL starts here
*/
ipl_scsi(); /* no return */
}
+ /* Check if we can boot as ISO media */
+ if (virtio_guessed_disk_nature()) {
+ virtio_assume_iso9660();
+ }
+ ipl_iso_el_torito();
+
/* We have failed to follow the SCSI scheme, so */
if (virtio_guessed_disk_nature()) {
sclp_print("Using guessed DASD geometry.\n");
return *((uint32_t *)data) == *((uint32_t *)magic);
}
+static inline int _memcmp(const void *s1, const void *s2, size_t n)
+{
+ int i;
+ const uint8_t *p1 = s1, *p2 = s2;
+
+ for (i = 0; i < n; i++) {
+ if (p1[i] != p2[i]) {
+ return p1[i] > p2[i] ? 1 : -1;
+ }
+ }
+
+ return 0;
+}
+
+/* from include/qemu/bswap.h */
+
+/* El Torito is always little-endian */
+static inline uint16_t bswap16(uint16_t x)
+{
+ return ((x & 0x00ff) << 8) | ((x & 0xff00) >> 8);
+}
+
+static inline uint32_t bswap32(uint32_t x)
+{
+ return ((x & 0x000000ffU) << 24) | ((x & 0x0000ff00U) << 8) |
+ ((x & 0x00ff0000U) >> 8) | ((x & 0xff000000U) >> 24);
+}
+
+static inline uint64_t bswap64(uint64_t x)
+{
+ return ((x & 0x00000000000000ffULL) << 56) |
+ ((x & 0x000000000000ff00ULL) << 40) |
+ ((x & 0x0000000000ff0000ULL) << 24) |
+ ((x & 0x00000000ff000000ULL) << 8) |
+ ((x & 0x000000ff00000000ULL) >> 8) |
+ ((x & 0x0000ff0000000000ULL) >> 24) |
+ ((x & 0x00ff000000000000ULL) >> 40) |
+ ((x & 0xff00000000000000ULL) >> 56);
+}
+
+static inline uint32_t iso_733_to_u32(uint64_t x)
+{
+ return (uint32_t)x;
+}
+
+#define ISO_SECTOR_SIZE 2048
+/* El Torito specifies boot image size in 512 byte blocks */
+#define ET_SECTOR_SHIFT 2
+#define KERN_IMAGE_START 0x010000UL
+#define PSW_MASK_64 0x0000000100000000ULL
+#define PSW_MASK_32 0x0000000080000000ULL
+#define IPL_PSW_MASK (PSW_MASK_32 | PSW_MASK_64)
+
+#define ISO_PRIMARY_VD_SECTOR 16
+
+static inline void read_iso_sector(uint32_t block_offset, void *buf,
+ const char *errmsg)
+{
+ IPL_assert(virtio_read_many(block_offset, buf, 1) == 0, errmsg);
+}
+
+static inline void read_iso_boot_image(uint32_t block_offset, void *load_addr,
+ uint32_t blks_to_load)
+{
+ IPL_assert(virtio_read_many(block_offset, load_addr, blks_to_load) == 0,
+ "Failed to read boot image!");
+}
+
+const uint8_t el_torito_magic[] = "EL TORITO SPECIFICATION"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+#define ISO9660_MAX_DIR_DEPTH 8
+
+typedef struct IsoDirHdr {
+ uint8_t dr_len;
+ uint8_t ear_len;
+ uint64_t ext_loc;
+ uint64_t data_len;
+ uint8_t recording_datetime[7];
+ uint8_t file_flags;
+ uint8_t file_unit_size;
+ uint8_t gap_size;
+ uint32_t vol_seqnum;
+ uint8_t fileid_len;
+} __attribute__((packed)) IsoDirHdr;
+
+typedef struct IsoVdElTorito {
+ uint8_t el_torito[32]; /* must contain el_torito_magic value */
+ uint8_t unused0[32];
+ uint32_t bc_offset;
+ uint8_t unused1[1974];
+} __attribute__((packed)) IsoVdElTorito;
+
+typedef struct IsoVdPrimary {
+ uint8_t unused1;
+ uint8_t sys_id[32];
+ uint8_t vol_id[32];
+ uint8_t unused2[8];
+ uint64_t vol_space_size;
+ uint8_t unused3[32];
+ uint32_t vol_set_size;
+ uint32_t vol_seqnum;
+ uint32_t log_block_size;
+ uint64_t path_table_size;
+ uint32_t l_path_table;
+ uint32_t opt_l_path_table;
+ uint32_t m_path_table;
+ uint32_t opt_m_path_table;
+ IsoDirHdr rootdir;
+ uint8_t root_null;
+ uint8_t reserved2[1858];
+} __attribute__((packed)) IsoVdPrimary;
+
+typedef struct IsoVolDesc {
+ uint8_t type;
+ uint8_t ident[5];
+ uint8_t version;
+ union {
+ IsoVdElTorito boot;
+ IsoVdPrimary primary;
+ } vd;
+} __attribute__((packed)) IsoVolDesc;
+
+const uint8_t vol_desc_magic[] = "CD001";
+#define VOL_DESC_TYPE_BOOT 0
+#define VOL_DESC_TYPE_PRIMARY 1
+#define VOL_DESC_TYPE_SUPPLEMENT 2
+#define VOL_DESC_TYPE_PARTITION 3
+#define VOL_DESC_TERMINATOR 255
+
+static inline bool is_iso_vd_valid(IsoVolDesc *vd)
+{
+ return !_memcmp(&vd->ident[0], vol_desc_magic, 5) &&
+ vd->version == 0x1 &&
+ vd->type <= VOL_DESC_TYPE_PARTITION;
+}
+
+typedef struct IsoBcValid {
+ uint8_t platform_id;
+ uint16_t reserved;
+ uint8_t id[24];
+ uint16_t checksum;
+ uint8_t key[2];
+} __attribute__((packed)) IsoBcValid;
+
+typedef struct IsoBcSection {
+ uint8_t boot_type;
+ uint16_t load_segment;
+ uint8_t sys_type;
+ uint8_t unused;
+ uint16_t sector_count;
+ uint32_t load_rba;
+ uint8_t selection[20];
+} __attribute__((packed)) IsoBcSection;
+
+typedef struct IsoBcHdr {
+ uint8_t platform_id;
+ uint16_t sect_num;
+ uint8_t id[28];
+} __attribute__((packed)) IsoBcHdr;
+
+/*
+ * Match two CCWs located after PSW and eight filler bytes.
+ * From libmagic and arch/s390/kernel/head.S.
+ */
+const uint8_t linux_s390_magic[] = "\x02\x00\x00\x18\x60\x00\x00\x50\x02\x00"
+ "\x00\x68\x60\x00\x00\x50\x40\x40\x40\x40"
+ "\x40\x40\x40\x40";
+
+typedef struct IsoBcEntry {
+ uint8_t id;
+ union {
+ IsoBcValid valid; /* id == 0x01 */
+ IsoBcSection sect; /* id == 0x88 || id == 0x0 */
+ IsoBcHdr hdr; /* id == 0x90 || id == 0x91 */
+ } body;
+} __attribute__((packed)) IsoBcEntry;
+
+#define ISO_BC_ENTRY_PER_SECTOR (ISO_SECTOR_SIZE / sizeof(IsoBcEntry))
+#define ISO_BC_HDR_VALIDATION 0x01
+#define ISO_BC_BOOTABLE_SECTION 0x88
+#define ISO_BC_MAGIC_55 0x55
+#define ISO_BC_MAGIC_AA 0xaa
+#define ISO_BC_PLATFORM_X86 0x0
+#define ISO_BC_PLATFORM_PPC 0x1
+#define ISO_BC_PLATFORM_MAC 0x2
+
+static inline bool is_iso_bc_valid(IsoBcEntry *e)
+{
+ IsoBcValid *v = &e->body.valid;
+
+ if (e->id != ISO_BC_HDR_VALIDATION) {
+ return false;
+ }
+
+ if (v->platform_id != ISO_BC_PLATFORM_X86 &&
+ v->platform_id != ISO_BC_PLATFORM_PPC &&
+ v->platform_id != ISO_BC_PLATFORM_MAC) {
+ return false;
+ }
+
+ return v->key[0] == ISO_BC_MAGIC_55 &&
+ v->key[1] == ISO_BC_MAGIC_AA &&
+ v->reserved == 0x0;
+}
+
#endif /* _PC_BIOS_S390_CCW_BOOTMAP_H */
blk_cfg.physical_block_exp = 0;
}
+void virtio_assume_iso9660(void)
+{
+ guessed_disk_nature = true;
+ blk_cfg.blk_size = 2048;
+ blk_cfg.physical_block_exp = 0;
+}
+
void virtio_assume_eckd(void)
{
guessed_disk_nature = true;
bool virtio_guessed_disk_nature(void);
void virtio_assume_scsi(void);
void virtio_assume_eckd(void);
+void virtio_assume_iso9660(void);
extern bool virtio_disk_is_scsi(void);
extern bool virtio_disk_is_eckd(void);
#define VIRTIO_SECTOR_SIZE 512
-static inline ulong virtio_eckd_sector_adjust(ulong sector)
-{
- return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
-}
-
static inline ulong virtio_sector_adjust(ulong sector)
{
- return virtio_disk_is_eckd() ? virtio_eckd_sector_adjust(sector) : sector;
+ return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
}
#endif /* VIRTIO_H */
# abort since 1.6
# blockdev-snapshot-internal-sync since 1.7
# blockdev-backup since 2.3
+# blockdev-snapshot since 2.5
##
{ 'union': 'TransactionAction',
'data': {
- 'blockdev-snapshot-sync': 'BlockdevSnapshot',
+ 'blockdev-snapshot': 'BlockdevSnapshot',
+ 'blockdev-snapshot-sync': 'BlockdevSnapshotSync',
'drive-backup': 'DriveBackup',
'blockdev-backup': 'BlockdevBackup',
'abort': 'Abort',
# device's password. The behavior of reads and writes to the block
# device between when these calls are executed is undefined.
#
-# Notes: It is strongly recommended that this interface is not used especially
-# for changing block devices.
+# Notes: This interface is deprecated, and it is strongly recommended that you
+# avoid using it. For changing block devices, use
+# blockdev-change-medium; for changing VNC parameters, use
+# change-vnc-password.
#
# Since: 0.14.0
##
'data': [ 'existing', 'absolute-paths' ] }
##
-# @BlockdevSnapshot
+# @BlockdevSnapshotSync
#
# Either @device or @node-name must be set but not both.
#
# @mode: #optional whether and how QEMU should create a new image, default is
# 'absolute-paths'.
##
-{ 'struct': 'BlockdevSnapshot',
+{ 'struct': 'BlockdevSnapshotSync',
'data': { '*device': 'str', '*node-name': 'str',
'snapshot-file': 'str', '*snapshot-node-name': 'str',
'*format': 'str', '*mode': 'NewImageMode' } }
+##
+# @BlockdevSnapshot
+#
+# @node: device or node name that will have a snapshot created.
+#
+# @overlay: reference to the existing block device that will become
+# the overlay of @node, as part of creating the snapshot.
+# It must not have a current backing file (this can be
+# achieved by passing "backing": "" to blockdev-add).
+#
+# Since 2.5
+##
+{ 'struct': 'BlockdevSnapshot',
+ 'data': { 'node': 'str', 'overlay': 'str' } }
+
##
# @DriveBackup
#
#
# Generates a synchronous snapshot of a block device.
#
-# For the arguments, see the documentation of BlockdevSnapshot.
+# For the arguments, see the documentation of BlockdevSnapshotSync.
#
# Returns: nothing on success
# If @device is not a valid block device, DeviceNotFound
# Since 0.14.0
##
{ 'command': 'blockdev-snapshot-sync',
+ 'data': 'BlockdevSnapshotSync' }
+
+
+##
+# @blockdev-snapshot
+#
+# Generates a snapshot of a block device.
+#
+# For the arguments, see the documentation of BlockdevSnapshot.
+#
+# Since 2.5
+##
+{ 'command': 'blockdev-snapshot',
'data': 'BlockdevSnapshot' }
##
# level and no BlockBackend will be created.
#
# This command is still a work in progress. It doesn't support all
-# block drivers, it lacks a matching blockdev-del, and more. Stay
-# away from it unless you want to help with its development.
+# block drivers among other things. Stay away from it unless you want
+# to help with its development.
#
# @options: block device options for the new device
#
##
{ 'command': 'blockdev-add', 'data': { 'options': 'BlockdevOptions' } }
+##
+# @x-blockdev-del:
+#
+# Deletes a block device that has been added using blockdev-add.
+# The selected device can be either a block backend or a graph node.
+#
+# In the former case the backend will be destroyed, along with its
+# inserted medium if there's any. The command will fail if the backend
+# or its medium are in use.
+#
+# In the latter case the node will be destroyed. The command will fail
+# if the node is attached to a block backend or is otherwise being
+# used.
+#
+# One of @id or @node-name must be specified, but not both.
+#
+# This command is still a work in progress and is considered
+# experimental. Stay away from it unless you want to help with its
+# development.
+#
+# @id: #optional Name of the block backend device to delete.
+#
+# @node-name: #optional Name of the graph node to delete.
+#
+# Since: 2.5
+##
+{ 'command': 'x-blockdev-del', 'data': { '*id': 'str', '*node-name': 'str' } }
+
+##
+# @blockdev-open-tray:
+#
+# Opens a block device's tray. If there is a block driver state tree inserted as
+# a medium, it will become inaccessible to the guest (but it will remain
+# associated to the block device, so closing the tray will make it accessible
+# again).
+#
+# If the tray was already open before, this will be a no-op.
+#
+# Once the tray opens, a DEVICE_TRAY_MOVED event is emitted. There are cases in
+# which no such event will be generated, these include:
+# - if the guest has locked the tray, @force is false and the guest does not
+# respond to the eject request
+# - if the BlockBackend denoted by @device does not have a guest device attached
+# to it
+# - if the guest device does not have an actual tray and is empty, for instance
+# for floppy disk drives
+#
+# @device: block device name
+#
+# @force: #optional if false (the default), an eject request will be sent to
+# the guest if it has locked the tray (and the tray will not be opened
+# immediately); if true, the tray will be opened regardless of whether
+# it is locked
+#
+# Since: 2.5
+##
+{ 'command': 'blockdev-open-tray',
+ 'data': { 'device': 'str',
+ '*force': 'bool' } }
+
+##
+# @blockdev-close-tray:
+#
+# Closes a block device's tray. If there is a block driver state tree associated
+# with the block device (which is currently ejected), that tree will be loaded
+# as the medium.
+#
+# If the tray was already closed before, this will be a no-op.
+#
+# @device: block device name
+#
+# Since: 2.5
+##
+{ 'command': 'blockdev-close-tray',
+ 'data': { 'device': 'str' } }
+
+##
+# @blockdev-remove-medium:
+#
+# Removes a medium (a block driver state tree) from a block device. That block
+# device's tray must currently be open (unless there is no attached guest
+# device).
+#
+# If the tray is open and there is no medium inserted, this will be a no-op.
+#
+# @device: block device name
+#
+# Since: 2.5
+##
+{ 'command': 'blockdev-remove-medium',
+ 'data': { 'device': 'str' } }
+
+##
+# @blockdev-insert-medium:
+#
+# Inserts a medium (a block driver state tree) into a block device. That block
+# device's tray must currently be open (unless there is no attached guest
+# device) and there must be no medium inserted already.
+#
+# @device: block device name
+#
+# @node-name: name of a node in the block driver state graph
+#
+# Since: 2.5
+##
+{ 'command': 'blockdev-insert-medium',
+ 'data': { 'device': 'str',
+ 'node-name': 'str'} }
+
+
+##
+# @BlockdevChangeReadOnlyMode:
+#
+# Specifies the new read-only mode of a block device subject to the
+# @blockdev-change-medium command.
+#
+# @retain: Retains the current read-only mode
+#
+# @read-only: Makes the device read-only
+#
+# @read-write: Makes the device writable
+#
+# Since: 2.3
+##
+{ 'enum': 'BlockdevChangeReadOnlyMode',
+ 'data': ['retain', 'read-only', 'read-write'] }
+
+
+##
+# @blockdev-change-medium:
+#
+# Changes the medium inserted into a block device by ejecting the current medium
+# and loading a new image file which is inserted as the new medium (this command
+# combines blockdev-open-tray, blockdev-remove-medium, blockdev-insert-medium
+# and blockdev-close-tray).
+#
+# @device: block device name
+#
+# @filename: filename of the new image to be loaded
+#
+# @format: #optional, format to open the new image with (defaults to
+# the probed format)
+#
+# @read-only-mode: #optional, change the read-only mode of the device; defaults
+# to 'retain'
+#
+# Since: 2.5
+##
+{ 'command': 'blockdev-change-medium',
+ 'data': { 'device': 'str',
+ 'filename': 'str',
+ '*format': 'str',
+ '*read-only-mode': 'BlockdevChangeReadOnlyMode' } }
+
##
# @BlockErrorAction
do {
aio_poll(aio_context, true);
- qemu_progress_print((float)job->offset / job->len * 100.f, 0);
+ qemu_progress_print(job->len ?
+ ((float)job->offset / job->len * 100.f) : 0.0f, 0);
} while (!job->ready);
block_job_complete_sync(job, errp);
static int64_t cvtnum(const char *s)
{
char *end;
- return qemu_strtosz_suffix(s, &end, QEMU_STRTOSZ_DEFSUFFIX_B);
+ int64_t ret;
+
+ ret = qemu_strtosz_suffix(s, &end, QEMU_STRTOSZ_DEFSUFFIX_B);
+ if (*end != '\0') {
+ /* Detritus at the end of the string */
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static void print_cvtnum_err(int64_t rc, const char *arg)
+{
+ switch (rc) {
+ case -EINVAL:
+ printf("Parsing error: non-numeric argument,"
+ " or extraneous/unrecognized suffix -- %s\n", arg);
+ break;
+ case -ERANGE:
+ printf("Parsing error: argument too large -- %s\n", arg);
+ break;
+ default:
+ printf("Parsing error: %s\n", arg);
+ }
}
#define EXABYTES(x) ((long long)(x) << 60)
qemu_vfree(p);
}
-static void dump_buffer(const void *buffer, int64_t offset, int len)
+static void dump_buffer(const void *buffer, int64_t offset, int64_t len)
{
- int i, j;
+ uint64_t i;
+ int j;
const uint8_t *p;
for (i = 0, p = buffer; i < len; i += 16) {
}
static void print_report(const char *op, struct timeval *t, int64_t offset,
- int count, int total, int cnt, int Cflag)
+ int64_t count, int64_t total, int cnt, int Cflag)
{
char s1[64], s2[64], ts[64];
if (!Cflag) {
cvtstr((double)total, s1, sizeof(s1));
cvtstr(tdiv((double)total, *t), s2, sizeof(s2));
- printf("%s %d/%d bytes at offset %" PRId64 "\n",
+ printf("%s %"PRId64"/%"PRId64" bytes at offset %" PRId64 "\n",
op, total, count, offset);
printf("%s, %d ops; %s (%s/sec and %.4f ops/sec)\n",
s1, cnt, ts, s2, tdiv((double)cnt, *t));
} else {/* bytes,ops,time,bytes/sec,ops/sec */
- printf("%d,%d,%s,%.3f,%.3f\n",
+ printf("%"PRId64",%d,%s,%.3f,%.3f\n",
total, cnt, ts,
tdiv((double)total, *t),
tdiv((double)cnt, *t));
len = cvtnum(arg);
if (len < 0) {
- printf("non-numeric length argument -- %s\n", arg);
+ print_cvtnum_err(len, arg);
goto fail;
}
/* should be SIZE_T_MAX, but that doesn't exist */
if (len > INT_MAX) {
- printf("too large length argument -- %s\n", arg);
+ printf("Argument '%s' exceeds maximum size %d\n", arg, INT_MAX);
goto fail;
}
return buf;
}
-static int do_read(BlockBackend *blk, char *buf, int64_t offset, int count,
- int *total)
+static int do_read(BlockBackend *blk, char *buf, int64_t offset, int64_t count,
+ int64_t *total)
{
int ret;
+ if (count >> 9 > INT_MAX) {
+ return -ERANGE;
+ }
+
ret = blk_read(blk, offset >> 9, (uint8_t *)buf, count >> 9);
if (ret < 0) {
return ret;
return 1;
}
-static int do_write(BlockBackend *blk, char *buf, int64_t offset, int count,
- int *total)
+static int do_write(BlockBackend *blk, char *buf, int64_t offset, int64_t count,
+ int64_t *total)
{
int ret;
+ if (count >> 9 > INT_MAX) {
+ return -ERANGE;
+ }
+
ret = blk_write(blk, offset >> 9, (uint8_t *)buf, count >> 9);
if (ret < 0) {
return ret;
return 1;
}
-static int do_pread(BlockBackend *blk, char *buf, int64_t offset, int count,
- int *total)
+static int do_pread(BlockBackend *blk, char *buf, int64_t offset,
+ int64_t count, int64_t *total)
{
+ if (count > INT_MAX) {
+ return -ERANGE;
+ }
+
*total = blk_pread(blk, offset, (uint8_t *)buf, count);
if (*total < 0) {
return *total;
return 1;
}
-static int do_pwrite(BlockBackend *blk, char *buf, int64_t offset, int count,
- int *total)
+static int do_pwrite(BlockBackend *blk, char *buf, int64_t offset,
+ int64_t count, int64_t *total)
{
+ if (count > INT_MAX) {
+ return -ERANGE;
+ }
+
*total = blk_pwrite(blk, offset, (uint8_t *)buf, count);
if (*total < 0) {
return *total;
typedef struct {
BlockBackend *blk;
int64_t offset;
- int count;
- int *total;
+ int64_t count;
+ int64_t *total;
int ret;
bool done;
} CoWriteZeroes;
*data->total = data->count;
}
-static int do_co_write_zeroes(BlockBackend *blk, int64_t offset, int count,
- int *total)
+static int do_co_write_zeroes(BlockBackend *blk, int64_t offset, int64_t count,
+ int64_t *total)
{
Coroutine *co;
CoWriteZeroes data = {
.done = false,
};
+ if (count >> BDRV_SECTOR_BITS > INT_MAX) {
+ return -ERANGE;
+ }
+
co = qemu_coroutine_create(co_write_zeroes_entry);
qemu_coroutine_enter(co, &data);
while (!data.done) {
}
static int do_write_compressed(BlockBackend *blk, char *buf, int64_t offset,
- int count, int *total)
+ int64_t count, int64_t *total)
{
int ret;
+ if (count >> 9 > INT_MAX) {
+ return -ERANGE;
+ }
+
ret = blk_write_compressed(blk, offset >> 9, (uint8_t *)buf, count >> 9);
if (ret < 0) {
return ret;
}
static int do_load_vmstate(BlockBackend *blk, char *buf, int64_t offset,
- int count, int *total)
+ int64_t count, int64_t *total)
{
+ if (count > INT_MAX) {
+ return -ERANGE;
+ }
+
*total = blk_load_vmstate(blk, (uint8_t *)buf, offset, count);
if (*total < 0) {
return *total;
}
static int do_save_vmstate(BlockBackend *blk, char *buf, int64_t offset,
- int count, int *total)
+ int64_t count, int64_t *total)
{
+ if (count > INT_MAX) {
+ return -ERANGE;
+ }
+
*total = blk_save_vmstate(blk, (uint8_t *)buf, offset, count);
if (*total < 0) {
return *total;
int c, cnt;
char *buf;
int64_t offset;
- int count;
+ int64_t count;
/* Some compilers get confused and warn if this is not initialized. */
- int total = 0;
- int pattern = 0, pattern_offset = 0, pattern_count = 0;
+ int64_t total = 0;
+ int pattern = 0;
+ int64_t pattern_offset = 0, pattern_count = 0;
while ((c = getopt(argc, argv, "bCl:pP:qs:v")) != -1) {
switch (c) {
lflag = 1;
pattern_count = cvtnum(optarg);
if (pattern_count < 0) {
- printf("non-numeric length argument -- %s\n", optarg);
+ print_cvtnum_err(pattern_count, optarg);
return 0;
}
break;
sflag = 1;
pattern_offset = cvtnum(optarg);
if (pattern_offset < 0) {
- printf("non-numeric length argument -- %s\n", optarg);
+ print_cvtnum_err(pattern_offset, optarg);
return 0;
}
break;
offset = cvtnum(argv[optind]);
if (offset < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(offset, argv[optind]);
return 0;
}
optind++;
count = cvtnum(argv[optind]);
if (count < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(count, argv[optind]);
+ return 0;
+ } else if (count > SIZE_MAX) {
+ printf("length cannot exceed %" PRIu64 ", given %s\n",
+ (uint64_t) SIZE_MAX, argv[optind]);
return 0;
}
return 0;
}
if (count & 0x1ff) {
- printf("count %d is not sector aligned\n",
+ printf("count %"PRId64" is not sector aligned\n",
count);
return 0;
}
memset(cmp_buf, pattern, pattern_count);
if (memcmp(buf + pattern_offset, cmp_buf, pattern_count)) {
printf("Pattern verification failed at offset %"
- PRId64 ", %d bytes\n",
+ PRId64 ", %"PRId64" bytes\n",
offset + pattern_offset, pattern_count);
}
g_free(cmp_buf);
offset = cvtnum(argv[optind]);
if (offset < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(offset, argv[optind]);
return 0;
}
optind++;
int c, cnt;
char *buf = NULL;
int64_t offset;
- int count;
+ int64_t count;
/* Some compilers get confused and warn if this is not initialized. */
- int total = 0;
+ int64_t total = 0;
int pattern = 0xcd;
while ((c = getopt(argc, argv, "bcCpP:qz")) != -1) {
offset = cvtnum(argv[optind]);
if (offset < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(offset, argv[optind]);
return 0;
}
optind++;
count = cvtnum(argv[optind]);
if (count < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(count, argv[optind]);
+ return 0;
+ } else if (count > SIZE_MAX) {
+ printf("length cannot exceed %" PRIu64 ", given %s\n",
+ (uint64_t) SIZE_MAX, argv[optind]);
return 0;
}
}
if (count & 0x1ff) {
- printf("count %d is not sector aligned\n",
+ printf("count %"PRId64" is not sector aligned\n",
count);
return 0;
}
offset = cvtnum(argv[optind]);
if (offset < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(offset, argv[optind]);
return 0;
}
optind++;
/* Read the offset of the request */
offset = cvtnum(argv[optind]);
if (offset < 0) {
- printf("non-numeric offset argument -- %s\n", argv[optind]);
+ print_cvtnum_err(offset, argv[optind]);
goto out;
}
optind++;
ctx->offset = cvtnum(argv[optind]);
if (ctx->offset < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(ctx->offset, argv[optind]);
g_free(ctx);
return 0;
}
ctx->offset = cvtnum(argv[optind]);
if (ctx->offset < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(ctx->offset, argv[optind]);
g_free(ctx);
return 0;
}
offset = cvtnum(argv[1]);
if (offset < 0) {
- printf("non-numeric truncate argument -- %s\n", argv[1]);
+ print_cvtnum_err(offset, argv[1]);
return 0;
}
struct timeval t1, t2;
int Cflag = 0, qflag = 0;
int c, ret;
- int64_t offset;
- int count;
+ int64_t offset, count;
while ((c = getopt(argc, argv, "Cq")) != -1) {
switch (c) {
offset = cvtnum(argv[optind]);
if (offset < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(offset, argv[optind]);
return 0;
}
optind++;
count = cvtnum(argv[optind]);
if (count < 0) {
- printf("non-numeric length argument -- %s\n", argv[optind]);
+ print_cvtnum_err(count, argv[optind]);
+ return 0;
+ } else if (count >> BDRV_SECTOR_BITS > INT_MAX) {
+ printf("length cannot exceed %"PRIu64", given %s\n",
+ (uint64_t)INT_MAX << BDRV_SECTOR_BITS,
+ argv[optind]);
return 0;
}
static int alloc_f(BlockBackend *blk, int argc, char **argv)
{
BlockDriverState *bs = blk_bs(blk);
- int64_t offset, sector_num;
- int nb_sectors, remaining;
+ int64_t offset, sector_num, nb_sectors, remaining;
char s1[64];
- int num, sum_alloc;
- int ret;
+ int num, ret;
+ int64_t sum_alloc;
offset = cvtnum(argv[1]);
if (offset < 0) {
- printf("non-numeric offset argument -- %s\n", argv[1]);
+ print_cvtnum_err(offset, argv[1]);
return 0;
} else if (offset & 0x1ff) {
printf("offset %" PRId64 " is not sector aligned\n",
if (argc == 3) {
nb_sectors = cvtnum(argv[2]);
if (nb_sectors < 0) {
- printf("non-numeric length argument -- %s\n", argv[2]);
+ print_cvtnum_err(nb_sectors, argv[2]);
+ return 0;
+ } else if (nb_sectors > INT_MAX) {
+ printf("length argument cannot exceed %d, given %s\n",
+ INT_MAX, argv[2]);
return 0;
}
} else {
cvtstr(offset, s1, sizeof(s1));
- printf("%d/%d sectors allocated at offset %s\n",
+ printf("%"PRId64"/%"PRId64" sectors allocated at offset %s\n",
sum_alloc, nb_sectors, s1);
return 0;
}
static int sigraise_f(BlockBackend *blk, int argc, char **argv)
{
- int sig = cvtnum(argv[1]);
+ int64_t sig = cvtnum(argv[1]);
if (sig < 0) {
- printf("non-numeric signal number argument -- %s\n", argv[1]);
+ print_cvtnum_err(sig, argv[1]);
+ return 0;
+ } else if (sig > NSIG) {
+ printf("signal argument '%s' is too large to be a valid signal\n",
+ argv[1]);
return 0;
}
"format": "qcow2" } }
<- { "return": {} }
+EQMP
+
+ {
+ .name = "blockdev-snapshot",
+ .args_type = "node:s,overlay:s",
+ .mhandler.cmd_new = qmp_marshal_blockdev_snapshot,
+ },
+
+SQMP
+blockdev-snapshot
+-----------------
+Since 2.5
+
+Create a snapshot, by installing 'node' as the backing image of
+'overlay'. Additionally, if 'node' is associated with a block
+device, the block device changes to using 'overlay' as its new active
+image.
+
+Arguments:
+
+- "node": device that will have a snapshot created (json-string)
+- "overlay": device that will have 'node' as its backing image (json-string)
+
+Example:
+
+-> { "execute": "blockdev-add",
+ "arguments": { "options": { "driver": "qcow2",
+ "node-name": "node1534",
+ "file": { "driver": "file",
+ "filename": "hd1.qcow2" },
+ "backing": "" } } }
+
+<- { "return": {} }
+
+-> { "execute": "blockdev-snapshot", "arguments": { "node": "ide-hd0",
+ "overlay": "node1534" } }
+<- { "return": {} }
+
EQMP
{
Add a block device.
This command is still a work in progress. It doesn't support all
-block drivers, it lacks a matching blockdev-del, and more. Stay away
-from it unless you want to help with its development.
+block drivers among other things. Stay away from it unless you want
+to help with its development.
Arguments:
<- { "return": {} }
+EQMP
+
+ {
+ .name = "x-blockdev-del",
+ .args_type = "id:s?,node-name:s?",
+ .mhandler.cmd_new = qmp_marshal_x_blockdev_del,
+ },
+
+SQMP
+x-blockdev-del
+------------
+Since 2.5
+
+Deletes a block device thas has been added using blockdev-add.
+The selected device can be either a block backend or a graph node.
+
+In the former case the backend will be destroyed, along with its
+inserted medium if there's any. The command will fail if the backend
+or its medium are in use.
+
+In the latter case the node will be destroyed. The command will fail
+if the node is attached to a block backend or is otherwise being
+used.
+
+One of "id" or "node-name" must be specified, but not both.
+
+This command is still a work in progress and is considered
+experimental. Stay away from it unless you want to help with its
+development.
+
+Arguments:
+
+- "id": Name of the block backend device to delete (json-string, optional)
+- "node-name": Name of the graph node to delete (json-string, optional)
+
+Example:
+
+-> { "execute": "blockdev-add",
+ "arguments": {
+ "options": {
+ "driver": "qcow2",
+ "id": "drive0",
+ "file": {
+ "driver": "file",
+ "filename": "test.qcow2"
+ }
+ }
+ }
+ }
+
+<- { "return": {} }
+
+-> { "execute": "x-blockdev-del",
+ "arguments": { "id": "drive0" }
+ }
+<- { "return": {} }
+
+EQMP
+
+ {
+ .name = "blockdev-open-tray",
+ .args_type = "device:s,force:b?",
+ .mhandler.cmd_new = qmp_marshal_blockdev_open_tray,
+ },
+
+SQMP
+blockdev-open-tray
+------------------
+
+Opens a block device's tray. If there is a block driver state tree inserted as a
+medium, it will become inaccessible to the guest (but it will remain associated
+to the block device, so closing the tray will make it accessible again).
+
+If the tray was already open before, this will be a no-op.
+
+Once the tray opens, a DEVICE_TRAY_MOVED event is emitted. There are cases in
+which no such event will be generated, these include:
+- if the guest has locked the tray, @force is false and the guest does not
+ respond to the eject request
+- if the BlockBackend denoted by @device does not have a guest device attached
+ to it
+- if the guest device does not have an actual tray and is empty, for instance
+ for floppy disk drives
+
+Arguments:
+
+- "device": block device name (json-string)
+- "force": if false (the default), an eject request will be sent to the guest if
+ it has locked the tray (and the tray will not be opened immediately);
+ if true, the tray will be opened regardless of whether it is locked
+ (json-bool, optional)
+
+Example:
+
+-> { "execute": "blockdev-open-tray",
+ "arguments": { "device": "ide1-cd0" } }
+
+<- { "timestamp": { "seconds": 1418751016,
+ "microseconds": 716996 },
+ "event": "DEVICE_TRAY_MOVED",
+ "data": { "device": "ide1-cd0",
+ "tray-open": true } }
+
+<- { "return": {} }
+
+EQMP
+
+ {
+ .name = "blockdev-close-tray",
+ .args_type = "device:s",
+ .mhandler.cmd_new = qmp_marshal_blockdev_close_tray,
+ },
+
+SQMP
+blockdev-close-tray
+-------------------
+
+Closes a block device's tray. If there is a block driver state tree associated
+with the block device (which is currently ejected), that tree will be loaded as
+the medium.
+
+If the tray was already closed before, this will be a no-op.
+
+Arguments:
+
+- "device": block device name (json-string)
+
+Example:
+
+-> { "execute": "blockdev-close-tray",
+ "arguments": { "device": "ide1-cd0" } }
+
+<- { "timestamp": { "seconds": 1418751345,
+ "microseconds": 272147 },
+ "event": "DEVICE_TRAY_MOVED",
+ "data": { "device": "ide1-cd0",
+ "tray-open": false } }
+
+<- { "return": {} }
+
+EQMP
+
+ {
+ .name = "blockdev-remove-medium",
+ .args_type = "device:s",
+ .mhandler.cmd_new = qmp_marshal_blockdev_remove_medium,
+ },
+
+SQMP
+blockdev-remove-medium
+----------------------
+
+Removes a medium (a block driver state tree) from a block device. That block
+device's tray must currently be open (unless there is no attached guest device).
+
+If the tray is open and there is no medium inserted, this will be a no-op.
+
+Arguments:
+
+- "device": block device name (json-string)
+
+Example:
+
+-> { "execute": "blockdev-remove-medium",
+ "arguments": { "device": "ide1-cd0" } }
+
+<- { "error": { "class": "GenericError",
+ "desc": "Tray of device 'ide1-cd0' is not open" } }
+
+-> { "execute": "blockdev-open-tray",
+ "arguments": { "device": "ide1-cd0" } }
+
+<- { "timestamp": { "seconds": 1418751627,
+ "microseconds": 549958 },
+ "event": "DEVICE_TRAY_MOVED",
+ "data": { "device": "ide1-cd0",
+ "tray-open": true } }
+
+<- { "return": {} }
+
+-> { "execute": "blockdev-remove-medium",
+ "arguments": { "device": "ide1-cd0" } }
+
+<- { "return": {} }
+
+EQMP
+
+ {
+ .name = "blockdev-insert-medium",
+ .args_type = "device:s,node-name:s",
+ .mhandler.cmd_new = qmp_marshal_blockdev_insert_medium,
+ },
+
+SQMP
+blockdev-insert-medium
+----------------------
+
+Inserts a medium (a block driver state tree) into a block device. That block
+device's tray must currently be open (unless there is no attached guest device)
+and there must be no medium inserted already.
+
+Arguments:
+
+- "device": block device name (json-string)
+- "node-name": root node of the BDS tree to insert into the block device
+
+Example:
+
+-> { "execute": "blockdev-add",
+ "arguments": { "options": { "node-name": "node0",
+ "driver": "raw",
+ "file": { "driver": "file",
+ "filename": "fedora.iso" } } } }
+
+<- { "return": {} }
+
+-> { "execute": "blockdev-insert-medium",
+ "arguments": { "device": "ide1-cd0",
+ "node-name": "node0" } }
+
+<- { "return": {} }
+
EQMP
{
}
} } ] }
+EQMP
+
+ {
+ .name = "blockdev-change-medium",
+ .args_type = "device:B,filename:F,format:s?,read-only-mode:s?",
+ .mhandler.cmd_new = qmp_marshal_blockdev_change_medium,
+ },
+
+SQMP
+blockdev-change-medium
+----------------------
+
+Changes the medium inserted into a block device by ejecting the current medium
+and loading a new image file which is inserted as the new medium.
+
+Arguments:
+
+- "device": device name (json-string)
+- "filename": filename of the new image (json-string)
+- "format": format of the new image (json-string, optional)
+- "read-only-mode": new read-only mode (json-string, optional)
+ - Possible values: "retain" (default), "read-only", "read-write"
+
+Examples:
+
+1. Change a removable medium
+
+-> { "execute": "blockdev-change-medium",
+ "arguments": { "device": "ide1-cd0",
+ "filename": "/srv/images/Fedora-12-x86_64-DVD.iso",
+ "format": "raw" } }
+<- { "return": {} }
+
+2. Load a read-only medium into a writable drive
+
+-> { "execute": "blockdev-change-medium",
+ "arguments": { "device": "isa-fd0",
+ "filename": "/srv/images/ro.img",
+ "format": "raw",
+ "read-only-mode": "retain" } }
+
+<- { "error":
+ { "class": "GenericError",
+ "desc": "Could not open '/srv/images/ro.img': Permission denied" } }
+
+-> { "execute": "blockdev-change-medium",
+ "arguments": { "device": "isa-fd0",
+ "filename": "/srv/images/ro.img",
+ "format": "raw",
+ "read-only-mode": "read-only" } }
+
+<- { "return": {} }
+
EQMP
{
if (strcmp(device, "vnc") == 0) {
qmp_change_vnc(target, has_arg, arg, errp);
} else {
- qmp_change_blockdev(device, target, arg, errp);
+ qmp_blockdev_change_medium(device, target, has_arg, arg, false, 0,
+ errp);
}
}
target = object_resolve_path_type(path, target_type, &ambiguous);
if (ambiguous) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "Path '%s' does not uniquely identify an object", path);
+ error_setg(errp, "Path '%s' does not uniquely identify an object",
+ path);
} else if (!target) {
target = object_resolve_path(path, &ambiguous);
if (target || ambiguous) {
cap_mem_op = kvm_check_extension(s, KVM_CAP_S390_MEM_OP);
cap_s390_irq = kvm_check_extension(s, KVM_CAP_S390_INJECT_IRQ);
- kvm_s390_enable_cmma(s);
+ if (!mem_path) {
+ kvm_s390_enable_cmma(s);
+ }
if (!kvm_check_extension(s, KVM_CAP_S390_GMAP)
|| !kvm_check_extension(s, KVM_CAP_S390_COW)) {
tmpserver = g_strconcat(tmpdir, "/server", NULL);
qtest_add_func("/ivshmem/single", test_ivshmem_single);
- qtest_add_func("/ivshmem/pair", test_ivshmem_pair);
- qtest_add_func("/ivshmem/server", test_ivshmem_server);
qtest_add_func("/ivshmem/hotplug", test_ivshmem_hotplug);
qtest_add_func("/ivshmem/memdev", test_ivshmem_memdev);
+ if (g_test_slow()) {
+ qtest_add_func("/ivshmem/pair", test_ivshmem_pair);
+ qtest_add_func("/ivshmem/server", test_ivshmem_server);
+ }
ret = g_test_run();
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
wrote 512/512 bytes at offset 0
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
incompatible_features 0x1
ERROR cluster 5 refcount=0 reference=1
ERROR OFLAG_COPIED data cluster: l2_entry=8000000000050000 refcount=0
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
wrote 512/512 bytes at offset 0
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
incompatible_features 0x1
ERROR cluster 5 refcount=0 reference=1
Rebuilding refcount structure
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
wrote 512/512 bytes at offset 0
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
incompatible_features 0x0
No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
wrote 512/512 bytes at offset 0
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
incompatible_features 0x1
ERROR cluster 5 refcount=0 reference=1
ERROR OFLAG_COPIED data cluster: l2_entry=8000000000050000 refcount=0
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
wrote 512/512 bytes at offset 0
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
incompatible_features 0x0
No errors were found on the image.
*** done
while not completed:
for event in self.vm.get_qmp_events(wait=True):
if event['event'] == 'BLOCK_JOB_COMPLETED':
+ self.assert_qmp_absent(event, 'data/error')
self.assert_qmp(event, 'data/type', 'commit')
self.assert_qmp(event, 'data/device', 'drive0')
self.assert_qmp(event, 'data/offset', event['data']['len'])
class TestActiveZeroLengthImage(TestSingleDrive):
image_len = 0
+class TestReopenOverlay(ImageCommitTestCase):
+ image_len = 1024 * 1024
+ img0 = os.path.join(iotests.test_dir, '0.img')
+ img1 = os.path.join(iotests.test_dir, '1.img')
+ img2 = os.path.join(iotests.test_dir, '2.img')
+ img3 = os.path.join(iotests.test_dir, '3.img')
+
+ def setUp(self):
+ iotests.create_image(self.img0, self.image_len)
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img0, self.img1)
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img1, self.img2)
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img2, self.img3)
+ qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xab 0 128K', self.img1)
+ self.vm = iotests.VM().add_drive(self.img3)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(self.img0)
+ os.remove(self.img1)
+ os.remove(self.img2)
+ os.remove(self.img3)
+
+ # This tests what happens when the overlay image of the 'top' node
+ # needs to be reopened in read-write mode in order to update the
+ # backing image string.
+ def test_reopen_overlay(self):
+ self.run_commit_test(self.img1, self.img0)
+
if __name__ == '__main__':
iotests.main(supported_fmts=['qcow2', 'qed'])
-........................
+.........................
----------------------------------------------------------------------
-Ran 24 tests
+Ran 25 tests
OK
nbd_unix_socket=$TEST_DIR/test_qemu_nbd_socket
nbd_snapshot_img="nbd:unix:$nbd_unix_socket"
+rm -f "${TEST_DIR}/qemu-nbd.pid"
_cleanup_nbd()
{
- if [ -n "$NBD_SNAPSHOT_PID" ]; then
- kill "$NBD_SNAPSHOT_PID"
+ local NBD_SNAPSHOT_PID
+ if [ -f "${TEST_DIR}/qemu-nbd.pid" ]; then
+ read NBD_SNAPSHOT_PID < "${TEST_DIR}/qemu-nbd.pid"
+ rm -f "${TEST_DIR}/qemu-nbd.pid"
+ if [ -n "$NBD_SNAPSHOT_PID" ]; then
+ kill "$NBD_SNAPSHOT_PID"
+ fi
fi
rm -f "$nbd_unix_socket"
}
{
_cleanup_nbd
$QEMU_NBD -v -t -k "$nbd_unix_socket" "$TEST_IMG" -l $1 &
- NBD_SNAPSHOT_PID=$!
_wait_for_nbd
}
{
_cleanup_nbd
$QEMU_NBD -v -t -k "$nbd_unix_socket" "$TEST_IMG" -l snapshot.name=$1 &
- NBD_SNAPSHOT_PID=$!
_wait_for_nbd
}
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
wrote 131072/131072 bytes at offset 0
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
magic 0x514649fb
version 3
backing_file_offset 0x0
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
wrote 131072/131072 bytes at offset 0
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
magic 0x514649fb
version 3
backing_file_offset 0x0
# snapshots are performed.
#
# Copyright (C) 2014 Red Hat, Inc.
+# Copyright (C) 2015 Igalia, S.L.
#
# 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
snapshot_virt0="snapshot-v0.qcow2"
snapshot_virt1="snapshot-v1.qcow2"
-MAX_SNAPSHOTS=10
+SNAPSHOTS=10
_cleanup()
{
_cleanup_qemu
- for i in $(seq 1 ${MAX_SNAPSHOTS})
+ for i in $(seq 1 ${SNAPSHOTS})
do
rm -f "${TEST_DIR}/${i}-${snapshot_virt0}"
rm -f "${TEST_DIR}/${i}-${snapshot_virt1}"
done
- _cleanup_test_img
+ rm -f "${TEST_IMG}.1" "${TEST_IMG}.2"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
{
cmd="{ 'execute': 'blockdev-snapshot-sync',
'arguments': { 'device': 'virtio0',
- 'snapshot-file':'"${TEST_DIR}/${1}-${snapshot_virt0}"',
+ 'snapshot-file':'${TEST_DIR}/${1}-${snapshot_virt0}',
'format': 'qcow2' } }"
_send_qemu_cmd $h "${cmd}" "return"
}
{'actions': [
{ 'type': 'blockdev-snapshot-sync', 'data' :
{ 'device': 'virtio0',
- 'snapshot-file': '"${TEST_DIR}/${1}-${snapshot_virt0}"' } },
+ 'snapshot-file': '${TEST_DIR}/${1}-${snapshot_virt0}' } },
{ 'type': 'blockdev-snapshot-sync', 'data' :
{ 'device': 'virtio1',
- 'snapshot-file': '"${TEST_DIR}/${1}-${snapshot_virt1}"' } } ]
+ 'snapshot-file': '${TEST_DIR}/${1}-${snapshot_virt1}' } } ]
} }"
_send_qemu_cmd $h "${cmd}" "return"
}
+# ${1}: unique identifier for the snapshot filename
+# ${2}: true: open backing images; false: don't open them (default)
+function add_snapshot_image()
+{
+ if [ "${2}" = "true" ]; then
+ extra_params=""
+ else
+ extra_params="'backing': '', "
+ fi
+ base_image="${TEST_DIR}/$((${1}-1))-${snapshot_virt0}"
+ snapshot_file="${TEST_DIR}/${1}-${snapshot_virt0}"
+ _make_test_img -b "${base_image}" "$size"
+ mv "${TEST_IMG}" "${snapshot_file}"
+ cmd="{ 'execute': 'blockdev-add', 'arguments':
+ { 'options':
+ { 'driver': 'qcow2', 'node-name': 'snap_${1}', ${extra_params}
+ 'file':
+ { 'driver': 'file', 'filename': '${snapshot_file}',
+ 'node-name': 'file_${1}' } } } }"
+ _send_qemu_cmd $h "${cmd}" "return"
+}
+
+# ${1}: unique identifier for the snapshot filename
+# ${2}: expected response, defaults to 'return'
+function blockdev_snapshot()
+{
+ cmd="{ 'execute': 'blockdev-snapshot',
+ 'arguments': { 'node': 'virtio0',
+ 'overlay':'snap_${1}' } }"
+ _send_qemu_cmd $h "${cmd}" "${2:-return}"
+}
+
size=128M
_make_test_img $size
-mv "${TEST_IMG}" "${TEST_IMG}.orig"
+mv "${TEST_IMG}" "${TEST_IMG}.1"
_make_test_img $size
+mv "${TEST_IMG}" "${TEST_IMG}.2"
echo
echo === Running QEMU ===
echo
qemu_comm_method="qmp"
-_launch_qemu -drive file="${TEST_IMG}.orig",if=virtio -drive file="${TEST_IMG}",if=virtio
+_launch_qemu -drive file="${TEST_IMG}.1",if=virtio -drive file="${TEST_IMG}.2",if=virtio
h=$QEMU_HANDLE
echo
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" "return"
+# Tests for the blockdev-snapshot-sync command
+
echo
echo === Create a single snapshot on virtio0 ===
echo
echo
_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot-sync',
- 'arguments': { 'snapshot-file':'"${TEST_DIR}/1-${snapshot_virt0}"',
+ 'arguments': { 'snapshot-file':'${TEST_DIR}/1-${snapshot_virt0}',
'format': 'qcow2' } }" "error"
echo
echo === Create several transactional group snapshots ===
echo
-for i in $(seq 2 ${MAX_SNAPSHOTS})
+for i in $(seq 2 ${SNAPSHOTS})
do
create_group_snapshot ${i}
done
+# Tests for the blockdev-snapshot command
+
+echo
+echo === Create a couple of snapshots using blockdev-snapshot ===
+echo
+
+SNAPSHOTS=$((${SNAPSHOTS}+1))
+add_snapshot_image ${SNAPSHOTS}
+blockdev_snapshot ${SNAPSHOTS}
+
+SNAPSHOTS=$((${SNAPSHOTS}+1))
+add_snapshot_image ${SNAPSHOTS}
+blockdev_snapshot ${SNAPSHOTS}
+
+echo
+echo === Invalid command - cannot create a snapshot using a file BDS ===
+echo
+
+_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot',
+ 'arguments': { 'node':'virtio0',
+ 'overlay':'file_${SNAPSHOTS}' }
+ }" "error"
+
+echo
+echo === Invalid command - snapshot node used as active layer ===
+echo
+
+blockdev_snapshot ${SNAPSHOTS} error
+
+_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot',
+ 'arguments': { 'node':'virtio0',
+ 'overlay':'virtio0' }
+ }" "error"
+
+_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot',
+ 'arguments': { 'node':'virtio0',
+ 'overlay':'virtio1' }
+ }" "error"
+
+echo
+echo === Invalid command - snapshot node used as backing hd ===
+echo
+
+blockdev_snapshot $((${SNAPSHOTS}-1)) error
+
+echo
+echo === Invalid command - snapshot node has a backing image ===
+echo
+
+SNAPSHOTS=$((${SNAPSHOTS}+1))
+add_snapshot_image ${SNAPSHOTS} true
+blockdev_snapshot ${SNAPSHOTS} error
+
+echo
+echo === Invalid command - The node does not exist ===
+echo
+
+blockdev_snapshot $((${SNAPSHOTS}+1)) error
+
+_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot',
+ 'arguments': { 'node':'nodevice',
+ 'overlay':'snap_${SNAPSHOTS}' }
+ }" "error"
+
# success, all done
echo "*** done"
rm -f $seq.full
=== Create a single snapshot on virtio0 ===
-Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.orig backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.1 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
{"return": {}}
=== Invalid command - missing device and nodename ===
=== Create several transactional group snapshots ===
Formatting 'TEST_DIR/2-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/1-snapshot-v0.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/2-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/2-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
{"return": {}}
Formatting 'TEST_DIR/3-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/2-snapshot-v0.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
Formatting 'TEST_DIR/3-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/2-snapshot-v1.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
Formatting 'TEST_DIR/10-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/9-snapshot-v0.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
Formatting 'TEST_DIR/10-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/9-snapshot-v1.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
{"return": {}}
+
+=== Create a couple of snapshots using blockdev-snapshot ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/10-snapshot-v0.IMGFMT
+{"return": {}}
+{"return": {}}
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/11-snapshot-v0.IMGFMT
+{"return": {}}
+{"return": {}}
+
+=== Invalid command - cannot create a snapshot using a file BDS ===
+
+{"error": {"class": "GenericError", "desc": "The snapshot does not support backing images"}}
+
+=== Invalid command - snapshot node used as active layer ===
+
+{"error": {"class": "GenericError", "desc": "The snapshot is already in use by virtio0"}}
+{"error": {"class": "GenericError", "desc": "The snapshot is already in use by virtio0"}}
+{"error": {"class": "GenericError", "desc": "The snapshot is already in use by virtio1"}}
+
+=== Invalid command - snapshot node used as backing hd ===
+
+{"error": {"class": "GenericError", "desc": "Node 'snap_11' is busy: node is used as backing hd of 'virtio0'"}}
+
+=== Invalid command - snapshot node has a backing image ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/12-snapshot-v0.IMGFMT
+{"return": {}}
+{"error": {"class": "GenericError", "desc": "The snapshot already has a backing image"}}
+
+=== Invalid command - The node does not exist ===
+
+{"error": {"class": "GenericError", "desc": "Cannot find device=snap_14 nor node_name=snap_14"}}
+{"error": {"class": "GenericError", "desc": "Cannot find device=nodevice nor node_name=nodevice"}}
*** done
--- /dev/null
+#!/usr/bin/env python
+#
+# Test case for the QMP 'change' command and all other associated
+# commands
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# 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 os
+import stat
+import time
+import iotests
+from iotests import qemu_img
+
+old_img = os.path.join(iotests.test_dir, 'test0.img')
+new_img = os.path.join(iotests.test_dir, 'test1.img')
+
+class ChangeBaseClass(iotests.QMPTestCase):
+ has_opened = False
+ has_closed = False
+
+ def process_events(self):
+ for event in self.vm.get_qmp_events(wait=False):
+ if (event['event'] == 'DEVICE_TRAY_MOVED' and
+ event['data']['device'] == 'drive0'):
+ if event['data']['tray-open'] == False:
+ self.has_closed = True
+ else:
+ self.has_opened = True
+
+ def wait_for_open(self):
+ timeout = time.clock() + 3
+ while not self.has_opened and time.clock() < timeout:
+ self.process_events()
+ if not self.has_opened:
+ self.fail('Timeout while waiting for the tray to open')
+
+ def wait_for_close(self):
+ timeout = time.clock() + 3
+ while not self.has_closed and time.clock() < timeout:
+ self.process_events()
+ if not self.has_opened:
+ self.fail('Timeout while waiting for the tray to close')
+
+class GeneralChangeTestsBaseClass(ChangeBaseClass):
+ def test_change(self):
+ result = self.vm.qmp('change', device='drive0', target=new_img,
+ arg=iotests.imgfmt)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_blockdev_change_medium(self):
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_eject(self):
+ result = self.vm.qmp('eject', device='drive0', force=True)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+
+ def test_tray_eject_change(self):
+ result = self.vm.qmp('eject', device='drive0', force=True)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_tray_open_close(self):
+ result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ if self.was_empty == True:
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+ else:
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-close-tray', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ if self.has_real_tray or not self.was_empty:
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ if self.has_real_tray or not self.was_empty:
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ else:
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ if self.was_empty == True:
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+ else:
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ def test_tray_eject_close(self):
+ result = self.vm.qmp('eject', device='drive0', force=True)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+
+ result = self.vm.qmp('blockdev-close-tray', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ if self.has_real_tray:
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ if self.has_real_tray:
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ else:
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+
+ def test_tray_open_change(self):
+ result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ if self.was_empty == True:
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+ else:
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_cycle(self):
+ result = self.vm.qmp('blockdev-add',
+ options={'node-name': 'new',
+ 'driver': iotests.imgfmt,
+ 'file': {'filename': new_img,
+ 'driver': 'file'}})
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ if self.was_empty == True:
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+ else:
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-remove-medium', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+
+ result = self.vm.qmp('blockdev-insert-medium', device='drive0',
+ node_name='new')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ result = self.vm.qmp('blockdev-close-tray', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_close_on_closed(self):
+ result = self.vm.qmp('blockdev-close-tray', device='drive0')
+ # Should be a no-op
+ self.assert_qmp(result, 'return', {})
+ self.assertEquals(self.vm.get_qmp_events(wait=False), [])
+
+ def test_remove_on_closed(self):
+ if self.has_opened:
+ # Empty floppy drive
+ return
+
+ result = self.vm.qmp('blockdev-remove-medium', device='drive0')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ def test_insert_on_closed(self):
+ if self.has_opened:
+ # Empty floppy drive
+ return
+
+ result = self.vm.qmp('blockdev-add',
+ options={'node-name': 'new',
+ 'driver': iotests.imgfmt,
+ 'file': {'filename': new_img,
+ 'driver': 'file'}})
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('blockdev-insert-medium', device='drive0',
+ node_name='new')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+class TestInitiallyFilled(GeneralChangeTestsBaseClass):
+ was_empty = False
+
+ def setUp(self, media, interface):
+ qemu_img('create', '-f', iotests.imgfmt, old_img, '1440k')
+ qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k')
+ self.vm = iotests.VM().add_drive(old_img, 'media=%s' % media, interface)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(old_img)
+ os.remove(new_img)
+
+ def test_insert_on_filled(self):
+ result = self.vm.qmp('blockdev-add',
+ options={'node-name': 'new',
+ 'driver': iotests.imgfmt,
+ 'file': {'filename': new_img,
+ 'driver': 'file'}})
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('blockdev-open-tray', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('blockdev-insert-medium', device='drive0',
+ node_name='new')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+class TestInitiallyEmpty(GeneralChangeTestsBaseClass):
+ was_empty = True
+
+ def setUp(self, media, interface):
+ qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k')
+ self.vm = iotests.VM().add_drive(None, 'media=%s' % media, interface)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(new_img)
+
+ def test_remove_on_empty(self):
+ result = self.vm.qmp('blockdev-open-tray', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('blockdev-remove-medium', device='drive0')
+ # Should be a no-op
+ self.assert_qmp(result, 'return', {})
+
+class TestCDInitiallyFilled(TestInitiallyFilled):
+ TestInitiallyFilled = TestInitiallyFilled
+ has_real_tray = True
+
+ def setUp(self):
+ self.TestInitiallyFilled.setUp(self, 'cdrom', 'ide')
+
+class TestCDInitiallyEmpty(TestInitiallyEmpty):
+ TestInitiallyEmpty = TestInitiallyEmpty
+ has_real_tray = True
+
+ def setUp(self):
+ self.TestInitiallyEmpty.setUp(self, 'cdrom', 'ide')
+
+class TestFloppyInitiallyFilled(TestInitiallyFilled):
+ TestInitiallyFilled = TestInitiallyFilled
+ has_real_tray = False
+
+ def setUp(self):
+ self.TestInitiallyFilled.setUp(self, 'disk', 'floppy')
+
+class TestFloppyInitiallyEmpty(TestInitiallyEmpty):
+ TestInitiallyEmpty = TestInitiallyEmpty
+ has_real_tray = False
+
+ def setUp(self):
+ self.TestInitiallyEmpty.setUp(self, 'disk', 'floppy')
+ # FDDs not having a real tray and there not being a medium inside the
+ # tray at startup means the tray will be considered open
+ self.has_opened = True
+
+class TestChangeReadOnly(ChangeBaseClass):
+ def setUp(self):
+ qemu_img('create', '-f', iotests.imgfmt, old_img, '1440k')
+ qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k')
+ self.vm = iotests.VM()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.chmod(old_img, 0666)
+ os.chmod(new_img, 0666)
+ os.remove(old_img)
+ os.remove(new_img)
+
+ def test_ro_ro_retain(self):
+ os.chmod(old_img, 0444)
+ os.chmod(new_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='retain')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_ro_rw_retain(self):
+ os.chmod(old_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='retain')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_rw_ro_retain(self):
+ os.chmod(new_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='retain')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ self.assertEquals(self.vm.get_qmp_events(wait=False), [])
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ def test_ro_rw(self):
+ os.chmod(old_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium',
+ device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='read-write')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_rw_ro(self):
+ os.chmod(new_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium',
+ device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='read-only')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_make_rw_ro(self):
+ self.vm.add_drive(old_img, 'media=disk', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium',
+ device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='read-only')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_make_ro_rw(self):
+ os.chmod(new_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium',
+ device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='read-write')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ self.assertEquals(self.vm.get_qmp_events(wait=False), [])
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ def test_make_rw_ro_by_retain(self):
+ os.chmod(old_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='retain')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ def test_make_ro_rw_by_retain(self):
+ os.chmod(new_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-change-medium', device='drive0',
+ filename=new_img,
+ format=iotests.imgfmt,
+ read_only_mode='retain')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ self.assertEquals(self.vm.get_qmp_events(wait=False), [])
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ def test_rw_ro_cycle(self):
+ os.chmod(new_img, 0444)
+ self.vm.add_drive(old_img, 'media=disk', 'floppy')
+ self.vm.launch()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-add',
+ options={'node-name': 'new',
+ 'driver': iotests.imgfmt,
+ 'read-only': True,
+ 'file': {'filename': new_img,
+ 'driver': 'file'}})
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True)
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_open()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp(result, 'return[0]/inserted/ro', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ result = self.vm.qmp('blockdev-remove-medium', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+
+ result = self.vm.qmp('blockdev-insert-medium', device='drive0',
+ node_name='new')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', True)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+ result = self.vm.qmp('blockdev-close-tray', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.wait_for_close()
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/ro', True)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+
+GeneralChangeTestsBaseClass = None
+TestInitiallyFilled = None
+TestInitiallyEmpty = None
+
+
+class TestBlockJobsAfterCycle(ChangeBaseClass):
+ def setUp(self):
+ qemu_img('create', '-f', iotests.imgfmt, old_img, '1M')
+
+ self.vm = iotests.VM()
+ self.vm.launch()
+
+ result = self.vm.qmp('blockdev-add',
+ options={'id': 'drive0',
+ 'driver': 'null-co'})
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/format', 'null-co')
+
+ # For device-less BBs, calling blockdev-open-tray or blockdev-close-tray
+ # is not necessary
+ result = self.vm.qmp('blockdev-remove-medium', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp_absent(result, 'return[0]/inserted')
+
+ result = self.vm.qmp('blockdev-add',
+ options={'node-name': 'node0',
+ 'driver': iotests.imgfmt,
+ 'file': {'filename': old_img,
+ 'driver': 'file'}})
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('blockdev-insert-medium', device='drive0',
+ node_name='node0')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/tray_open', False)
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(old_img)
+ try:
+ os.remove(new_img)
+ except OSError:
+ pass
+
+ def test_snapshot_and_commit(self):
+ # We need backing file support
+ if iotests.imgfmt != 'qcow2' and iotests.imgfmt != 'qed':
+ return
+
+ result = self.vm.qmp('blockdev-snapshot-sync', device='drive0',
+ snapshot_file=new_img,
+ format=iotests.imgfmt)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
+ self.assert_qmp(result,
+ 'return[0]/inserted/image/backing-image/filename',
+ old_img)
+
+ result = self.vm.qmp('block-commit', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.vm.event_wait(name='BLOCK_JOB_READY')
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/device', 'drive0')
+
+ result = self.vm.qmp('block-job-complete', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.vm.event_wait(name='BLOCK_JOB_COMPLETED')
+
+
+if __name__ == '__main__':
+ if iotests.qemu_default_machine != 'pc':
+ # We need floppy and IDE CD-ROM
+ iotests.notrun('not suitable for this machine type: %s' %
+ iotests.qemu_default_machine)
+ iotests.main()
--- /dev/null
+...........................................................
+----------------------------------------------------------------------
+Ran 59 tests
+
+OK
Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all
wrote 512/512 bytes at offset 0
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" )
+./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@";
+fi )
incompatible_features 0x0
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
wrote 65536/65536 bytes at offset 0
--- /dev/null
+#!/usr/bin/env python
+#
+# Test cases for the QMP 'x-blockdev-del' command
+#
+# Copyright (C) 2015 Igalia, S.L.
+# Author: Alberto Garcia <berto@igalia.com>
+#
+# 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 os
+import iotests
+import time
+
+base_img = os.path.join(iotests.test_dir, 'base.img')
+new_img = os.path.join(iotests.test_dir, 'new.img')
+
+class TestBlockdevDel(iotests.QMPTestCase):
+
+ def setUp(self):
+ iotests.qemu_img('create', '-f', iotests.imgfmt, base_img, '1M')
+ self.vm = iotests.VM()
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(base_img)
+ if os.path.isfile(new_img):
+ os.remove(new_img)
+
+ # Check whether a BlockBackend exists
+ def checkBlockBackend(self, backend, node, must_exist = True):
+ result = self.vm.qmp('query-block')
+ backends = filter(lambda x: x['device'] == backend, result['return'])
+ self.assertLessEqual(len(backends), 1)
+ self.assertEqual(must_exist, len(backends) == 1)
+ if must_exist:
+ if node:
+ self.assertEqual(backends[0]['inserted']['node-name'], node)
+ else:
+ self.assertFalse(backends[0].has_key('inserted'))
+
+ # Check whether a BlockDriverState exists
+ def checkBlockDriverState(self, node, must_exist = True):
+ result = self.vm.qmp('query-named-block-nodes')
+ nodes = filter(lambda x: x['node-name'] == node, result['return'])
+ self.assertLessEqual(len(nodes), 1)
+ self.assertEqual(must_exist, len(nodes) == 1)
+
+ # Add a new BlockBackend (with its attached BlockDriverState)
+ def addBlockBackend(self, backend, node):
+ file_node = '%s_file' % node
+ self.checkBlockBackend(backend, node, False)
+ self.checkBlockDriverState(node, False)
+ self.checkBlockDriverState(file_node, False)
+ opts = {'driver': iotests.imgfmt,
+ 'id': backend,
+ 'node-name': node,
+ 'file': {'driver': 'file',
+ 'node-name': file_node,
+ 'filename': base_img}}
+ result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockBackend(backend, node)
+ self.checkBlockDriverState(node)
+ self.checkBlockDriverState(file_node)
+
+ # Add a BlockDriverState without a BlockBackend
+ def addBlockDriverState(self, node):
+ file_node = '%s_file' % node
+ self.checkBlockDriverState(node, False)
+ self.checkBlockDriverState(file_node, False)
+ opts = {'driver': iotests.imgfmt,
+ 'node-name': node,
+ 'file': {'driver': 'file',
+ 'node-name': file_node,
+ 'filename': base_img}}
+ result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(node)
+ self.checkBlockDriverState(file_node)
+
+ # Add a BlockDriverState that will be used as overlay for the base_img BDS
+ def addBlockDriverStateOverlay(self, node):
+ self.checkBlockDriverState(node, False)
+ iotests.qemu_img('create', '-f', iotests.imgfmt,
+ '-b', base_img, new_img, '1M')
+ opts = {'driver': iotests.imgfmt,
+ 'node-name': node,
+ 'backing': '',
+ 'file': {'driver': 'file',
+ 'filename': new_img}}
+ result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(node)
+
+ # Delete a BlockBackend
+ def delBlockBackend(self, backend, node, expect_error = False,
+ destroys_media = True):
+ self.checkBlockBackend(backend, node)
+ if node:
+ self.checkBlockDriverState(node)
+ result = self.vm.qmp('x-blockdev-del', id = backend)
+ if expect_error:
+ self.assert_qmp(result, 'error/class', 'GenericError')
+ if node:
+ self.checkBlockDriverState(node)
+ else:
+ self.assert_qmp(result, 'return', {})
+ if node:
+ self.checkBlockDriverState(node, not destroys_media)
+ self.checkBlockBackend(backend, node, must_exist = expect_error)
+
+ # Delete a BlockDriverState
+ def delBlockDriverState(self, node, expect_error = False):
+ self.checkBlockDriverState(node)
+ result = self.vm.qmp('x-blockdev-del', node_name = node)
+ if expect_error:
+ self.assert_qmp(result, 'error/class', 'GenericError')
+ else:
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(node, expect_error)
+
+ # Add a device model
+ def addDeviceModel(self, device, backend):
+ result = self.vm.qmp('device_add', id = device,
+ driver = 'virtio-blk-pci', drive = backend)
+ self.assert_qmp(result, 'return', {})
+
+ # Delete a device model
+ def delDeviceModel(self, device):
+ result = self.vm.qmp('device_del', id = device)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('system_reset')
+ self.assert_qmp(result, 'return', {})
+
+ device_path = '/machine/peripheral/%s/virtio-backend' % device
+ event = self.vm.event_wait(name="DEVICE_DELETED",
+ match={'data': {'path': device_path}})
+ self.assertNotEqual(event, None)
+
+ event = self.vm.event_wait(name="DEVICE_DELETED",
+ match={'data': {'device': device}})
+ self.assertNotEqual(event, None)
+
+ # Remove a BlockDriverState
+ def ejectDrive(self, backend, node, expect_error = False,
+ destroys_media = True):
+ self.checkBlockBackend(backend, node)
+ self.checkBlockDriverState(node)
+ result = self.vm.qmp('eject', device = backend)
+ if expect_error:
+ self.assert_qmp(result, 'error/class', 'GenericError')
+ self.checkBlockDriverState(node)
+ self.checkBlockBackend(backend, node)
+ else:
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(node, not destroys_media)
+ self.checkBlockBackend(backend, None)
+
+ # Insert a BlockDriverState
+ def insertDrive(self, backend, node):
+ self.checkBlockBackend(backend, None)
+ self.checkBlockDriverState(node)
+ result = self.vm.qmp('blockdev-insert-medium',
+ device = backend, node_name = node)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockBackend(backend, node)
+ self.checkBlockDriverState(node)
+
+ # Create a snapshot using 'blockdev-snapshot-sync'
+ def createSnapshotSync(self, node, overlay):
+ self.checkBlockDriverState(node)
+ self.checkBlockDriverState(overlay, False)
+ opts = {'node-name': node,
+ 'snapshot-file': new_img,
+ 'snapshot-node-name': overlay,
+ 'format': iotests.imgfmt}
+ result = self.vm.qmp('blockdev-snapshot-sync', conv_keys=False, **opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(node)
+ self.checkBlockDriverState(overlay)
+
+ # Create a snapshot using 'blockdev-snapshot'
+ def createSnapshot(self, node, overlay):
+ self.checkBlockDriverState(node)
+ self.checkBlockDriverState(overlay)
+ result = self.vm.qmp('blockdev-snapshot',
+ node = node, overlay = overlay)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(node)
+ self.checkBlockDriverState(overlay)
+
+ # Create a mirror
+ def createMirror(self, backend, node, new_node):
+ self.checkBlockBackend(backend, node)
+ self.checkBlockDriverState(new_node, False)
+ opts = {'device': backend,
+ 'target': new_img,
+ 'node-name': new_node,
+ 'sync': 'top',
+ 'format': iotests.imgfmt}
+ result = self.vm.qmp('drive-mirror', conv_keys=False, **opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockBackend(backend, node)
+ self.checkBlockDriverState(new_node)
+
+ # Complete an existing block job
+ def completeBlockJob(self, backend, node_before, node_after):
+ self.checkBlockBackend(backend, node_before)
+ result = self.vm.qmp('block-job-complete', device=backend)
+ self.assert_qmp(result, 'return', {})
+ self.wait_until_completed(backend)
+ self.checkBlockBackend(backend, node_after)
+
+ # Add a BlkDebug node
+ # Note that the purpose of this is to test the x-blockdev-del
+ # sanity checks, not to create a usable blkdebug drive
+ def addBlkDebug(self, debug, node):
+ self.checkBlockDriverState(node, False)
+ self.checkBlockDriverState(debug, False)
+ image = {'driver': iotests.imgfmt,
+ 'node-name': node,
+ 'file': {'driver': 'file',
+ 'filename': base_img}}
+ opts = {'driver': 'blkdebug',
+ 'node-name': debug,
+ 'image': image}
+ result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(node)
+ self.checkBlockDriverState(debug)
+
+ # Add a BlkVerify node
+ # Note that the purpose of this is to test the x-blockdev-del
+ # sanity checks, not to create a usable blkverify drive
+ def addBlkVerify(self, blkverify, test, raw):
+ self.checkBlockDriverState(test, False)
+ self.checkBlockDriverState(raw, False)
+ self.checkBlockDriverState(blkverify, False)
+ iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M')
+ node_0 = {'driver': iotests.imgfmt,
+ 'node-name': test,
+ 'file': {'driver': 'file',
+ 'filename': base_img}}
+ node_1 = {'driver': iotests.imgfmt,
+ 'node-name': raw,
+ 'file': {'driver': 'file',
+ 'filename': new_img}}
+ opts = {'driver': 'blkverify',
+ 'node-name': blkverify,
+ 'test': node_0,
+ 'raw': node_1}
+ result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(test)
+ self.checkBlockDriverState(raw)
+ self.checkBlockDriverState(blkverify)
+
+ # Add a Quorum node
+ def addQuorum(self, quorum, child0, child1):
+ self.checkBlockDriverState(child0, False)
+ self.checkBlockDriverState(child1, False)
+ self.checkBlockDriverState(quorum, False)
+ iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M')
+ child_0 = {'driver': iotests.imgfmt,
+ 'node-name': child0,
+ 'file': {'driver': 'file',
+ 'filename': base_img}}
+ child_1 = {'driver': iotests.imgfmt,
+ 'node-name': child1,
+ 'file': {'driver': 'file',
+ 'filename': new_img}}
+ opts = {'driver': 'quorum',
+ 'node-name': quorum,
+ 'vote-threshold': 1,
+ 'children': [ child_0, child_1 ]}
+ result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts)
+ self.assert_qmp(result, 'return', {})
+ self.checkBlockDriverState(child0)
+ self.checkBlockDriverState(child1)
+ self.checkBlockDriverState(quorum)
+
+ ########################
+ # The tests start here #
+ ########################
+
+ def testWrongParameters(self):
+ self.addBlockBackend('drive0', 'node0')
+ result = self.vm.qmp('x-blockdev-del')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+ result = self.vm.qmp('x-blockdev-del', id='drive0', node_name='node0')
+ self.assert_qmp(result, 'error/class', 'GenericError')
+ self.delBlockBackend('drive0', 'node0')
+
+ def testBlockBackend(self):
+ self.addBlockBackend('drive0', 'node0')
+ # You cannot delete a BDS that is attached to a backend
+ self.delBlockDriverState('node0', expect_error = True)
+ self.delBlockBackend('drive0', 'node0')
+
+ def testBlockDriverState(self):
+ self.addBlockDriverState('node0')
+ # You cannot delete a file BDS directly
+ self.delBlockDriverState('node0_file', expect_error = True)
+ self.delBlockDriverState('node0')
+
+ def testEject(self):
+ self.addBlockBackend('drive0', 'node0')
+ self.ejectDrive('drive0', 'node0')
+ self.delBlockBackend('drive0', None)
+
+ def testDeviceModel(self):
+ self.addBlockBackend('drive0', 'node0')
+ self.addDeviceModel('device0', 'drive0')
+ self.ejectDrive('drive0', 'node0', expect_error = True)
+ self.delBlockBackend('drive0', 'node0', expect_error = True)
+ self.delDeviceModel('device0')
+ self.delBlockBackend('drive0', 'node0')
+
+ def testAttachMedia(self):
+ # This creates a BlockBackend and removes its media
+ self.addBlockBackend('drive0', 'node0')
+ self.ejectDrive('drive0', 'node0')
+ # This creates a new BlockDriverState and inserts it into the backend
+ self.addBlockDriverState('node1')
+ self.insertDrive('drive0', 'node1')
+ # The backend can't be removed: the new BDS has an extra reference
+ self.delBlockBackend('drive0', 'node1', expect_error = True)
+ self.delBlockDriverState('node1', expect_error = True)
+ # The BDS still exists after being ejected, but now it can be removed
+ self.ejectDrive('drive0', 'node1', destroys_media = False)
+ self.delBlockDriverState('node1')
+ self.delBlockBackend('drive0', None)
+
+ def testSnapshotSync(self):
+ self.addBlockBackend('drive0', 'node0')
+ self.createSnapshotSync('node0', 'overlay0')
+ # This fails because node0 is now being used as a backing image
+ self.delBlockDriverState('node0', expect_error = True)
+ # This succeeds because overlay0 only has the backend reference
+ self.delBlockBackend('drive0', 'overlay0')
+ self.checkBlockDriverState('node0', False)
+
+ def testSnapshot(self):
+ self.addBlockBackend('drive0', 'node0')
+ self.addBlockDriverStateOverlay('overlay0')
+ self.createSnapshot('node0', 'overlay0')
+ self.delBlockBackend('drive0', 'overlay0', expect_error = True)
+ self.delBlockDriverState('node0', expect_error = True)
+ self.delBlockDriverState('overlay0', expect_error = True)
+ self.ejectDrive('drive0', 'overlay0', destroys_media = False)
+ self.delBlockBackend('drive0', None)
+ self.delBlockDriverState('node0', expect_error = True)
+ self.delBlockDriverState('overlay0')
+ self.checkBlockDriverState('node0', False)
+
+ def testMirror(self):
+ self.addBlockBackend('drive0', 'node0')
+ self.createMirror('drive0', 'node0', 'mirror0')
+ # The block job prevents removing the device
+ self.delBlockBackend('drive0', 'node0', expect_error = True)
+ self.delBlockDriverState('node0', expect_error = True)
+ self.delBlockDriverState('mirror0', expect_error = True)
+ self.wait_ready('drive0')
+ self.completeBlockJob('drive0', 'node0', 'mirror0')
+ self.assert_no_active_block_jobs()
+ self.checkBlockDriverState('node0', False)
+ # This succeeds because the backend now points to mirror0
+ self.delBlockBackend('drive0', 'mirror0')
+
+ def testBlkDebug(self):
+ self.addBlkDebug('debug0', 'node0')
+ # 'node0' is used by the blkdebug node
+ self.delBlockDriverState('node0', expect_error = True)
+ # But we can remove the blkdebug node directly
+ self.delBlockDriverState('debug0')
+ self.checkBlockDriverState('node0', False)
+
+ def testBlkVerify(self):
+ self.addBlkVerify('verify0', 'node0', 'node1')
+ # We cannot remove the children of a blkverify device
+ self.delBlockDriverState('node0', expect_error = True)
+ self.delBlockDriverState('node1', expect_error = True)
+ # But we can remove the blkverify node directly
+ self.delBlockDriverState('verify0')
+ self.checkBlockDriverState('node0', False)
+ self.checkBlockDriverState('node1', False)
+
+ def testQuorum(self):
+ if not 'quorum' in iotests.qemu_img_pipe('--help'):
+ return
+ self.addQuorum('quorum0', 'node0', 'node1')
+ # We cannot remove the children of a Quorum device
+ self.delBlockDriverState('node0', expect_error = True)
+ self.delBlockDriverState('node1', expect_error = True)
+ # But we can remove the Quorum node directly
+ self.delBlockDriverState('quorum0')
+ self.checkBlockDriverState('node0', False)
+ self.checkBlockDriverState('node1', False)
+
+
+if __name__ == '__main__':
+ iotests.main(supported_fmts=["qcow2"])
--- /dev/null
+............
+----------------------------------------------------------------------
+Ran 12 tests
+
+OK
expunge=true
have_test_arg=false
randomize=false
-valgrind=false
cachemode=false
rm -f $tmp.list $tmp.tmp $tmp.sed
export QEMU_IO_OPTIONS=""
export CACHEMODE_IS_DEFAULT=true
export QEMU_OPTIONS="-nodefaults"
+export VALGRIND_QEMU=
for r
do
;;
-valgrind)
- valgrind=true
+ VALGRIND_QEMU='y'
xpand=false
;;
if [ "$IMGPROTO" = "nbd" ] ; then
[ "$QEMU_NBD" = "" ] && _fatal "qemu-nbd not found"
fi
-
-if $valgrind; then
- export REAL_QEMU_IO="$QEMU_IO_PROG"
- export QEMU_IO_PROG=valgrind_qemu_io
-fi
export CHECK_OPTIONS=${CHECK_OPTIONS:="-g auto"}
export PWD=`pwd`
+export _QEMU_HANDLE=0
+
# $1 = prog to look for, $2* = default pathnames if not found in $PATH
set_prog_path()
{
_qemu_wrapper()
{
- (exec "$QEMU_PROG" $QEMU_OPTIONS "$@")
+ (
+ if [ -n "${QEMU_NEED_PID}" ]; then
+ echo $BASHPID > "${TEST_DIR}/qemu-${_QEMU_HANDLE}.pid"
+ fi
+ exec "$QEMU_PROG" $QEMU_OPTIONS "$@"
+ )
}
_qemu_img_wrapper()
_qemu_io_wrapper()
{
- (exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@")
+ local VALGRIND_LOGFILE=/tmp/$$.valgrind
+ local RETVAL
+ (
+ if [ "${VALGRIND_QEMU}" == "y" ]; then
+ exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"
+ else
+ exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"
+ fi
+ )
+ RETVAL=$?
+ if [ "${VALGRIND_QEMU}" == "y" ]; then
+ if [ $RETVAL == 99 ]; then
+ cat "${VALGRIND_LOGFILE}"
+ fi
+ rm -f "${VALGRIND_LOGFILE}"
+ fi
+ (exit $RETVAL)
}
_qemu_nbd_wrapper()
{
- (exec "$QEMU_NBD_PROG" $QEMU_NBD_OPTIONS "$@")
+ (
+ echo $BASHPID > "${TEST_DIR}/qemu-nbd.pid"
+ exec "$QEMU_NBD_PROG" $QEMU_NBD_OPTIONS "$@"
+ )
}
export QEMU=_qemu_wrapper
QEMU_FIFO_IN="${TEST_DIR}/qmp-in-$$"
QEMU_FIFO_OUT="${TEST_DIR}/qmp-out-$$"
-QEMU_PID=
-_QEMU_HANDLE=0
QEMU_HANDLE=0
# If bash version is >= 4.1, these will be overwritten and dynamic
mkfifo "${fifo_out}"
mkfifo "${fifo_in}"
+ QEMU_NEED_PID='y'\
${QEMU} -nographic -serial none ${comm} -machine accel=qtest "${@}" \
>"${fifo_out}" \
2>&1 \
<"${fifo_in}" &
- QEMU_PID[${_QEMU_HANDLE}]=$!
if [[ "${BASH_VERSINFO[0]}" -ge "5" ||
("${BASH_VERSINFO[0]}" -ge "4" && "${BASH_VERSINFO[1]}" -ge "1") ]]
# QEMU_PID[], QEMU_IN[], QEMU_OUT[] all use same indices
for i in "${!QEMU_OUT[@]}"
do
- if [ -z "${wait}" ]; then
- kill -KILL ${QEMU_PID[$i]} 2>/dev/null
+ local QEMU_PID
+ if [ -f "${TEST_DIR}/qemu-${i}.pid" ]; then
+ read QEMU_PID < "${TEST_DIR}/qemu-${i}.pid"
+ rm -f "${TEST_DIR}/qemu-${i}.pid"
+ if [ -z "${wait}" ] && [ -n "${QEMU_PID}" ]; then
+ kill -KILL ${QEMU_PID} 2>/dev/null
+ fi
+ if [ -n "${QEMU_PID}" ]; then
+ wait ${QEMU_PID} 2>/dev/null # silent kill
+ fi
fi
- wait ${QEMU_PID[$i]} 2>/dev/null # silent kill
+
if [ -n "${wait}" ]; then
cat <&${QEMU_OUT[$i]} | _filter_testdir | _filter_qemu \
| _filter_qemu_io | _filter_qmp
TEST_IMG=$IMGPROTO:$TEST_DIR/t.$IMGFMT
fi
-function valgrind_qemu_io()
-{
- valgrind --log-file=/tmp/$$.valgrind --error-exitcode=99 $REAL_QEMU_IO "$@"
- if [ $? != 0 ]; then
- cat /tmp/$$.valgrind
- fi
- rm -f /tmp/$$.valgrind
-}
-
-
_optstr_add()
{
if [ -n "$1" ]; then
# Start an NBD server on the image file, which is what we'll be talking to
if [ $IMGPROTO = "nbd" ]; then
eval "$QEMU_NBD -v -t -b 127.0.0.1 -p 10810 -f $IMGFMT $TEST_IMG_FILE &"
- QEMU_NBD_PID=$!
sleep 1 # FIXME: qemu-nbd needs to be listening before we continue
fi
}
case "$IMGPROTO" in
nbd)
- if [ -n "$QEMU_NBD_PID" ]; then
- kill $QEMU_NBD_PID
+ if [ -f "${TEST_DIR}/qemu-nbd.pid" ]; then
+ local QEMU_NBD_PID
+ read QEMU_NBD_PID < "${TEST_DIR}/qemu-nbd.pid"
+ kill ${QEMU_NBD_PID}
+ rm -f "${TEST_DIR}/qemu-nbd.pid"
fi
rm -f "$TEST_IMG_FILE"
;;
114 rw auto quick
115 rw auto
116 rw auto quick
+118 rw auto
119 rw auto quick
120 rw auto quick
121 rw auto
135 rw auto
137 rw auto
138 rw auto quick
+139 rw auto quick
}
Error *err = NULL;
- qmp_change_blockdev([drive cStringUsingEncoding: NSASCIIStringEncoding],
- [file cStringUsingEncoding: NSASCIIStringEncoding],
- "raw",
- &err);
+ qmp_blockdev_change_medium([drive cStringUsingEncoding:
+ NSASCIIStringEncoding],
+ [file cStringUsingEncoding:
+ NSASCIIStringEncoding],
+ true, "raw",
+ false, 0,
+ &err);
handleAnyDeviceErrors(err);
}
}