mirror of https://github.com/xemu-project/xemu.git
Merge remote-tracking branch 'qmp/queue/qmp' into staging
This commit is contained in:
commit
9bf4896e5d
9
Makefile
9
Makefile
|
@ -169,7 +169,7 @@ test-coroutine: test-coroutine.o qemu-timer-common.o async.o $(coroutine-obj-y)
|
|||
|
||||
$(qapi-obj-y): $(GENERATED_HEADERS)
|
||||
qapi-dir := $(BUILD_DIR)/qapi-generated
|
||||
test-visitor.o test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS += -I $(qapi-dir)
|
||||
test-qmp-input-visitor.o test-qmp-output-visitor.o test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS += -I $(qapi-dir)
|
||||
qemu-ga$(EXESUF): LIBS = $(LIBS_QGA)
|
||||
|
||||
$(qapi-dir)/test-qapi-types.c $(qapi-dir)/test-qapi-types.h :\
|
||||
|
@ -202,8 +202,11 @@ qmp-commands.h qmp-marshal.c :\
|
|||
$(SRC_PATH)/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py -m -o "." < $<, " GEN $@")
|
||||
|
||||
test-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
|
||||
test-visitor: test-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
|
||||
test-qmp-output-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
|
||||
test-qmp-output-visitor: test-qmp-output-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
|
||||
|
||||
test-qmp-input-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
|
||||
test-qmp-input-visitor: test-qmp-input-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
|
||||
|
||||
test-qmp-commands.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h test-qmp-marshal.c test-qmp-commands.h) $(qapi-obj-y)
|
||||
test-qmp-commands: test-qmp-commands.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o $(qapi-dir)/test-qmp-marshal.o module.o
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# QMP command line tool
|
||||
#
|
||||
# Copyright IBM, Corp. 2011
|
||||
#
|
||||
# Authors:
|
||||
# Anthony Liguori <aliguori@us.ibm.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPLv2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
import sys, os
|
||||
from qmp import QEMUMonitorProtocol
|
||||
|
||||
def print_response(rsp, prefix=[]):
|
||||
if type(rsp) == list:
|
||||
i = 0
|
||||
for item in rsp:
|
||||
if prefix == []:
|
||||
prefix = ['item']
|
||||
print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
|
||||
i += 1
|
||||
elif type(rsp) == dict:
|
||||
for key in rsp.keys():
|
||||
print_response(rsp[key], prefix + [key])
|
||||
else:
|
||||
if len(prefix):
|
||||
print '%s: %s' % ('.'.join(prefix), rsp)
|
||||
else:
|
||||
print '%s' % (rsp)
|
||||
|
||||
def main(args):
|
||||
path = None
|
||||
|
||||
# Use QMP_PATH if it's set
|
||||
if os.environ.has_key('QMP_PATH'):
|
||||
path = os.environ['QMP_PATH']
|
||||
|
||||
while len(args):
|
||||
arg = args[0]
|
||||
|
||||
if arg.startswith('--'):
|
||||
arg = arg[2:]
|
||||
if arg.find('=') == -1:
|
||||
value = True
|
||||
else:
|
||||
arg, value = arg.split('=', 1)
|
||||
|
||||
if arg in ['path']:
|
||||
if type(value) == str:
|
||||
path = value
|
||||
elif arg in ['help']:
|
||||
os.execlp('man', 'man', 'qmp')
|
||||
else:
|
||||
print 'Unknown argument "%s"' % arg
|
||||
|
||||
args = args[1:]
|
||||
else:
|
||||
break
|
||||
|
||||
if not path:
|
||||
print "QMP path isn't set, use --path=qmp-monitor-address or set QMP_PATH"
|
||||
return 1
|
||||
|
||||
if len(args):
|
||||
command, args = args[0], args[1:]
|
||||
else:
|
||||
print 'No command found'
|
||||
print 'Usage: "qmp [--path=qmp-monitor-address] qmp-cmd arguments"'
|
||||
return 1
|
||||
|
||||
if command in ['help']:
|
||||
os.execlp('man', 'man', 'qmp')
|
||||
|
||||
srv = QEMUMonitorProtocol(path)
|
||||
srv.connect()
|
||||
|
||||
def do_command(srv, cmd, **kwds):
|
||||
rsp = srv.cmd(cmd, kwds)
|
||||
if rsp.has_key('error'):
|
||||
raise Exception(rsp['error']['desc'])
|
||||
return rsp['return']
|
||||
|
||||
commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
|
||||
|
||||
srv.close()
|
||||
|
||||
if command not in commands:
|
||||
fullcmd = 'qmp-%s' % command
|
||||
try:
|
||||
os.environ['QMP_PATH'] = path
|
||||
os.execvp(fullcmd, [fullcmd] + args)
|
||||
except OSError, (errno, msg):
|
||||
if errno == 2:
|
||||
print 'Command "%s" not found.' % (fullcmd)
|
||||
return 1
|
||||
raise
|
||||
return 0
|
||||
|
||||
srv = QEMUMonitorProtocol(path)
|
||||
srv.connect()
|
||||
|
||||
arguments = {}
|
||||
for arg in args:
|
||||
if not arg.startswith('--'):
|
||||
print 'Unknown argument "%s"' % arg
|
||||
return 1
|
||||
|
||||
arg = arg[2:]
|
||||
if arg.find('=') == -1:
|
||||
value = True
|
||||
else:
|
||||
arg, value = arg.split('=', 1)
|
||||
|
||||
if arg in ['help']:
|
||||
os.execlp('man', 'man', 'qmp-%s' % command)
|
||||
return 1
|
||||
|
||||
arguments[arg] = value
|
||||
|
||||
rsp = do_command(srv, command, **arguments)
|
||||
print_response(rsp)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
28
balloon.c
28
balloon.c
|
@ -100,31 +100,19 @@ BalloonInfo *qmp_query_balloon(Error **errp)
|
|||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* do_balloon(): Request VM to change its memory allocation
|
||||
*/
|
||||
int do_balloon(Monitor *mon, const QDict *params,
|
||||
MonitorCompletion cb, void *opaque)
|
||||
void qmp_balloon(int64_t value, Error **errp)
|
||||
{
|
||||
int64_t target;
|
||||
int ret;
|
||||
|
||||
if (kvm_enabled() && !kvm_has_sync_mmu()) {
|
||||
qerror_report(QERR_KVM_MISSING_CAP, "synchronous MMU", "balloon");
|
||||
return -1;
|
||||
error_set(errp, QERR_KVM_MISSING_CAP, "synchronous MMU", "balloon");
|
||||
return;
|
||||
}
|
||||
|
||||
target = qdict_get_int(params, "value");
|
||||
if (target <= 0) {
|
||||
if (value <= 0) {
|
||||
qerror_report(QERR_INVALID_PARAMETER_VALUE, "target", "a size");
|
||||
return -1;
|
||||
return;
|
||||
}
|
||||
ret = qemu_balloon(target);
|
||||
if (ret == 0) {
|
||||
qerror_report(QERR_DEVICE_NOT_ACTIVE, "balloon");
|
||||
return -1;
|
||||
|
||||
if (qemu_balloon(value) == 0) {
|
||||
error_set(errp, QERR_DEVICE_NOT_ACTIVE, "balloon");
|
||||
}
|
||||
|
||||
cb(opaque, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,4 @@ int qemu_add_balloon_handler(QEMUBalloonEvent *event_func,
|
|||
QEMUBalloonStatus *stat_func, void *opaque);
|
||||
void qemu_remove_balloon_handler(void *opaque);
|
||||
|
||||
int do_balloon(Monitor *mon, const QDict *params,
|
||||
MonitorCompletion cb, void *opaque);
|
||||
|
||||
#endif
|
||||
|
|
89
blockdev.c
89
blockdev.c
|
@ -15,6 +15,7 @@
|
|||
#include "qemu-config.h"
|
||||
#include "sysemu.h"
|
||||
#include "block_int.h"
|
||||
#include "qmp-commands.h"
|
||||
|
||||
static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
|
||||
|
||||
|
@ -600,28 +601,20 @@ void do_commit(Monitor *mon, const QDict *qdict)
|
|||
}
|
||||
}
|
||||
|
||||
int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
void qmp_blockdev_snapshot_sync(const char *device, const char *snapshot_file,
|
||||
bool has_format, const char *format,
|
||||
Error **errp)
|
||||
{
|
||||
const char *device = qdict_get_str(qdict, "device");
|
||||
const char *filename = qdict_get_try_str(qdict, "snapshot-file");
|
||||
const char *format = qdict_get_try_str(qdict, "format");
|
||||
BlockDriverState *bs;
|
||||
BlockDriver *drv, *old_drv, *proto_drv;
|
||||
int ret = 0;
|
||||
int flags;
|
||||
char old_filename[1024];
|
||||
|
||||
if (!filename) {
|
||||
qerror_report(QERR_MISSING_PARAMETER, "snapshot-file");
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
bs = bdrv_find(device);
|
||||
if (!bs) {
|
||||
qerror_report(QERR_DEVICE_NOT_FOUND, device);
|
||||
ret = -1;
|
||||
goto out;
|
||||
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
|
||||
return;
|
||||
}
|
||||
|
||||
pstrcpy(old_filename, sizeof(old_filename), bs->filename);
|
||||
|
@ -629,35 +622,34 @@ int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
|||
old_drv = bs->drv;
|
||||
flags = bs->open_flags;
|
||||
|
||||
if (!format) {
|
||||
if (!has_format) {
|
||||
format = "qcow2";
|
||||
}
|
||||
|
||||
drv = bdrv_find_format(format);
|
||||
if (!drv) {
|
||||
qerror_report(QERR_INVALID_BLOCK_FORMAT, format);
|
||||
ret = -1;
|
||||
goto out;
|
||||
error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
|
||||
return;
|
||||
}
|
||||
|
||||
proto_drv = bdrv_find_protocol(filename);
|
||||
proto_drv = bdrv_find_protocol(snapshot_file);
|
||||
if (!proto_drv) {
|
||||
qerror_report(QERR_INVALID_BLOCK_FORMAT, format);
|
||||
ret = -1;
|
||||
goto out;
|
||||
error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = bdrv_img_create(filename, format, bs->filename,
|
||||
ret = bdrv_img_create(snapshot_file, format, bs->filename,
|
||||
bs->drv->format_name, NULL, -1, flags);
|
||||
if (ret) {
|
||||
goto out;
|
||||
error_set(errp, QERR_UNDEFINED_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
bdrv_drain_all();
|
||||
bdrv_flush(bs);
|
||||
|
||||
bdrv_close(bs);
|
||||
ret = bdrv_open(bs, filename, flags, drv);
|
||||
ret = bdrv_open(bs, snapshot_file, flags, drv);
|
||||
/*
|
||||
* If reopening the image file we just created fails, fall back
|
||||
* and try to re-open the original image. If that fails too, we
|
||||
|
@ -666,17 +658,11 @@ int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
|||
if (ret != 0) {
|
||||
ret = bdrv_open(bs, old_filename, flags, old_drv);
|
||||
if (ret != 0) {
|
||||
qerror_report(QERR_OPEN_FILE_FAILED, old_filename);
|
||||
error_set(errp, QERR_OPEN_FILE_FAILED, old_filename);
|
||||
} else {
|
||||
qerror_report(QERR_OPEN_FILE_FAILED, filename);
|
||||
error_set(errp, QERR_OPEN_FILE_FAILED, snapshot_file);
|
||||
}
|
||||
}
|
||||
out:
|
||||
if (ret) {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int eject_device(Monitor *mon, BlockDriverState *bs, int force)
|
||||
|
@ -710,28 +696,25 @@ int do_eject(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
|||
return eject_device(mon, bs, force);
|
||||
}
|
||||
|
||||
int do_block_set_passwd(Monitor *mon, const QDict *qdict,
|
||||
QObject **ret_data)
|
||||
void qmp_block_passwd(const char *device, const char *password, Error **errp)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
int err;
|
||||
|
||||
bs = bdrv_find(qdict_get_str(qdict, "device"));
|
||||
bs = bdrv_find(device);
|
||||
if (!bs) {
|
||||
qerror_report(QERR_DEVICE_NOT_FOUND, qdict_get_str(qdict, "device"));
|
||||
return -1;
|
||||
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
|
||||
return;
|
||||
}
|
||||
|
||||
err = bdrv_set_key(bs, qdict_get_str(qdict, "password"));
|
||||
err = bdrv_set_key(bs, password);
|
||||
if (err == -EINVAL) {
|
||||
qerror_report(QERR_DEVICE_NOT_ENCRYPTED, bdrv_get_device_name(bs));
|
||||
return -1;
|
||||
error_set(errp, QERR_DEVICE_NOT_ENCRYPTED, bdrv_get_device_name(bs));
|
||||
return;
|
||||
} else if (err < 0) {
|
||||
qerror_report(QERR_INVALID_PASSWORD);
|
||||
return -1;
|
||||
error_set(errp, QERR_INVALID_PASSWORD);
|
||||
return;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_change_block(Monitor *mon, const char *device,
|
||||
|
@ -863,27 +846,23 @@ int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
|||
* existing QERR_ macro mess is cleaned up. A good example for better
|
||||
* error reports can be found in the qemu-img resize code.
|
||||
*/
|
||||
int do_block_resize(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
void qmp_block_resize(const char *device, int64_t size, Error **errp)
|
||||
{
|
||||
const char *device = qdict_get_str(qdict, "device");
|
||||
int64_t size = qdict_get_int(qdict, "size");
|
||||
BlockDriverState *bs;
|
||||
|
||||
bs = bdrv_find(device);
|
||||
if (!bs) {
|
||||
qerror_report(QERR_DEVICE_NOT_FOUND, device);
|
||||
return -1;
|
||||
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
qerror_report(QERR_UNDEFINED_ERROR);
|
||||
return -1;
|
||||
error_set(errp, QERR_UNDEFINED_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bdrv_truncate(bs, size)) {
|
||||
qerror_report(QERR_UNDEFINED_ERROR);
|
||||
return -1;
|
||||
error_set(errp, QERR_UNDEFINED_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -59,13 +59,9 @@ DriveInfo *add_init_drive(const char *opts);
|
|||
|
||||
void do_commit(Monitor *mon, const QDict *qdict);
|
||||
int do_eject(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
int do_block_set_passwd(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
int do_change_block(Monitor *mon, const char *device,
|
||||
const char *filename, const char *fmt);
|
||||
int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
int do_block_set_io_throttle(Monitor *mon,
|
||||
const QDict *qdict, QObject **ret_data);
|
||||
int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
int do_block_resize(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2740,8 +2740,9 @@ if test "$softmmu" = yes ; then
|
|||
fi
|
||||
if [ "$check_utests" = "yes" ]; then
|
||||
checks="check-qint check-qstring check-qdict check-qlist"
|
||||
checks="check-qfloat check-qjson test-coroutine $checks"
|
||||
checks="check-qfloat check-qjson $checks"
|
||||
fi
|
||||
test_progs="$checks test-coroutine test-qmp-output-visitor test-qmp-input-visitor"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -3227,7 +3228,7 @@ if test "$trace_default" = "yes"; then
|
|||
fi
|
||||
|
||||
echo "TOOLS=$tools" >> $config_host_mak
|
||||
echo "CHECKS=$checks" >> $config_host_mak
|
||||
echo "CHECKS=$test_progs" >> $config_host_mak
|
||||
echo "ROMS=$roms" >> $config_host_mak
|
||||
echo "MAKE=$make" >> $config_host_mak
|
||||
echo "INSTALL=$install" >> $config_host_mak
|
||||
|
|
|
@ -74,8 +74,6 @@ struct MouseTransformInfo {
|
|||
int a[7];
|
||||
};
|
||||
|
||||
void do_info_mice_print(Monitor *mon, const QObject *data);
|
||||
void do_info_mice(Monitor *mon, QObject **ret_data);
|
||||
void do_mouse_set(Monitor *mon, const QDict *qdict);
|
||||
|
||||
/* keysym is a unicode code except for special keys (see QEMU_KEY_xxx
|
||||
|
|
90
cpus.c
90
cpus.c
|
@ -1136,3 +1136,93 @@ CpuInfoList *qmp_query_cpus(Error **errp)
|
|||
|
||||
return head;
|
||||
}
|
||||
|
||||
void qmp_memsave(int64_t addr, int64_t size, const char *filename,
|
||||
bool has_cpu, int64_t cpu_index, Error **errp)
|
||||
{
|
||||
FILE *f;
|
||||
uint32_t l;
|
||||
CPUState *env;
|
||||
uint8_t buf[1024];
|
||||
|
||||
if (!has_cpu) {
|
||||
cpu_index = 0;
|
||||
}
|
||||
|
||||
for (env = first_cpu; env; env = env->next_cpu) {
|
||||
if (cpu_index == env->cpu_index) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (env == NULL) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
|
||||
"a CPU number");
|
||||
return;
|
||||
}
|
||||
|
||||
f = fopen(filename, "wb");
|
||||
if (!f) {
|
||||
error_set(errp, QERR_OPEN_FILE_FAILED, filename);
|
||||
return;
|
||||
}
|
||||
|
||||
while (size != 0) {
|
||||
l = sizeof(buf);
|
||||
if (l > size)
|
||||
l = size;
|
||||
cpu_memory_rw_debug(env, addr, buf, l, 0);
|
||||
if (fwrite(buf, 1, l, f) != l) {
|
||||
error_set(errp, QERR_IO_ERROR);
|
||||
goto exit;
|
||||
}
|
||||
addr += l;
|
||||
size -= l;
|
||||
}
|
||||
|
||||
exit:
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void qmp_pmemsave(int64_t addr, int64_t size, const char *filename,
|
||||
Error **errp)
|
||||
{
|
||||
FILE *f;
|
||||
uint32_t l;
|
||||
uint8_t buf[1024];
|
||||
|
||||
f = fopen(filename, "wb");
|
||||
if (!f) {
|
||||
error_set(errp, QERR_OPEN_FILE_FAILED, filename);
|
||||
return;
|
||||
}
|
||||
|
||||
while (size != 0) {
|
||||
l = sizeof(buf);
|
||||
if (l > size)
|
||||
l = size;
|
||||
cpu_physical_memory_rw(addr, buf, l, 0);
|
||||
if (fwrite(buf, 1, l, f) != l) {
|
||||
error_set(errp, QERR_IO_ERROR);
|
||||
goto exit;
|
||||
}
|
||||
addr += l;
|
||||
size -= l;
|
||||
}
|
||||
|
||||
exit:
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void qmp_inject_nmi(Error **errp)
|
||||
{
|
||||
#if defined(TARGET_I386)
|
||||
CPUState *env;
|
||||
|
||||
for (env = first_cpu; env != NULL; env = env->next_cpu) {
|
||||
cpu_interrupt(env, CPU_INTERRUPT_NMI);
|
||||
}
|
||||
#else
|
||||
error_set(errp, QERR_UNSUPPORTED);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -0,0 +1,642 @@
|
|||
= How to write QMP commands using the QAPI framework =
|
||||
|
||||
This document is a step-by-step guide on how to write new QMP commands using
|
||||
the QAPI framework. It also shows how to implement new style HMP commands.
|
||||
|
||||
This document doesn't discuss QMP protocol level details, nor does it dive
|
||||
into the QAPI framework implementation.
|
||||
|
||||
For an in-depth introduction to the QAPI framework, please refer to
|
||||
docs/qapi-code-gen.txt. For documentation about the QMP protocol, please
|
||||
check the files in QMP/.
|
||||
|
||||
== Overview ==
|
||||
|
||||
Generally speaking, the following steps should be taken in order to write a
|
||||
new QMP command.
|
||||
|
||||
1. Write the command's and type(s) specification in the QAPI schema file
|
||||
(qapi-schema.json in the root source directory)
|
||||
|
||||
2. Write the QMP command itself, which is a regular C function. Preferably,
|
||||
the command should be exported by some QEMU subsystem. But it can also be
|
||||
added to the qmp.c file
|
||||
|
||||
3. At this point the command can be tested under the QMP protocol
|
||||
|
||||
4. Write the HMP command equivalent. This is not required and should only be
|
||||
done if it does make sense to have the functionality in HMP. The HMP command
|
||||
is implemented in terms of the QMP command
|
||||
|
||||
The following sections will demonstrate each of the steps above. We will start
|
||||
very simple and get more complex as we progress.
|
||||
|
||||
=== Testing ===
|
||||
|
||||
For all the examples in the next sections, the test setup is the same and is
|
||||
shown here.
|
||||
|
||||
First, QEMU should be started as:
|
||||
|
||||
# /path/to/your/source/qemu [...] \
|
||||
-chardev socket,id=qmp,port=4444,host=localhost,server \
|
||||
-mon chardev=qmp,mode=control,pretty=on
|
||||
|
||||
Then, in a different terminal:
|
||||
|
||||
$ telnet localhost 4444
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
{
|
||||
"QMP": {
|
||||
"version": {
|
||||
"qemu": {
|
||||
"micro": 50,
|
||||
"minor": 15,
|
||||
"major": 0
|
||||
},
|
||||
"package": ""
|
||||
},
|
||||
"capabilities": [
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
The above output is the QMP server saying you're connected. The server is
|
||||
actually in capabilities negotiation mode. To enter in command mode type:
|
||||
|
||||
{ "execute": "qmp_capabilities" }
|
||||
|
||||
Then the server should respond:
|
||||
|
||||
{
|
||||
"return": {
|
||||
}
|
||||
}
|
||||
|
||||
Which is QMP's way of saying "the latest command executed OK and didn't return
|
||||
any data". Now you're ready to enter the QMP example commands as explained in
|
||||
the following sections.
|
||||
|
||||
== Writing a command that doesn't return data ==
|
||||
|
||||
That's the most simple QMP command that can be written. Usually, this kind of
|
||||
command carries some meaningful action in QEMU but here it will just print
|
||||
"Hello, world" to the standard output.
|
||||
|
||||
Our command will be called "hello-world". It takes no arguments, nor does it
|
||||
return any data.
|
||||
|
||||
The first step is to add the following line to the bottom of the
|
||||
qapi-schema.json file:
|
||||
|
||||
{ 'command': 'hello-world' }
|
||||
|
||||
The "command" keyword defines a new QMP command. It's an JSON object. All
|
||||
schema entries are JSON objects. The line above will instruct the QAPI to
|
||||
generate any prototypes and the necessary code to marshal and unmarshal
|
||||
protocol data.
|
||||
|
||||
The next step is to write the "hello-world" implementation. As explained
|
||||
earlier, it's preferable for commands to live in QEMU subsystems. But
|
||||
"hello-world" doesn't pertain to any, so we put its implementation in qmp.c:
|
||||
|
||||
void qmp_hello_world(Error **errp)
|
||||
{
|
||||
printf("Hello, world!\n");
|
||||
}
|
||||
|
||||
There are a few things to be noticed:
|
||||
|
||||
1. QMP command implementation functions must be prefixed with "qmp_"
|
||||
2. qmp_hello_world() returns void, this is in accordance with the fact that the
|
||||
command doesn't return any data
|
||||
3. It takes an "Error **" argument. This is required. Later we will see how to
|
||||
return errors and take additional arguments. The Error argument should not
|
||||
be touched if the command doesn't return errors
|
||||
4. We won't add the function's prototype. That's automatically done by the QAPI
|
||||
5. Printing to the terminal is discouraged for QMP commands, we do it here
|
||||
because it's the easiest way to demonstrate a QMP command
|
||||
|
||||
Now a little hack is needed. As we're still using the old QMP server we need
|
||||
to add the new command to its internal dispatch table. This step won't be
|
||||
required in the near future. Open the qmp-commands.hx file and add the
|
||||
following in the botton:
|
||||
|
||||
{
|
||||
.name = "hello-world",
|
||||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_marshal_input_hello_world,
|
||||
},
|
||||
|
||||
You're done. Now build qemu, run it as suggested in the "Testing" section,
|
||||
and then type the following QMP command:
|
||||
|
||||
{ "execute": "hello-world" }
|
||||
|
||||
Then check the terminal running qemu and look for the "Hello, world" string. If
|
||||
you don't see it then something went wrong.
|
||||
|
||||
=== Arguments ===
|
||||
|
||||
Let's add an argument called "message" to our "hello-world" command. The new
|
||||
argument will contain the string to be printed to stdout. It's an optional
|
||||
argument, if it's not present we print our default "Hello, World" string.
|
||||
|
||||
The first change we have to do is to modify the command specification in the
|
||||
schema file to the following:
|
||||
|
||||
{ 'command': 'hello-world', 'data': { '*message': 'str' } }
|
||||
|
||||
Notice the new 'data' member in the schema. It's an JSON object whose each
|
||||
element is an argument to the command in question. Also notice the asterisk,
|
||||
it's used to mark the argument optional (that means that you shouldn't use it
|
||||
for mandatory arguments). Finally, 'str' is the argument's type, which
|
||||
stands for "string". The QAPI also supports integers, booleans, enumerations
|
||||
and user defined types.
|
||||
|
||||
Now, let's update our C implementation in qmp.c:
|
||||
|
||||
void qmp_hello_world(bool has_message, const char *message, Error **errp)
|
||||
{
|
||||
if (has_message) {
|
||||
printf("%s\n", message);
|
||||
} else {
|
||||
printf("Hello, world\n");
|
||||
}
|
||||
}
|
||||
|
||||
There are two important details to be noticed:
|
||||
|
||||
1. All optional arguments are accompanied by a 'has_' boolean, which is set
|
||||
if the optional argument is present or false otherwise
|
||||
2. The C implementation signature must follow the schema's argument ordering,
|
||||
which is defined by the "data" member
|
||||
|
||||
The last step is to update the qmp-commands.hx file:
|
||||
|
||||
{
|
||||
.name = "hello-world",
|
||||
.args_type = "message:s?",
|
||||
.mhandler.cmd_new = qmp_marshal_input_hello_world,
|
||||
},
|
||||
|
||||
Notice that the "args_type" member got our "message" argument. The character
|
||||
"s" stands for "string" and "?" means it's optional. This too must be ordered
|
||||
according to the C implementation and schema file. You can look for more
|
||||
examples in the qmp-commands.hx file if you need to define more arguments.
|
||||
|
||||
Again, this step won't be required in the future.
|
||||
|
||||
Time to test our new version of the "hello-world" command. Build qemu, run it as
|
||||
described in the "Testing" section and then send two commands:
|
||||
|
||||
{ "execute": "hello-world" }
|
||||
{
|
||||
"return": {
|
||||
}
|
||||
}
|
||||
|
||||
{ "execute": "hello-world", "arguments": { "message": "We love qemu" } }
|
||||
{
|
||||
"return": {
|
||||
}
|
||||
}
|
||||
|
||||
You should see "Hello, world" and "we love qemu" in the terminal running qemu,
|
||||
if you don't see these strings, then something went wrong.
|
||||
|
||||
=== Errors ===
|
||||
|
||||
QMP commands should use the error interface exported by the error.h header
|
||||
file. The basic function used to set an error is the error_set() one.
|
||||
|
||||
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 the return the
|
||||
InvalidParameter error.
|
||||
|
||||
Only one change is required, and it's in the C implementation:
|
||||
|
||||
void qmp_hello_world(bool has_message, const char *message, Error **errp)
|
||||
{
|
||||
if (has_message) {
|
||||
if (strstr(message, "love")) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER, "message");
|
||||
return;
|
||||
}
|
||||
printf("%s\n", message);
|
||||
} else {
|
||||
printf("Hello, world\n");
|
||||
}
|
||||
}
|
||||
|
||||
Let's test it. Build qemu, run it as defined in the "Testing" section, and
|
||||
then issue the following command:
|
||||
|
||||
{ "execute": "hello-world", "arguments": { "message": "we love qemu" } }
|
||||
|
||||
The QMP server's response should be:
|
||||
|
||||
{
|
||||
"error": {
|
||||
"class": "InvalidParameter",
|
||||
"desc": "Invalid parameter 'message'",
|
||||
"data": {
|
||||
"name": "message"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Which is the InvalidParameter error.
|
||||
|
||||
When you have to return an error but you're unsure what error to return or
|
||||
which arguments an error takes, you should look at the qerror.h file. Note
|
||||
that you might be required to add new errors if needed.
|
||||
|
||||
FIXME: describe better the error API and how to add new errors.
|
||||
|
||||
=== Command Documentation ===
|
||||
|
||||
There's only one step missing to make "hello-world"'s implementation complete,
|
||||
and that's its documentation in the schema file.
|
||||
|
||||
This is very important. No QMP command will be accepted in QEMU without proper
|
||||
documentation.
|
||||
|
||||
There are many examples of such documentation in the schema file already, but
|
||||
here goes "hello-world"'s new entry for the qapi-schema.json file:
|
||||
|
||||
##
|
||||
# @hello-world
|
||||
#
|
||||
# Print a client provided string to the standard output stream.
|
||||
#
|
||||
# @message: #optional string to be printed
|
||||
#
|
||||
# Returns: Nothing on success.
|
||||
# If @message contains "love", InvalidParameter
|
||||
#
|
||||
# Notes: if @message is not provided, the "Hello, world" string will
|
||||
# be printed instead
|
||||
#
|
||||
# Since: <next qemu stable release, eg. 1.0>
|
||||
##
|
||||
{ 'command': 'hello-world', 'data': { '*message': 'str' } }
|
||||
|
||||
Please, note that the "Returns" clause is optional if a command doesn't return
|
||||
any data nor any errors.
|
||||
|
||||
=== Implementing the HMP command ===
|
||||
|
||||
Now that the QMP command is in place, we can also make it available in the human
|
||||
monitor (HMP).
|
||||
|
||||
With the introduction of the QAPI, HMP commands make QMP calls. Most of the
|
||||
time HMP commands are simple wrappers. All HMP commands implementation exist in
|
||||
the hmp.c file.
|
||||
|
||||
Here's the implementation of the "hello-world" HMP command:
|
||||
|
||||
void hmp_hello_world(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *message = qdict_get_try_str(qdict, "message");
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_hello_world(!!message, message, &errp);
|
||||
if (error_is_set(&errp)) {
|
||||
monitor_printf(mon, "%s\n", error_get_pretty(errp));
|
||||
error_free(errp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Also, you have to add the function's prototype to the hmp.h file.
|
||||
|
||||
There are three important points to be noticed:
|
||||
|
||||
1. The "mon" and "qdict" arguments are mandatory for all HMP functions. The
|
||||
former is the monitor object. The latter is how the monitor passes
|
||||
arguments entered by the user to the command implementation
|
||||
2. hmp_hello_world() performs error checking. In this example we just print
|
||||
the error description to the user, but we could do more, like taking
|
||||
different actions depending on the error qmp_hello_world() returns
|
||||
3. The "errp" variable must be initialized to NULL before performing the
|
||||
QMP call
|
||||
|
||||
There's one last step to actually make the command available to monitor users,
|
||||
we should add it to the hmp-commands.hx file:
|
||||
|
||||
{
|
||||
.name = "hello-world",
|
||||
.args_type = "message:s?",
|
||||
.params = "hello-world [message]",
|
||||
.help = "Print message to the standard output",
|
||||
.mhandler.cmd = hmp_hello_world,
|
||||
},
|
||||
|
||||
STEXI
|
||||
@item hello_world @var{message}
|
||||
@findex hello_world
|
||||
Print message to the standard output
|
||||
ETEXI
|
||||
|
||||
To test this you have to open a user monitor and issue the "hello-world"
|
||||
command. It might be instructive to check the command's documentation with
|
||||
HMP's "help" command.
|
||||
|
||||
Please, check the "-monitor" command-line option to know how to open a user
|
||||
monitor.
|
||||
|
||||
== Writing a command that returns data ==
|
||||
|
||||
A QMP command is capable of returning any data the QAPI supports like integers,
|
||||
strings, booleans, enumerations and user defined types.
|
||||
|
||||
In this section we will focus on user defined types. Please, check the QAPI
|
||||
documentation for information about the other types.
|
||||
|
||||
=== User Defined Types ===
|
||||
|
||||
For this example we will write the query-alarm-clock command, which returns
|
||||
information about QEMU's timer alarm. For more information about it, please
|
||||
check the "-clock" command-line option.
|
||||
|
||||
We want to return two pieces of information. The first one is the alarm clock's
|
||||
name. The second one is when the next alarm will fire. The former information is
|
||||
returned as a string, the latter is an integer in nanoseconds (which is not
|
||||
very useful in practice, as the timer has probably already fired when the
|
||||
information reaches the client).
|
||||
|
||||
The best way to return that data is to create a new QAPI type, as shown below:
|
||||
|
||||
##
|
||||
# @QemuAlarmClock
|
||||
#
|
||||
# QEMU alarm clock information.
|
||||
#
|
||||
# @clock-name: The alarm clock method's name.
|
||||
#
|
||||
# @next-deadline: #optional The time (in nanoseconds) the next alarm will fire.
|
||||
#
|
||||
# Since: 1.0
|
||||
##
|
||||
{ 'type': 'QemuAlarmClock',
|
||||
'data': { 'clock-name': 'str', '*next-deadline': 'int' } }
|
||||
|
||||
The "type" keyword defines a new QAPI type. Its "data" member contains the
|
||||
type's members. In this example our members are the "clock-name" and the
|
||||
"next-deadline" one, which is optional.
|
||||
|
||||
Now let's define the query-alarm-clock command:
|
||||
|
||||
##
|
||||
# @query-alarm-clock
|
||||
#
|
||||
# Return information about QEMU's alarm clock.
|
||||
#
|
||||
# Returns a @QemuAlarmClock instance describing the alarm clock method
|
||||
# being currently used by QEMU (this is usually set by the '-clock'
|
||||
# command-line option).
|
||||
#
|
||||
# Since: 1.0
|
||||
##
|
||||
{ 'command': 'query-alarm-clock', 'returns': 'QemuAlarmClock' }
|
||||
|
||||
Notice the "returns" keyword. As its name suggests, it's used to define the
|
||||
data returned by a command.
|
||||
|
||||
It's time to implement the qmp_query_alarm_clock() function, you can put it
|
||||
in the qemu-timer.c file:
|
||||
|
||||
QemuAlarmClock *qmp_query_alarm_clock(Error **errp)
|
||||
{
|
||||
QemuAlarmClock *clock;
|
||||
int64_t deadline;
|
||||
|
||||
clock = g_malloc0(sizeof(*clock));
|
||||
|
||||
deadline = qemu_next_alarm_deadline();
|
||||
if (deadline > 0) {
|
||||
clock->has_next_deadline = true;
|
||||
clock->next_deadline = deadline;
|
||||
}
|
||||
clock->clock_name = g_strdup(alarm_timer->name);
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
There are a number of things to be noticed:
|
||||
|
||||
1. The QemuAlarmClock type is automatically generated by the QAPI framework,
|
||||
its members correspond to the type's specification in the schema file
|
||||
2. As specified in the schema file, the function returns a QemuAlarmClock
|
||||
instance and takes no arguments (besides the "errp" one, which is mandatory
|
||||
for all QMP functions)
|
||||
3. The "clock" variable (which will point to our QAPI type instance) is
|
||||
allocated by the regular g_malloc0() function. Note that we chose to
|
||||
initialize the memory to zero. This is recomended for all QAPI types, as
|
||||
it helps avoiding bad surprises (specially with booleans)
|
||||
4. Remember that "next_deadline" is optional? All optional members have a
|
||||
'has_TYPE_NAME' member that should be properly set by the implementation,
|
||||
as shown above
|
||||
5. Even static strings, such as "alarm_timer->name", should be dynamically
|
||||
allocated by the implementation. This is so because the QAPI also generates
|
||||
a function to free its types and it cannot distinguish between dynamically
|
||||
or statically allocated strings
|
||||
6. You have to include the "qmp-commands.h" header file in qemu-timer.c,
|
||||
otherwise qemu won't build
|
||||
|
||||
The last step is to add the correspoding entry in the qmp-commands.hx file:
|
||||
|
||||
{
|
||||
.name = "query-alarm-clock",
|
||||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_marshal_input_query_alarm_clock,
|
||||
},
|
||||
|
||||
Time to test the new command. Build qemu, run it as described in the "Testing"
|
||||
section and try this:
|
||||
|
||||
{ "execute": "query-alarm-clock" }
|
||||
{
|
||||
"return": {
|
||||
"next-deadline": 2368219,
|
||||
"clock-name": "dynticks"
|
||||
}
|
||||
}
|
||||
|
||||
==== The HMP command ====
|
||||
|
||||
Here's the HMP counterpart of the query-alarm-clock command:
|
||||
|
||||
void hmp_info_alarm_clock(Monitor *mon)
|
||||
{
|
||||
QemuAlarmClock *clock;
|
||||
Error *errp = NULL;
|
||||
|
||||
clock = qmp_query_alarm_clock(&errp);
|
||||
if (error_is_set(&errp)) {
|
||||
monitor_printf(mon, "Could not query alarm clock information\n");
|
||||
error_free(errp);
|
||||
return;
|
||||
}
|
||||
|
||||
monitor_printf(mon, "Alarm clock method in use: '%s'\n", clock->clock_name);
|
||||
if (clock->has_next_deadline) {
|
||||
monitor_printf(mon, "Next alarm will fire in %" PRId64 " nanoseconds\n",
|
||||
clock->next_deadline);
|
||||
}
|
||||
|
||||
qapi_free_QemuAlarmClock(clock);
|
||||
}
|
||||
|
||||
It's important to notice that hmp_info_alarm_clock() calls
|
||||
qapi_free_QemuAlarmClock() to free the data returned by qmp_query_alarm_clock().
|
||||
For user defined types, the QAPI will generate a qapi_free_QAPI_TYPE_NAME()
|
||||
function and that's what you have to use to free the types you define and
|
||||
qapi_free_QAPI_TYPE_NAMEList() for list types (explained in the next section).
|
||||
If the QMP call returns a string, then you should g_free() to free it.
|
||||
|
||||
Also note that hmp_info_alarm_clock() performs error handling. That's not
|
||||
strictly required if you're sure the QMP function doesn't return errors, but
|
||||
it's good practice to always check for errors.
|
||||
|
||||
Another important detail is that HMP's "info" commands don't go into the
|
||||
hmp-commands.hx. Instead, they go into the info_cmds[] table, which is defined
|
||||
in the monitor.c file. The entry for the "info alarmclock" follows:
|
||||
|
||||
{
|
||||
.name = "alarmclock",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "show information about the alarm clock",
|
||||
.mhandler.info = hmp_info_alarm_clock,
|
||||
},
|
||||
|
||||
To test this, run qemu and type "info alarmclock" in the user monitor.
|
||||
|
||||
=== Returning Lists ===
|
||||
|
||||
For this example, we're going to return all available methods for the timer
|
||||
alarm, which is pretty much what the command-line option "-clock ?" does,
|
||||
except that we're also going to inform which method is in use.
|
||||
|
||||
This first step is to define a new type:
|
||||
|
||||
##
|
||||
# @TimerAlarmMethod
|
||||
#
|
||||
# Timer alarm method information.
|
||||
#
|
||||
# @method-name: The method's name.
|
||||
#
|
||||
# @current: true if this alarm method is currently in use, false otherwise
|
||||
#
|
||||
# Since: 1.0
|
||||
##
|
||||
{ 'type': 'TimerAlarmMethod',
|
||||
'data': { 'method-name': 'str', 'current': 'bool' } }
|
||||
|
||||
The command will be called "query-alarm-methods", here is its schema
|
||||
specification:
|
||||
|
||||
##
|
||||
# @query-alarm-methods
|
||||
#
|
||||
# Returns information about available alarm methods.
|
||||
#
|
||||
# Returns: a list of @TimerAlarmMethod for each method
|
||||
#
|
||||
# Since: 1.0
|
||||
##
|
||||
{ 'command': 'query-alarm-methods', 'returns': ['TimerAlarmMethod'] }
|
||||
|
||||
Notice the syntax for returning lists "'returns': ['TimerAlarmMethod']", this
|
||||
should be read as "returns a list of TimerAlarmMethod instances".
|
||||
|
||||
The C implementation follows:
|
||||
|
||||
TimerAlarmMethodList *qmp_query_alarm_methods(Error **errp)
|
||||
{
|
||||
TimerAlarmMethodList *method_list = NULL;
|
||||
const struct qemu_alarm_timer *p;
|
||||
bool current = true;
|
||||
|
||||
for (p = alarm_timers; p->name; p++) {
|
||||
TimerAlarmMethodList *info = g_malloc0(sizeof(*info));
|
||||
info->value = g_malloc0(sizeof(*info->value));
|
||||
info->value->method_name = g_strdup(p->name);
|
||||
info->value->current = current;
|
||||
|
||||
current = false;
|
||||
|
||||
info->next = method_list;
|
||||
method_list = info;
|
||||
}
|
||||
|
||||
return method_list;
|
||||
}
|
||||
|
||||
The most important difference from the previous examples is the
|
||||
TimerAlarmMethodList type, which is automatically generated by the QAPI from
|
||||
the TimerAlarmMethod type.
|
||||
|
||||
Each list node is represented by a TimerAlarmMethodList instance. We have to
|
||||
allocate it, and that's done inside the for loop: the "info" pointer points to
|
||||
an allocated node. We also have to allocate the node's contents, which is
|
||||
stored in its "value" member. In our example, the "value" member is a pointer
|
||||
to an TimerAlarmMethod instance.
|
||||
|
||||
Notice that the "current" variable is used as "true" only in the first
|
||||
interation of the loop. That's because the alarm timer method in use is the
|
||||
first element of the alarm_timers array. Also notice that QAPI lists are handled
|
||||
by hand and we return the head of the list.
|
||||
|
||||
To test this you have to add the corresponding qmp-commands.hx entry:
|
||||
|
||||
{
|
||||
.name = "query-alarm-methods",
|
||||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_marshal_input_query_alarm_methods,
|
||||
},
|
||||
|
||||
Now Build qemu, run it as explained in the "Testing" section and try our new
|
||||
command:
|
||||
|
||||
{ "execute": "query-alarm-methods" }
|
||||
{
|
||||
"return": [
|
||||
{
|
||||
"current": false,
|
||||
"method-name": "unix"
|
||||
},
|
||||
{
|
||||
"current": true,
|
||||
"method-name": "dynticks"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
The HMP counterpart is a bit more complex than previous examples because it
|
||||
has to traverse the list, it's shown below for reference:
|
||||
|
||||
void hmp_info_alarm_methods(Monitor *mon)
|
||||
{
|
||||
TimerAlarmMethodList *method_list, *method;
|
||||
Error *errp = NULL;
|
||||
|
||||
method_list = qmp_query_alarm_methods(&errp);
|
||||
if (error_is_set(&errp)) {
|
||||
monitor_printf(mon, "Could not query alarm methods\n");
|
||||
error_free(errp);
|
||||
return;
|
||||
}
|
||||
|
||||
for (method = method_list; method; method = method->next) {
|
||||
monitor_printf(mon, "%c %s\n", method->value->current ? '*' : ' ',
|
||||
method->value->method_name);
|
||||
}
|
||||
|
||||
qapi_free_TimerAlarmMethodList(method_list);
|
||||
}
|
|
@ -57,8 +57,7 @@ ETEXI
|
|||
.args_type = "device:B,size:o",
|
||||
.params = "device size",
|
||||
.help = "resize a block image",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_block_resize,
|
||||
.mhandler.cmd = hmp_block_resize,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -304,8 +303,7 @@ ETEXI
|
|||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "resume emulation",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_cont,
|
||||
.mhandler.cmd = hmp_cont,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -689,8 +687,7 @@ ETEXI
|
|||
.args_type = "val:l,size:i,filename:s",
|
||||
.params = "addr size file",
|
||||
.help = "save to disk virtual memory dump starting at 'addr' of size 'size'",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_memory_save,
|
||||
.mhandler.cmd = hmp_memsave,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -704,8 +701,7 @@ ETEXI
|
|||
.args_type = "val:l,size:i,filename:s",
|
||||
.params = "addr size file",
|
||||
.help = "save to disk physical memory dump starting at 'addr' of size 'size'",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_physical_memory_save,
|
||||
.mhandler.cmd = hmp_pmemsave,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -739,8 +735,7 @@ ETEXI
|
|||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "inject an NMI on all guest's CPUs",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_inject_nmi,
|
||||
.mhandler.cmd = hmp_inject_nmi,
|
||||
},
|
||||
#endif
|
||||
STEXI
|
||||
|
@ -776,8 +771,7 @@ ETEXI
|
|||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "cancel the current VM migration",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_migrate_cancel,
|
||||
.mhandler.cmd = hmp_migrate_cancel,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -792,8 +786,7 @@ ETEXI
|
|||
.params = "value",
|
||||
.help = "set maximum speed (in bytes) for migrations. "
|
||||
"Defaults to MB if no size suffix is specified, ie. B/K/M/G/T",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_migrate_set_speed,
|
||||
.mhandler.cmd = hmp_migrate_set_speed,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -807,8 +800,7 @@ ETEXI
|
|||
.args_type = "value:T",
|
||||
.params = "value",
|
||||
.help = "set maximum tolerated downtime (in seconds) for migrations",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_migrate_set_downtime,
|
||||
.mhandler.cmd = hmp_migrate_set_downtime,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -845,7 +837,7 @@ ETEXI
|
|||
"If format is specified, the snapshot file will\n\t\t\t"
|
||||
"be created in that format. Otherwise the\n\t\t\t"
|
||||
"snapshot will be internal! (currently unsupported)",
|
||||
.mhandler.cmd_new = do_snapshot_blkdev,
|
||||
.mhandler.cmd = hmp_snapshot_blkdev,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -1026,9 +1018,7 @@ ETEXI
|
|||
.args_type = "value:M",
|
||||
.params = "target",
|
||||
.help = "request VM to change its memory allocation (in MB)",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_async = do_balloon,
|
||||
.flags = MONITOR_CMD_ASYNC,
|
||||
.mhandler.cmd = hmp_balloon,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -1042,8 +1032,7 @@ ETEXI
|
|||
.args_type = "name:s,up:b",
|
||||
.params = "name on|off",
|
||||
.help = "change the link status of a network adapter",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_set_link,
|
||||
.mhandler.cmd = hmp_set_link,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
@ -1203,8 +1192,7 @@ ETEXI
|
|||
.args_type = "device:B,password:s",
|
||||
.params = "block_passwd device password",
|
||||
.help = "set the password of encrypted block devices",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_block_set_passwd,
|
||||
.mhandler.cmd = hmp_block_passwd,
|
||||
},
|
||||
|
||||
STEXI
|
||||
|
|
148
hmp.c
148
hmp.c
|
@ -14,6 +14,14 @@
|
|||
#include "hmp.h"
|
||||
#include "qmp-commands.h"
|
||||
|
||||
static void hmp_handle_error(Monitor *mon, Error **errp)
|
||||
{
|
||||
if (error_is_set(errp)) {
|
||||
monitor_printf(mon, "%s\n", error_get_pretty(*errp));
|
||||
error_free(*errp);
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_info_name(Monitor *mon)
|
||||
{
|
||||
NameInfo *info;
|
||||
|
@ -531,3 +539,143 @@ void hmp_cpu(Monitor *mon, const QDict *qdict)
|
|||
monitor_printf(mon, "invalid CPU index\n");
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_memsave(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
uint32_t size = qdict_get_int(qdict, "size");
|
||||
const char *filename = qdict_get_str(qdict, "filename");
|
||||
uint64_t addr = qdict_get_int(qdict, "val");
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_memsave(addr, size, filename, true, monitor_get_cpu_index(), &errp);
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_pmemsave(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
uint32_t size = qdict_get_int(qdict, "size");
|
||||
const char *filename = qdict_get_str(qdict, "filename");
|
||||
uint64_t addr = qdict_get_int(qdict, "val");
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_pmemsave(addr, size, filename, &errp);
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
static void hmp_cont_cb(void *opaque, int err)
|
||||
{
|
||||
Monitor *mon = opaque;
|
||||
|
||||
if (!err) {
|
||||
hmp_cont(mon, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_cont(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_cont(&errp);
|
||||
if (error_is_set(&errp)) {
|
||||
if (error_is_type(errp, QERR_DEVICE_ENCRYPTED)) {
|
||||
const char *device;
|
||||
|
||||
/* The device is encrypted. Ask the user for the password
|
||||
and retry */
|
||||
|
||||
device = error_get_field(errp, "device");
|
||||
assert(device != NULL);
|
||||
|
||||
monitor_read_block_device_key(mon, device, hmp_cont_cb, mon);
|
||||
error_free(errp);
|
||||
return;
|
||||
}
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_inject_nmi(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_inject_nmi(&errp);
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_set_link(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *name = qdict_get_str(qdict, "name");
|
||||
int up = qdict_get_bool(qdict, "up");
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_set_link(name, up, &errp);
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_block_passwd(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *device = qdict_get_str(qdict, "device");
|
||||
const char *password = qdict_get_str(qdict, "password");
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_block_passwd(device, password, &errp);
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_balloon(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
int64_t value = qdict_get_int(qdict, "value");
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_balloon(value, &errp);
|
||||
if (error_is_set(&errp)) {
|
||||
monitor_printf(mon, "balloon: %s\n", error_get_pretty(errp));
|
||||
error_free(errp);
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_block_resize(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *device = qdict_get_str(qdict, "device");
|
||||
int64_t size = qdict_get_int(qdict, "size");
|
||||
Error *errp = NULL;
|
||||
|
||||
qmp_block_resize(device, size, &errp);
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *device = qdict_get_str(qdict, "device");
|
||||
const char *filename = qdict_get_try_str(qdict, "snapshot-file");
|
||||
const char *format = qdict_get_try_str(qdict, "format");
|
||||
Error *errp = NULL;
|
||||
|
||||
if (!filename) {
|
||||
/* In the future, if 'snapshot-file' is not specified, the snapshot
|
||||
will be taken internally. Today it's actually required. */
|
||||
error_set(&errp, QERR_MISSING_PARAMETER, "snapshot-file");
|
||||
hmp_handle_error(mon, &errp);
|
||||
return;
|
||||
}
|
||||
|
||||
qmp_blockdev_snapshot_sync(device, filename, !!format, format, &errp);
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_migrate_cancel(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
qmp_migrate_cancel(NULL);
|
||||
}
|
||||
|
||||
void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
double value = qdict_get_double(qdict, "value");
|
||||
qmp_migrate_set_downtime(value, NULL);
|
||||
}
|
||||
|
||||
void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
int64_t value = qdict_get_int(qdict, "value");
|
||||
qmp_migrate_set_speed(value, NULL);
|
||||
}
|
||||
|
|
12
hmp.h
12
hmp.h
|
@ -37,5 +37,17 @@ void hmp_stop(Monitor *mon, const QDict *qdict);
|
|||
void hmp_system_reset(Monitor *mon, const QDict *qdict);
|
||||
void hmp_system_powerdown(Monitor *mon, const QDict *qdict);
|
||||
void hmp_cpu(Monitor *mon, const QDict *qdict);
|
||||
void hmp_memsave(Monitor *mon, const QDict *qdict);
|
||||
void hmp_pmemsave(Monitor *mon, const QDict *qdict);
|
||||
void hmp_cont(Monitor *mon, const QDict *qdict);
|
||||
void hmp_inject_nmi(Monitor *mon, const QDict *qdict);
|
||||
void hmp_set_link(Monitor *mon, const QDict *qdict);
|
||||
void hmp_block_passwd(Monitor *mon, const QDict *qdict);
|
||||
void hmp_balloon(Monitor *mon, const QDict *qdict);
|
||||
void hmp_block_resize(Monitor *mon, const QDict *qdict);
|
||||
void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
|
||||
|
||||
#endif
|
||||
|
|
28
migration.c
28
migration.c
|
@ -468,37 +468,27 @@ int do_migrate(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int do_migrate_cancel(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
void qmp_migrate_cancel(Error **errp)
|
||||
{
|
||||
migrate_fd_cancel(migrate_get_current());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_migrate_set_speed(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
void qmp_migrate_set_speed(int64_t value, Error **errp)
|
||||
{
|
||||
int64_t d;
|
||||
MigrationState *s;
|
||||
|
||||
d = qdict_get_int(qdict, "value");
|
||||
if (d < 0) {
|
||||
d = 0;
|
||||
if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
s = migrate_get_current();
|
||||
s->bandwidth_limit = d;
|
||||
s->bandwidth_limit = value;
|
||||
qemu_file_set_rate_limit(s->file, s->bandwidth_limit);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_migrate_set_downtime(Monitor *mon, const QDict *qdict,
|
||||
QObject **ret_data)
|
||||
void qmp_migrate_set_downtime(double value, Error **errp)
|
||||
{
|
||||
double d;
|
||||
|
||||
d = qdict_get_double(qdict, "value") * 1e9;
|
||||
d = MAX(0, MIN(UINT64_MAX, d));
|
||||
max_downtime = (uint64_t)d;
|
||||
|
||||
return 0;
|
||||
value *= 1e9;
|
||||
value = MAX(0, MIN(UINT64_MAX, value));
|
||||
max_downtime = (uint64_t)value;
|
||||
}
|
||||
|
|
|
@ -42,15 +42,8 @@ int qemu_start_incoming_migration(const char *uri);
|
|||
|
||||
int do_migrate(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
|
||||
int do_migrate_cancel(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
|
||||
int do_migrate_set_speed(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
|
||||
uint64_t migrate_max_downtime(void);
|
||||
|
||||
int do_migrate_set_downtime(Monitor *mon, const QDict *qdict,
|
||||
QObject **ret_data);
|
||||
|
||||
void do_info_migrate_print(Monitor *mon, const QObject *data);
|
||||
|
||||
void do_info_migrate(Monitor *mon, QObject **ret_data);
|
||||
|
|
201
monitor.c
201
monitor.c
|
@ -513,10 +513,10 @@ static int do_qmp_capabilities(Monitor *mon, const QDict *params,
|
|||
|
||||
static void handle_user_command(Monitor *mon, const char *cmdline);
|
||||
|
||||
static int do_hmp_passthrough(Monitor *mon, const QDict *params,
|
||||
QObject **ret_data)
|
||||
char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
|
||||
int64_t cpu_index, Error **errp)
|
||||
{
|
||||
int ret = 0;
|
||||
char *output = NULL;
|
||||
Monitor *old_mon, hmp;
|
||||
CharDriverState mchar;
|
||||
|
||||
|
@ -527,25 +527,30 @@ static int do_hmp_passthrough(Monitor *mon, const QDict *params,
|
|||
old_mon = cur_mon;
|
||||
cur_mon = &hmp;
|
||||
|
||||
if (qdict_haskey(params, "cpu-index")) {
|
||||
ret = monitor_set_cpu(qdict_get_int(params, "cpu-index"));
|
||||
if (has_cpu_index) {
|
||||
int ret = monitor_set_cpu(cpu_index);
|
||||
if (ret < 0) {
|
||||
cur_mon = old_mon;
|
||||
qerror_report(QERR_INVALID_PARAMETER_VALUE, "cpu-index", "a CPU number");
|
||||
error_set(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
|
||||
"a CPU number");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
handle_user_command(&hmp, qdict_get_str(params, "command-line"));
|
||||
handle_user_command(&hmp, command_line);
|
||||
cur_mon = old_mon;
|
||||
|
||||
if (qemu_chr_mem_osize(hmp.chr) > 0) {
|
||||
*ret_data = QOBJECT(qemu_chr_mem_to_qs(hmp.chr));
|
||||
QString *str = qemu_chr_mem_to_qs(hmp.chr);
|
||||
output = g_strdup(qstring_get_str(str));
|
||||
QDECREF(str);
|
||||
} else {
|
||||
output = g_strdup("");
|
||||
}
|
||||
|
||||
out:
|
||||
qemu_chr_close_mem(hmp.chr);
|
||||
return ret;
|
||||
return output;
|
||||
}
|
||||
|
||||
static int compare_cmd(const char *name, const char *list)
|
||||
|
@ -1073,65 +1078,6 @@ static void do_singlestep(Monitor *mon, const QDict *qdict)
|
|||
}
|
||||
}
|
||||
|
||||
static void encrypted_bdrv_it(void *opaque, BlockDriverState *bs);
|
||||
|
||||
struct bdrv_iterate_context {
|
||||
Monitor *mon;
|
||||
int err;
|
||||
};
|
||||
|
||||
static void iostatus_bdrv_it(void *opaque, BlockDriverState *bs)
|
||||
{
|
||||
bdrv_iostatus_reset(bs);
|
||||
}
|
||||
|
||||
/**
|
||||
* do_cont(): Resume emulation.
|
||||
*/
|
||||
static int do_cont(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
{
|
||||
struct bdrv_iterate_context context = { mon, 0 };
|
||||
|
||||
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
||||
qerror_report(QERR_MIGRATION_EXPECTED);
|
||||
return -1;
|
||||
} else if (runstate_check(RUN_STATE_INTERNAL_ERROR) ||
|
||||
runstate_check(RUN_STATE_SHUTDOWN)) {
|
||||
qerror_report(QERR_RESET_REQUIRED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bdrv_iterate(iostatus_bdrv_it, NULL);
|
||||
bdrv_iterate(encrypted_bdrv_it, &context);
|
||||
/* only resume the vm if all keys are set and valid */
|
||||
if (!context.err) {
|
||||
vm_start();
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void bdrv_key_cb(void *opaque, int err)
|
||||
{
|
||||
Monitor *mon = opaque;
|
||||
|
||||
/* another key was set successfully, retry to continue */
|
||||
if (!err)
|
||||
do_cont(mon, NULL, NULL);
|
||||
}
|
||||
|
||||
static void encrypted_bdrv_it(void *opaque, BlockDriverState *bs)
|
||||
{
|
||||
struct bdrv_iterate_context *context = opaque;
|
||||
|
||||
if (!context->err && bdrv_key_required(bs)) {
|
||||
context->err = -EBUSY;
|
||||
monitor_read_bdrv_key_start(context->mon, bs, bdrv_key_cb,
|
||||
context->mon);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_gdbserver(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *device = qdict_get_try_str(qdict, "device");
|
||||
|
@ -1370,81 +1316,6 @@ static void do_print(Monitor *mon, const QDict *qdict)
|
|||
monitor_printf(mon, "\n");
|
||||
}
|
||||
|
||||
static int do_memory_save(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
{
|
||||
FILE *f;
|
||||
uint32_t size = qdict_get_int(qdict, "size");
|
||||
const char *filename = qdict_get_str(qdict, "filename");
|
||||
target_long addr = qdict_get_int(qdict, "val");
|
||||
uint32_t l;
|
||||
CPUState *env;
|
||||
uint8_t buf[1024];
|
||||
int ret = -1;
|
||||
|
||||
env = mon_get_cpu();
|
||||
|
||||
f = fopen(filename, "wb");
|
||||
if (!f) {
|
||||
qerror_report(QERR_OPEN_FILE_FAILED, filename);
|
||||
return -1;
|
||||
}
|
||||
while (size != 0) {
|
||||
l = sizeof(buf);
|
||||
if (l > size)
|
||||
l = size;
|
||||
cpu_memory_rw_debug(env, addr, buf, l, 0);
|
||||
if (fwrite(buf, 1, l, f) != l) {
|
||||
monitor_printf(mon, "fwrite() error in do_memory_save\n");
|
||||
goto exit;
|
||||
}
|
||||
addr += l;
|
||||
size -= l;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
exit:
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_physical_memory_save(Monitor *mon, const QDict *qdict,
|
||||
QObject **ret_data)
|
||||
{
|
||||
FILE *f;
|
||||
uint32_t l;
|
||||
uint8_t buf[1024];
|
||||
uint32_t size = qdict_get_int(qdict, "size");
|
||||
const char *filename = qdict_get_str(qdict, "filename");
|
||||
target_phys_addr_t addr = qdict_get_int(qdict, "val");
|
||||
int ret = -1;
|
||||
|
||||
f = fopen(filename, "wb");
|
||||
if (!f) {
|
||||
qerror_report(QERR_OPEN_FILE_FAILED, filename);
|
||||
return -1;
|
||||
}
|
||||
while (size != 0) {
|
||||
l = sizeof(buf);
|
||||
if (l > size)
|
||||
l = size;
|
||||
cpu_physical_memory_read(addr, buf, l);
|
||||
if (fwrite(buf, 1, l, f) != l) {
|
||||
monitor_printf(mon, "fwrite() error in do_physical_memory_save\n");
|
||||
goto exit;
|
||||
}
|
||||
fflush(f);
|
||||
addr += l;
|
||||
size -= l;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
exit:
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void do_sum(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
uint32_t addr;
|
||||
|
@ -1796,16 +1667,6 @@ static void do_boot_set(Monitor *mon, const QDict *qdict)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* do_system_powerdown(): Issue a machine powerdown
|
||||
*/
|
||||
static int do_system_powerdown(Monitor *mon, const QDict *qdict,
|
||||
QObject **ret_data)
|
||||
{
|
||||
qemu_system_powerdown_request();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(TARGET_I386)
|
||||
static void print_pte(Monitor *mon, target_phys_addr_t addr,
|
||||
target_phys_addr_t pte,
|
||||
|
@ -2348,25 +2209,6 @@ static void do_wav_capture(Monitor *mon, const QDict *qdict)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_I386)
|
||||
static int do_inject_nmi(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
{
|
||||
CPUState *env;
|
||||
|
||||
for (env = first_cpu; env != NULL; env = env->next_cpu) {
|
||||
cpu_interrupt(env, CPU_INTERRUPT_NMI);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int do_inject_nmi(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
{
|
||||
qerror_report(QERR_UNSUPPORTED);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static qemu_acl *find_acl(Monitor *mon, const char *name)
|
||||
{
|
||||
qemu_acl *acl = qemu_acl_find(name);
|
||||
|
@ -4943,3 +4785,18 @@ int monitor_read_bdrv_key_start(Monitor *mon, BlockDriverState *bs,
|
|||
|
||||
return err;
|
||||
}
|
||||
|
||||
int monitor_read_block_device_key(Monitor *mon, const char *device,
|
||||
BlockDriverCompletionFunc *completion_cb,
|
||||
void *opaque)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
|
||||
bs = bdrv_find(device);
|
||||
if (!bs) {
|
||||
monitor_printf(mon, "Device not found %s\n", device);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return monitor_read_bdrv_key_start(mon, bs, completion_cb, opaque);
|
||||
}
|
||||
|
|
|
@ -49,6 +49,9 @@ void monitor_resume(Monitor *mon);
|
|||
int monitor_read_bdrv_key_start(Monitor *mon, BlockDriverState *bs,
|
||||
BlockDriverCompletionFunc *completion_cb,
|
||||
void *opaque);
|
||||
int monitor_read_block_device_key(Monitor *mon, const char *device,
|
||||
BlockDriverCompletionFunc *completion_cb,
|
||||
void *opaque);
|
||||
|
||||
int monitor_get_fd(Monitor *mon, const char *fdname);
|
||||
|
||||
|
|
10
net.c
10
net.c
|
@ -34,6 +34,7 @@
|
|||
#include "monitor.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qemu_socket.h"
|
||||
#include "qmp-commands.h"
|
||||
#include "hw/qdev.h"
|
||||
#include "iov.h"
|
||||
|
||||
|
@ -1258,12 +1259,10 @@ void do_info_network(Monitor *mon)
|
|||
}
|
||||
}
|
||||
|
||||
int do_set_link(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
void qmp_set_link(const char *name, bool up, Error **errp)
|
||||
{
|
||||
VLANState *vlan;
|
||||
VLANClientState *vc = NULL;
|
||||
const char *name = qdict_get_str(qdict, "name");
|
||||
int up = qdict_get_bool(qdict, "up");
|
||||
|
||||
QTAILQ_FOREACH(vlan, &vlans, next) {
|
||||
QTAILQ_FOREACH(vc, &vlan->clients, next) {
|
||||
|
@ -1280,8 +1279,8 @@ int do_set_link(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
|||
done:
|
||||
|
||||
if (!vc) {
|
||||
qerror_report(QERR_DEVICE_NOT_FOUND, name);
|
||||
return -1;
|
||||
error_set(errp, QERR_DEVICE_NOT_FOUND, name);
|
||||
return;
|
||||
}
|
||||
|
||||
vc->link_down = !up;
|
||||
|
@ -1300,7 +1299,6 @@ done:
|
|||
if (vc->peer && vc->peer->info->link_status_changed) {
|
||||
vc->peer->info->link_status_changed(vc->peer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void net_cleanup(void)
|
||||
|
|
1
net.h
1
net.h
|
@ -122,7 +122,6 @@ int qemu_find_nic_model(NICInfo *nd, const char * const *models,
|
|||
const char *default_model);
|
||||
|
||||
void do_info_network(Monitor *mon);
|
||||
int do_set_link(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
|
||||
/* NIC info */
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
'dict': { 'userdef': 'UserDefOne', 'string': 'str' },
|
||||
'*dict2': { 'userdef': 'UserDefOne', 'string': 'str' } } } }
|
||||
|
||||
{ 'type': 'UserDefNested',
|
||||
'data': { 'string0': 'str',
|
||||
'dict1': { 'string1': 'str',
|
||||
'dict2': { 'userdef1': 'UserDefOne', 'string2': 'str' },
|
||||
'*dict3': { 'userdef2': 'UserDefOne', 'string3': 'str' } } } }
|
||||
|
||||
# testing commands
|
||||
{ 'command': 'user_def_cmd', 'data': {} }
|
||||
{ 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} }
|
||||
|
|
267
qapi-schema.json
267
qapi-schema.json
|
@ -901,3 +901,270 @@
|
|||
# Notes: Do not use this command.
|
||||
##
|
||||
{ 'command': 'cpu', 'data': {'index': 'int'} }
|
||||
|
||||
##
|
||||
# @memsave:
|
||||
#
|
||||
# Save a portion of guest memory to a file.
|
||||
#
|
||||
# @val: the virtual address of the guest to start from
|
||||
#
|
||||
# @size: the size of memory region to save
|
||||
#
|
||||
# @filename: the file to save the memory to as binary data
|
||||
#
|
||||
# @cpu-index: #optional the index of the virtual CPU to use for translating the
|
||||
# virtual address (defaults to CPU 0)
|
||||
#
|
||||
# Returns: Nothing on success
|
||||
# If @cpu is not a valid VCPU, InvalidParameterValue
|
||||
# If @filename cannot be opened, OpenFileFailed
|
||||
# If an I/O error occurs while writing the file, IOError
|
||||
#
|
||||
# Since: 0.14.0
|
||||
#
|
||||
# Notes: Errors were not reliably returned until 1.1
|
||||
##
|
||||
{ 'command': 'memsave',
|
||||
'data': {'val': 'int', 'size': 'int', 'filename': 'str', '*cpu-index': 'int'} }
|
||||
|
||||
##
|
||||
# @pmemsave:
|
||||
#
|
||||
# Save a portion of guest physical memory to a file.
|
||||
#
|
||||
# @val: the physical address of the guest to start from
|
||||
#
|
||||
# @size: the size of memory region to save
|
||||
#
|
||||
# @filename: the file to save the memory to as binary data
|
||||
#
|
||||
# Returns: Nothing on success
|
||||
# If @filename cannot be opened, OpenFileFailed
|
||||
# If an I/O error occurs while writing the file, IOError
|
||||
#
|
||||
# Since: 0.14.0
|
||||
#
|
||||
# Notes: Errors were not reliably returned until 1.1
|
||||
##
|
||||
{ 'command': 'pmemsave',
|
||||
'data': {'val': 'int', 'size': 'int', 'filename': 'str'} }
|
||||
|
||||
##
|
||||
# @cont:
|
||||
#
|
||||
# Resume guest VCPU execution.
|
||||
#
|
||||
# Since: 0.14.0
|
||||
#
|
||||
# Returns: If successful, nothing
|
||||
# If the QEMU is waiting for an incoming migration, MigrationExpected
|
||||
# If QEMU was started with an encrypted block device and a key has
|
||||
# not yet been set, DeviceEncrypted.
|
||||
#
|
||||
# Notes: This command will succeed if the guest is currently running.
|
||||
##
|
||||
{ 'command': 'cont' }
|
||||
|
||||
##
|
||||
# @inject-nmi:
|
||||
#
|
||||
# Injects an Non-Maskable Interrupt into all guest's VCPUs.
|
||||
#
|
||||
# Returns: If successful, nothing
|
||||
# If the Virtual Machine doesn't support NMI injection, Unsupported
|
||||
#
|
||||
# Since: 0.14.0
|
||||
#
|
||||
# Notes: Only x86 Virtual Machines support this command.
|
||||
##
|
||||
{ 'command': 'inject-nmi' }
|
||||
|
||||
##
|
||||
# @set_link:
|
||||
#
|
||||
# Sets the link status of a virtual network adapter.
|
||||
#
|
||||
# @name: the device name of the virtual network adapter
|
||||
#
|
||||
# @up: true to set the link status to be up
|
||||
#
|
||||
# Returns: Nothing on success
|
||||
# If @name is not a valid network device, DeviceNotFound
|
||||
#
|
||||
# Since: 0.14.0
|
||||
#
|
||||
# Notes: Not all network adapters support setting link status. This command
|
||||
# will succeed even if the network adapter does not support link status
|
||||
# notification.
|
||||
##
|
||||
{ 'command': 'set_link', 'data': {'name': 'str', 'up': 'bool'} }
|
||||
|
||||
##
|
||||
# @block_passwd:
|
||||
#
|
||||
# This command sets the password of a block device that has not been open
|
||||
# with a password and requires one.
|
||||
#
|
||||
# The two cases where this can happen are a block device is created through
|
||||
# QEMU's initial command line or a block device is changed through the legacy
|
||||
# @change interface.
|
||||
#
|
||||
# In the event that the block device is created through the initial command
|
||||
# line, the VM will start in the stopped state regardless of whether '-S' is
|
||||
# used. The intention is for a management tool to query the block devices to
|
||||
# determine which ones are encrypted, set the passwords with this command, and
|
||||
# then start the guest with the @cont command.
|
||||
#
|
||||
# @device: the name of the device to set the password on
|
||||
#
|
||||
# @password: the password to use for the device
|
||||
#
|
||||
# Returns: nothing on success
|
||||
# If @device is not a valid block device, DeviceNotFound
|
||||
# If @device is not encrypted, DeviceNotEncrypted
|
||||
# If @password is not valid for this device, InvalidPassword
|
||||
#
|
||||
# Notes: Not all block formats support encryption and some that do are not
|
||||
# able to validate that a password is correct. Disk corruption may
|
||||
# occur if an invalid password is specified.
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'command': 'block_passwd', 'data': {'device': 'str', 'password': 'str'} }
|
||||
|
||||
##
|
||||
# @balloon:
|
||||
#
|
||||
# Request the balloon driver to change its balloon size.
|
||||
#
|
||||
# @value: the target size of the balloon in bytes
|
||||
#
|
||||
# Returns: Nothing on success
|
||||
# If the balloon driver is enabled but not functional because the KVM
|
||||
# kernel module cannot support it, KvmMissingCap
|
||||
# If no balloon device is present, DeviceNotActive
|
||||
#
|
||||
# Notes: This command just issues a request to the guest. When it returns,
|
||||
# the balloon size may not have changed. A guest can change the balloon
|
||||
# size independent of this command.
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'command': 'balloon', 'data': {'value': 'int'} }
|
||||
|
||||
##
|
||||
# @block_resize
|
||||
#
|
||||
# Resize a block image while a guest is running.
|
||||
#
|
||||
# @device: the name of the device to get the image resized
|
||||
#
|
||||
# @size: new image size in bytes
|
||||
#
|
||||
# Returns: nothing on success
|
||||
# If @device is not a valid block device, DeviceNotFound
|
||||
#
|
||||
# Notes: This command returns UndefinedError in a number of error conditions.
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'command': 'block_resize', 'data': { 'device': 'str', 'size': 'int' }}
|
||||
|
||||
##
|
||||
# @blockdev-snapshot-sync
|
||||
#
|
||||
# Generates a synchronous snapshot of a block device.
|
||||
#
|
||||
# @device: the name of the device to generate the snapshot from.
|
||||
#
|
||||
# @snapshot-file: the target of the new image. If the file exists, or if it
|
||||
# is a device, the snapshot will be created in the existing
|
||||
# file/device. If does not exist, a new file will be created.
|
||||
#
|
||||
# @format: #optional the format of the snapshot image, default is 'qcow2'.
|
||||
#
|
||||
# Returns: nothing on success
|
||||
# If @device is not a valid block device, DeviceNotFound
|
||||
# If @snapshot-file can't be opened, OpenFileFailed
|
||||
# If @format is invalid, InvalidBlockFormat
|
||||
#
|
||||
# Notes: One of the last steps taken by this command is to close the current
|
||||
# image being used by @device and open the @snapshot-file one. If that
|
||||
# fails, the command will try to reopen the original image file. If
|
||||
# that also fails OpenFileFailed will be returned and the guest may get
|
||||
# unexpected errors.
|
||||
#
|
||||
# Since 0.14.0
|
||||
##
|
||||
{ 'command': 'blockdev-snapshot-sync',
|
||||
'data': { 'device': 'str', 'snapshot-file': 'str', '*format': 'str' } }
|
||||
|
||||
##
|
||||
# @human-monitor-command:
|
||||
#
|
||||
# Execute a command on the human monitor and return the output.
|
||||
#
|
||||
# @command-line: the command to execute in the human monitor
|
||||
#
|
||||
# @cpu-index: #optional The CPU to use for commands that require an implicit CPU
|
||||
#
|
||||
# Returns: the output of the command as a string
|
||||
#
|
||||
# Since: 0.14.0
|
||||
#
|
||||
# Notes: This command only exists as a stop-gap. It's use is highly
|
||||
# discouraged. The semantics of this command are not guaranteed.
|
||||
#
|
||||
# Known limitations:
|
||||
#
|
||||
# o This command is stateless, this means that commands that depend
|
||||
# on state information (such as getfd) might not work
|
||||
#
|
||||
# o Commands that prompt the user for data (eg. 'cont' when the block
|
||||
# device is encrypted) don't currently work
|
||||
##
|
||||
{ 'command': 'human-monitor-command',
|
||||
'data': {'command-line': 'str', '*cpu-index': 'int'},
|
||||
'returns': 'str' }
|
||||
|
||||
##
|
||||
# @migrate_cancel
|
||||
#
|
||||
# Cancel the current executing migration process.
|
||||
#
|
||||
# Returns: nothing on success
|
||||
#
|
||||
# Notes: This command succeeds even if there is no migration process running.
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'command': 'migrate_cancel' }
|
||||
|
||||
##
|
||||
# @migrate_set_downtime
|
||||
#
|
||||
# Set maximum tolerated downtime for migration.
|
||||
#
|
||||
# @value: maximum downtime in seconds
|
||||
#
|
||||
# Returns: nothing on success
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'command': 'migrate_set_downtime', 'data': {'value': 'number'} }
|
||||
|
||||
##
|
||||
# @migrate_set_speed
|
||||
#
|
||||
# Set maximum speed for migration.
|
||||
#
|
||||
# @value: maximum speed in bytes.
|
||||
#
|
||||
# Returns: nothing on success
|
||||
#
|
||||
# Notes: A value lesser than zero will be automatically round up to zero.
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'command': 'migrate_set_speed', 'data': {'value': 'int'} }
|
||||
|
|
4
qerror.c
4
qerror.c
|
@ -148,6 +148,10 @@ static const QErrorStringTable qerror_table[] = {
|
|||
.error_fmt = QERR_INVALID_PASSWORD,
|
||||
.desc = "Password incorrect",
|
||||
},
|
||||
{
|
||||
.error_fmt = QERR_IO_ERROR,
|
||||
.desc = "An IO error has occurred",
|
||||
},
|
||||
{
|
||||
.error_fmt = QERR_JSON_PARSING,
|
||||
.desc = "Invalid JSON syntax",
|
||||
|
|
3
qerror.h
3
qerror.h
|
@ -126,6 +126,9 @@ QError *qobject_to_qerror(const QObject *obj);
|
|||
#define QERR_INVALID_PASSWORD \
|
||||
"{ 'class': 'InvalidPassword', 'data': {} }"
|
||||
|
||||
#define QERR_IO_ERROR \
|
||||
"{ 'class': 'IOError', 'data': {} }"
|
||||
|
||||
#define QERR_JSON_PARSING \
|
||||
"{ 'class': 'JSONParsing', 'data': {} }"
|
||||
|
||||
|
|
|
@ -199,10 +199,7 @@ EQMP
|
|||
{
|
||||
.name = "cont",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "resume emulation",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_cont,
|
||||
.mhandler.cmd_new = qmp_marshal_input_cont,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -244,10 +241,7 @@ EQMP
|
|||
{
|
||||
.name = "system_powerdown",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "send system power down event",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_system_powerdown,
|
||||
.mhandler.cmd_new = qmp_marshal_input_system_powerdown,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -355,11 +349,8 @@ EQMP
|
|||
|
||||
{
|
||||
.name = "memsave",
|
||||
.args_type = "val:l,size:i,filename:s",
|
||||
.params = "addr size file",
|
||||
.help = "save to disk virtual memory dump starting at 'addr' of size 'size'",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_memory_save,
|
||||
.args_type = "val:l,size:i,filename:s,cpu:i?",
|
||||
.mhandler.cmd_new = qmp_marshal_input_memsave,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -373,6 +364,7 @@ Arguments:
|
|||
- "val": the starting address (json-int)
|
||||
- "size": the memory size, in bytes (json-int)
|
||||
- "filename": file path (json-string)
|
||||
- "cpu": virtual CPU index (json-int, optional)
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -382,17 +374,12 @@ Example:
|
|||
"filename": "/tmp/virtual-mem-dump" } }
|
||||
<- { "return": {} }
|
||||
|
||||
Note: Depends on the current CPU.
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
.name = "pmemsave",
|
||||
.args_type = "val:l,size:i,filename:s",
|
||||
.params = "addr size file",
|
||||
.help = "save to disk physical memory dump starting at 'addr' of size 'size'",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_physical_memory_save,
|
||||
.mhandler.cmd_new = qmp_marshal_input_pmemsave,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -420,10 +407,7 @@ EQMP
|
|||
{
|
||||
.name = "inject-nmi",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_inject_nmi,
|
||||
.mhandler.cmd_new = qmp_marshal_input_inject_nmi,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -487,10 +471,7 @@ EQMP
|
|||
{
|
||||
.name = "migrate_cancel",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "cancel the current VM migration",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_migrate_cancel,
|
||||
.mhandler.cmd_new = qmp_marshal_input_migrate_cancel,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -511,10 +492,7 @@ EQMP
|
|||
{
|
||||
.name = "migrate_set_speed",
|
||||
.args_type = "value:o",
|
||||
.params = "value",
|
||||
.help = "set maximum speed (in bytes) for migrations",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_migrate_set_speed,
|
||||
.mhandler.cmd_new = qmp_marshal_input_migrate_set_speed,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -537,10 +515,7 @@ EQMP
|
|||
{
|
||||
.name = "migrate_set_downtime",
|
||||
.args_type = "value:T",
|
||||
.params = "value",
|
||||
.help = "set maximum tolerated downtime (in seconds) for migrations",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_migrate_set_downtime,
|
||||
.mhandler.cmd_new = qmp_marshal_input_migrate_set_downtime,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -658,10 +633,7 @@ EQMP
|
|||
{
|
||||
.name = "block_resize",
|
||||
.args_type = "device:B,size:o",
|
||||
.params = "device size",
|
||||
.help = "resize a block image",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_block_resize,
|
||||
.mhandler.cmd_new = qmp_marshal_input_block_resize,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -684,10 +656,8 @@ EQMP
|
|||
|
||||
{
|
||||
.name = "blockdev-snapshot-sync",
|
||||
.args_type = "device:B,snapshot-file:s?,format:s?",
|
||||
.params = "device [new-image-file] [format]",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_snapshot_blkdev,
|
||||
.args_type = "device:B,snapshot-file:s,format:s?",
|
||||
.mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -719,11 +689,7 @@ EQMP
|
|||
{
|
||||
.name = "balloon",
|
||||
.args_type = "value:M",
|
||||
.params = "target",
|
||||
.help = "request VM to change its memory allocation (in MB)",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_async = do_balloon,
|
||||
.flags = MONITOR_CMD_ASYNC,
|
||||
.mhandler.cmd_new = qmp_marshal_input_balloon,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -746,10 +712,7 @@ EQMP
|
|||
{
|
||||
.name = "set_link",
|
||||
.args_type = "name:s,up:b",
|
||||
.params = "name on|off",
|
||||
.help = "change the link status of a network adapter",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_set_link,
|
||||
.mhandler.cmd_new = qmp_marshal_input_set_link,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -825,10 +788,7 @@ EQMP
|
|||
{
|
||||
.name = "block_passwd",
|
||||
.args_type = "device:B,password:s",
|
||||
.params = "block_passwd device password",
|
||||
.help = "set the password of encrypted block devices",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_block_set_passwd,
|
||||
.mhandler.cmd_new = qmp_marshal_input_block_passwd,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
@ -1001,10 +961,7 @@ EQMP
|
|||
{
|
||||
.name = "human-monitor-command",
|
||||
.args_type = "command-line:s,cpu-index:i?",
|
||||
.params = "",
|
||||
.help = "",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_hmp_passthrough,
|
||||
.mhandler.cmd_new = qmp_marshal_input_human_monitor_command,
|
||||
},
|
||||
|
||||
SQMP
|
||||
|
|
37
qmp.c
37
qmp.c
|
@ -117,3 +117,40 @@ SpiceInfo *qmp_query_spice(Error **errp)
|
|||
return NULL;
|
||||
};
|
||||
#endif
|
||||
|
||||
static void iostatus_bdrv_it(void *opaque, BlockDriverState *bs)
|
||||
{
|
||||
bdrv_iostatus_reset(bs);
|
||||
}
|
||||
|
||||
static void encrypted_bdrv_it(void *opaque, BlockDriverState *bs)
|
||||
{
|
||||
Error **err = opaque;
|
||||
|
||||
if (!error_is_set(err) && bdrv_key_required(bs)) {
|
||||
error_set(err, QERR_DEVICE_ENCRYPTED, bdrv_get_device_name(bs));
|
||||
}
|
||||
}
|
||||
|
||||
void qmp_cont(Error **errp)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
||||
error_set(errp, QERR_MIGRATION_EXPECTED);
|
||||
return;
|
||||
} else if (runstate_check(RUN_STATE_INTERNAL_ERROR) ||
|
||||
runstate_check(RUN_STATE_SHUTDOWN)) {
|
||||
error_set(errp, QERR_RESET_REQUIRED);
|
||||
return;
|
||||
}
|
||||
|
||||
bdrv_iterate(iostatus_bdrv_it, NULL);
|
||||
bdrv_iterate(encrypted_bdrv_it, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
|
||||
vm_start();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* QMP Input Visitor unit-tests.
|
||||
*
|
||||
* Copyright (C) 2011 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Luiz Capitulino <lcapitulino@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "qapi/qmp-input-visitor.h"
|
||||
#include "test-qapi-types.h"
|
||||
#include "test-qapi-visit.h"
|
||||
#include "qemu-objects.h"
|
||||
|
||||
typedef struct TestInputVisitorData {
|
||||
QObject *obj;
|
||||
QmpInputVisitor *qiv;
|
||||
} TestInputVisitorData;
|
||||
|
||||
static void visitor_input_teardown(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
qobject_decref(data->obj);
|
||||
data->obj = NULL;
|
||||
|
||||
if (data->qiv) {
|
||||
qmp_input_visitor_cleanup(data->qiv);
|
||||
data->qiv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is provided instead of a test setup function so that the JSON
|
||||
string used by the tests are kept in the test functions (and not
|
||||
int main()) */
|
||||
static Visitor *visitor_input_test_init(TestInputVisitorData *data,
|
||||
const char *json_string, ...)
|
||||
{
|
||||
Visitor *v;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, json_string);
|
||||
data->obj = qobject_from_jsonv(json_string, &ap);
|
||||
va_end(ap);
|
||||
|
||||
g_assert(data->obj != NULL);
|
||||
|
||||
data->qiv = qmp_input_visitor_new(data->obj);
|
||||
g_assert(data->qiv != NULL);
|
||||
|
||||
v = qmp_input_get_visitor(data->qiv);
|
||||
g_assert(v != NULL);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
static void test_visitor_in_int(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
int64_t res = 0, value = -42;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init(data, "%d", value);
|
||||
|
||||
visit_type_int(v, &res, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_assert_cmpint(res, ==, value);
|
||||
}
|
||||
|
||||
static void test_visitor_in_bool(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
Error *errp = NULL;
|
||||
bool res = false;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init(data, "true");
|
||||
|
||||
visit_type_bool(v, &res, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_assert_cmpint(res, ==, true);
|
||||
}
|
||||
|
||||
static void test_visitor_in_number(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
double res = 0, value = 3.14;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init(data, "%f", value);
|
||||
|
||||
visit_type_number(v, &res, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_assert_cmpfloat(res, ==, value);
|
||||
}
|
||||
|
||||
static void test_visitor_in_string(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
char *res = NULL, *value = (char *) "Q E M U";
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init(data, "%s", value);
|
||||
|
||||
visit_type_str(v, &res, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_assert_cmpstr(res, ==, value);
|
||||
|
||||
g_free(res);
|
||||
}
|
||||
|
||||
static void test_visitor_in_enum(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
EnumOne i;
|
||||
|
||||
for (i = 0; EnumOne_lookup[i]; i++) {
|
||||
EnumOne res = -1;
|
||||
|
||||
v = visitor_input_test_init(data, "%s", EnumOne_lookup[i]);
|
||||
|
||||
visit_type_EnumOne(v, &res, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_assert_cmpint(i, ==, res);
|
||||
|
||||
visitor_input_teardown(data, NULL);
|
||||
}
|
||||
|
||||
data->obj = NULL;
|
||||
data->qiv = NULL;
|
||||
}
|
||||
|
||||
typedef struct TestStruct
|
||||
{
|
||||
int64_t integer;
|
||||
bool boolean;
|
||||
char *string;
|
||||
} TestStruct;
|
||||
|
||||
static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
|
||||
const char *name, Error **errp)
|
||||
{
|
||||
visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
|
||||
errp);
|
||||
|
||||
visit_type_int(v, &(*obj)->integer, "integer", errp);
|
||||
visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
|
||||
visit_type_str(v, &(*obj)->string, "string", errp);
|
||||
|
||||
visit_end_struct(v, errp);
|
||||
}
|
||||
|
||||
static void test_visitor_in_struct(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
TestStruct *p = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }");
|
||||
|
||||
visit_type_TestStruct(v, &p, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_assert_cmpint(p->integer, ==, -42);
|
||||
g_assert(p->boolean == true);
|
||||
g_assert_cmpstr(p->string, ==, "foo");
|
||||
|
||||
g_free(p->string);
|
||||
g_free(p);
|
||||
}
|
||||
|
||||
static void check_and_free_str(char *str, const char *cmp)
|
||||
{
|
||||
g_assert_cmpstr(str, ==, cmp);
|
||||
g_free(str);
|
||||
}
|
||||
|
||||
static void test_visitor_in_struct_nested(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefNested *udp = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}");
|
||||
|
||||
visit_type_UserDefNested(v, &udp, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
|
||||
check_and_free_str(udp->string0, "string0");
|
||||
check_and_free_str(udp->dict1.string1, "string1");
|
||||
g_assert_cmpint(udp->dict1.dict2.userdef1->integer, ==, 42);
|
||||
check_and_free_str(udp->dict1.dict2.userdef1->string, "string");
|
||||
check_and_free_str(udp->dict1.dict2.string2, "string2");
|
||||
g_assert(udp->dict1.has_dict3 == false);
|
||||
|
||||
g_free(udp->dict1.dict2.userdef1);
|
||||
g_free(udp);
|
||||
}
|
||||
|
||||
static void test_visitor_in_list(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefOneList *item, *head = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
int i;
|
||||
|
||||
v = visitor_input_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44 } ]");
|
||||
|
||||
visit_type_UserDefOneList(v, &head, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_assert(head != NULL);
|
||||
|
||||
for (i = 0, item = head; item; item = item->next, i++) {
|
||||
char string[12];
|
||||
|
||||
snprintf(string, sizeof(string), "string%d", i);
|
||||
g_assert_cmpstr(item->value->string, ==, string);
|
||||
g_assert_cmpint(item->value->integer, ==, 42 + i);
|
||||
}
|
||||
|
||||
qapi_free_UserDefOneList(head);
|
||||
}
|
||||
|
||||
static void input_visitor_test_add(const char *testpath,
|
||||
TestInputVisitorData *data,
|
||||
void (*test_func)(TestInputVisitorData *data, const void *user_data))
|
||||
{
|
||||
g_test_add(testpath, TestInputVisitorData, data, NULL, test_func,
|
||||
visitor_input_teardown);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
TestInputVisitorData in_visitor_data;
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
input_visitor_test_add("/visitor/input/int",
|
||||
&in_visitor_data, test_visitor_in_int);
|
||||
input_visitor_test_add("/visitor/input/bool",
|
||||
&in_visitor_data, test_visitor_in_bool);
|
||||
input_visitor_test_add("/visitor/input/number",
|
||||
&in_visitor_data, test_visitor_in_number);
|
||||
input_visitor_test_add("/visitor/input/string",
|
||||
&in_visitor_data, test_visitor_in_string);
|
||||
input_visitor_test_add("/visitor/input/enum",
|
||||
&in_visitor_data, test_visitor_in_enum);
|
||||
input_visitor_test_add("/visitor/input/struct",
|
||||
&in_visitor_data, test_visitor_in_struct);
|
||||
input_visitor_test_add("/visitor/input/struct-nested",
|
||||
&in_visitor_data, test_visitor_in_struct_nested);
|
||||
input_visitor_test_add("/visitor/input/list",
|
||||
&in_visitor_data, test_visitor_in_list);
|
||||
|
||||
g_test_run();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* QMP Output Visitor unit-tests.
|
||||
*
|
||||
* Copyright (C) 2011 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Luiz Capitulino <lcapitulino@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "qapi/qmp-output-visitor.h"
|
||||
#include "test-qapi-types.h"
|
||||
#include "test-qapi-visit.h"
|
||||
#include "qemu-objects.h"
|
||||
|
||||
typedef struct TestOutputVisitorData {
|
||||
QmpOutputVisitor *qov;
|
||||
Visitor *ov;
|
||||
} TestOutputVisitorData;
|
||||
|
||||
static void visitor_output_setup(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
data->qov = qmp_output_visitor_new();
|
||||
g_assert(data->qov != NULL);
|
||||
|
||||
data->ov = qmp_output_get_visitor(data->qov);
|
||||
g_assert(data->ov != NULL);
|
||||
}
|
||||
|
||||
static void visitor_output_teardown(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
qmp_output_visitor_cleanup(data->qov);
|
||||
data->qov = NULL;
|
||||
data->ov = NULL;
|
||||
}
|
||||
|
||||
static void test_visitor_out_int(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
int64_t value = -42;
|
||||
Error *errp = NULL;
|
||||
QObject *obj;
|
||||
|
||||
visit_type_int(data->ov, &value, NULL, &errp);
|
||||
g_assert(error_is_set(&errp) == 0);
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QINT);
|
||||
g_assert_cmpint(qint_get_int(qobject_to_qint(obj)), ==, value);
|
||||
|
||||
qobject_decref(obj);
|
||||
}
|
||||
|
||||
static void test_visitor_out_bool(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
Error *errp = NULL;
|
||||
bool value = true;
|
||||
QObject *obj;
|
||||
|
||||
visit_type_bool(data->ov, &value, NULL, &errp);
|
||||
g_assert(error_is_set(&errp) == 0);
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QBOOL);
|
||||
g_assert(qbool_get_int(qobject_to_qbool(obj)) == value);
|
||||
|
||||
qobject_decref(obj);
|
||||
}
|
||||
|
||||
static void test_visitor_out_number(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
double value = 3.14;
|
||||
Error *errp = NULL;
|
||||
QObject *obj;
|
||||
|
||||
visit_type_number(data->ov, &value, NULL, &errp);
|
||||
g_assert(error_is_set(&errp) == 0);
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QFLOAT);
|
||||
g_assert(qfloat_get_double(qobject_to_qfloat(obj)) == value);
|
||||
|
||||
qobject_decref(obj);
|
||||
}
|
||||
|
||||
static void test_visitor_out_string(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
char *string = (char *) "Q E M U";
|
||||
Error *errp = NULL;
|
||||
QObject *obj;
|
||||
|
||||
visit_type_str(data->ov, &string, NULL, &errp);
|
||||
g_assert(error_is_set(&errp) == 0);
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QSTRING);
|
||||
g_assert_cmpstr(qstring_get_str(qobject_to_qstring(obj)), ==, string);
|
||||
|
||||
qobject_decref(obj);
|
||||
}
|
||||
|
||||
static void test_visitor_out_no_string(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
char *string = NULL;
|
||||
Error *errp = NULL;
|
||||
QObject *obj;
|
||||
|
||||
/* A null string should return "" */
|
||||
visit_type_str(data->ov, &string, NULL, &errp);
|
||||
g_assert(error_is_set(&errp) == 0);
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QSTRING);
|
||||
g_assert_cmpstr(qstring_get_str(qobject_to_qstring(obj)), ==, "");
|
||||
|
||||
qobject_decref(obj);
|
||||
}
|
||||
|
||||
static void test_visitor_out_enum(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
Error *errp = NULL;
|
||||
QObject *obj;
|
||||
EnumOne i;
|
||||
|
||||
for (i = 0; i < ENUM_ONE_MAX; i++) {
|
||||
visit_type_EnumOne(data->ov, &i, "unused", &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QSTRING);
|
||||
g_assert_cmpstr(qstring_get_str(qobject_to_qstring(obj)), ==,
|
||||
EnumOne_lookup[i]);
|
||||
qobject_decref(obj);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_visitor_out_enum_errors(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
EnumOne i, bad_values[] = { ENUM_ONE_MAX, -1 };
|
||||
Error *errp;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bad_values) ; i++) {
|
||||
errp = NULL;
|
||||
visit_type_EnumOne(data->ov, &bad_values[i], "unused", &errp);
|
||||
g_assert(error_is_set(&errp) == true);
|
||||
error_free(errp);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct TestStruct
|
||||
{
|
||||
int64_t integer;
|
||||
bool boolean;
|
||||
char *string;
|
||||
} TestStruct;
|
||||
|
||||
static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
|
||||
const char *name, Error **errp)
|
||||
{
|
||||
visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
|
||||
errp);
|
||||
|
||||
visit_type_int(v, &(*obj)->integer, "integer", errp);
|
||||
visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
|
||||
visit_type_str(v, &(*obj)->string, "string", errp);
|
||||
|
||||
visit_end_struct(v, errp);
|
||||
}
|
||||
|
||||
static void test_visitor_out_struct(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
TestStruct test_struct = { .integer = 42,
|
||||
.boolean = false,
|
||||
.string = (char *) "foo"};
|
||||
TestStruct *p = &test_struct;
|
||||
Error *errp = NULL;
|
||||
QObject *obj;
|
||||
QDict *qdict;
|
||||
|
||||
visit_type_TestStruct(data->ov, &p, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QDICT);
|
||||
|
||||
qdict = qobject_to_qdict(obj);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 3);
|
||||
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 42);
|
||||
g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, 0);
|
||||
g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, "foo");
|
||||
|
||||
QDECREF(qdict);
|
||||
}
|
||||
|
||||
static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
int64_t value = 42;
|
||||
Error *errp = NULL;
|
||||
UserDefNested *ud2;
|
||||
QObject *obj;
|
||||
QDict *qdict, *dict1, *dict2, *dict3, *userdef;
|
||||
const char *string = "user def string";
|
||||
const char *strings[] = { "fourty two", "fourty three", "fourty four",
|
||||
"fourty five" };
|
||||
|
||||
ud2 = g_malloc0(sizeof(*ud2));
|
||||
ud2->string0 = g_strdup(strings[0]);
|
||||
|
||||
ud2->dict1.string1 = g_strdup(strings[1]);
|
||||
ud2->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne));
|
||||
ud2->dict1.dict2.userdef1->string = g_strdup(string);
|
||||
ud2->dict1.dict2.userdef1->integer = value;
|
||||
ud2->dict1.dict2.string2 = g_strdup(strings[2]);
|
||||
|
||||
ud2->dict1.has_dict3 = true;
|
||||
ud2->dict1.dict3.userdef2 = g_malloc0(sizeof(UserDefOne));
|
||||
ud2->dict1.dict3.userdef2->string = g_strdup(string);
|
||||
ud2->dict1.dict3.userdef2->integer = value;
|
||||
ud2->dict1.dict3.string3 = g_strdup(strings[3]);
|
||||
|
||||
visit_type_UserDefNested(data->ov, &ud2, "unused", &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QDICT);
|
||||
|
||||
qdict = qobject_to_qdict(obj);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 2);
|
||||
g_assert_cmpstr(qdict_get_str(qdict, "string0"), ==, strings[0]);
|
||||
|
||||
dict1 = qdict_get_qdict(qdict, "dict1");
|
||||
g_assert_cmpint(qdict_size(dict1), ==, 3);
|
||||
g_assert_cmpstr(qdict_get_str(dict1, "string1"), ==, strings[1]);
|
||||
|
||||
dict2 = qdict_get_qdict(dict1, "dict2");
|
||||
g_assert_cmpint(qdict_size(dict2), ==, 2);
|
||||
g_assert_cmpstr(qdict_get_str(dict2, "string2"), ==, strings[2]);
|
||||
userdef = qdict_get_qdict(dict2, "userdef1");
|
||||
g_assert_cmpint(qdict_size(userdef), ==, 2);
|
||||
g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value);
|
||||
g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string);
|
||||
|
||||
dict3 = qdict_get_qdict(dict1, "dict3");
|
||||
g_assert_cmpint(qdict_size(dict3), ==, 2);
|
||||
g_assert_cmpstr(qdict_get_str(dict3, "string3"), ==, strings[3]);
|
||||
userdef = qdict_get_qdict(dict3, "userdef2");
|
||||
g_assert_cmpint(qdict_size(userdef), ==, 2);
|
||||
g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value);
|
||||
g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string);
|
||||
|
||||
QDECREF(qdict);
|
||||
qapi_free_UserDefNested(ud2);
|
||||
}
|
||||
|
||||
typedef struct TestStructList
|
||||
{
|
||||
TestStruct *value;
|
||||
struct TestStructList *next;
|
||||
} TestStructList;
|
||||
|
||||
static void visit_type_TestStructList(Visitor *v, TestStructList **obj,
|
||||
const char *name, Error **errp)
|
||||
{
|
||||
GenericList *i, **head = (GenericList **)obj;
|
||||
|
||||
visit_start_list(v, name, errp);
|
||||
|
||||
for (*head = i = visit_next_list(v, head, errp); i; i = visit_next_list(v, &i, errp)) {
|
||||
TestStructList *native_i = (TestStructList *)i;
|
||||
visit_type_TestStruct(v, &native_i->value, NULL, errp);
|
||||
}
|
||||
|
||||
visit_end_list(v, errp);
|
||||
}
|
||||
|
||||
static void test_visitor_out_list(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
char *value_str = (char *) "list value";
|
||||
TestStructList *p, *head = NULL;
|
||||
const int max_items = 10;
|
||||
bool value_bool = true;
|
||||
int value_int = 10;
|
||||
Error *errp = NULL;
|
||||
QListEntry *entry;
|
||||
QObject *obj;
|
||||
QList *qlist;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < max_items; i++) {
|
||||
p = g_malloc0(sizeof(*p));
|
||||
p->value = g_malloc0(sizeof(*p->value));
|
||||
p->value->integer = value_int;
|
||||
p->value->boolean = value_bool;
|
||||
p->value->string = value_str;
|
||||
|
||||
p->next = head;
|
||||
head = p;
|
||||
}
|
||||
|
||||
visit_type_TestStructList(data->ov, &head, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
|
||||
obj = qmp_output_get_qobject(data->qov);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QLIST);
|
||||
|
||||
qlist = qobject_to_qlist(obj);
|
||||
g_assert(!qlist_empty(qlist));
|
||||
|
||||
i = 0;
|
||||
QLIST_FOREACH_ENTRY(qlist, entry) {
|
||||
QDict *qdict;
|
||||
|
||||
g_assert(qobject_type(entry->value) == QTYPE_QDICT);
|
||||
qdict = qobject_to_qdict(entry->value);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 3);
|
||||
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, value_int);
|
||||
g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, value_bool);
|
||||
g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, value_str);
|
||||
i++;
|
||||
}
|
||||
g_assert_cmpint(i, ==, max_items);
|
||||
|
||||
QDECREF(qlist);
|
||||
|
||||
for (p = head; p;) {
|
||||
TestStructList *tmp = p->next;
|
||||
g_free(p->value);
|
||||
g_free(p);
|
||||
p = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefNestedList *p, *head = NULL;
|
||||
const char string[] = "foo bar";
|
||||
int i, max_count = 1024;
|
||||
|
||||
for (i = 0; i < max_count; i++) {
|
||||
p = g_malloc0(sizeof(*p));
|
||||
p->value = g_malloc0(sizeof(*p->value));
|
||||
|
||||
p->value->string0 = g_strdup(string);
|
||||
p->value->dict1.string1 = g_strdup(string);
|
||||
p->value->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne));
|
||||
p->value->dict1.dict2.userdef1->string = g_strdup(string);
|
||||
p->value->dict1.dict2.userdef1->integer = 42;
|
||||
p->value->dict1.dict2.string2 = g_strdup(string);
|
||||
p->value->dict1.has_dict3 = false;
|
||||
|
||||
p->next = head;
|
||||
head = p;
|
||||
}
|
||||
|
||||
qapi_free_UserDefNestedList(head);
|
||||
}
|
||||
|
||||
static void output_visitor_test_add(const char *testpath,
|
||||
TestOutputVisitorData *data,
|
||||
void (*test_func)(TestOutputVisitorData *data, const void *user_data))
|
||||
{
|
||||
g_test_add(testpath, TestOutputVisitorData, data, visitor_output_setup,
|
||||
test_func, visitor_output_teardown);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
TestOutputVisitorData out_visitor_data;
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
output_visitor_test_add("/visitor/output/int",
|
||||
&out_visitor_data, test_visitor_out_int);
|
||||
output_visitor_test_add("/visitor/output/bool",
|
||||
&out_visitor_data, test_visitor_out_bool);
|
||||
output_visitor_test_add("/visitor/output/number",
|
||||
&out_visitor_data, test_visitor_out_number);
|
||||
output_visitor_test_add("/visitor/output/string",
|
||||
&out_visitor_data, test_visitor_out_string);
|
||||
output_visitor_test_add("/visitor/output/no-string",
|
||||
&out_visitor_data, test_visitor_out_no_string);
|
||||
output_visitor_test_add("/visitor/output/enum",
|
||||
&out_visitor_data, test_visitor_out_enum);
|
||||
output_visitor_test_add("/visitor/output/enum-errors",
|
||||
&out_visitor_data, test_visitor_out_enum_errors);
|
||||
output_visitor_test_add("/visitor/output/struct",
|
||||
&out_visitor_data, test_visitor_out_struct);
|
||||
output_visitor_test_add("/visitor/output/struct-nested",
|
||||
&out_visitor_data, test_visitor_out_struct_nested);
|
||||
output_visitor_test_add("/visitor/output/list",
|
||||
&out_visitor_data, test_visitor_out_list);
|
||||
output_visitor_test_add("/visitor/output/list-qapi-free",
|
||||
&out_visitor_data, test_visitor_out_list_qapi_free);
|
||||
|
||||
g_test_run();
|
||||
|
||||
return 0;
|
||||
}
|
338
test-visitor.c
338
test-visitor.c
|
@ -1,338 +0,0 @@
|
|||
#include <glib.h>
|
||||
#include "qapi/qmp-output-visitor.h"
|
||||
#include "qapi/qmp-input-visitor.h"
|
||||
#include "test-qapi-types.h"
|
||||
#include "test-qapi-visit.h"
|
||||
#include "qemu-objects.h"
|
||||
|
||||
typedef struct TestStruct
|
||||
{
|
||||
int64_t x;
|
||||
int64_t y;
|
||||
} TestStruct;
|
||||
|
||||
typedef struct TestStructList
|
||||
{
|
||||
TestStruct *value;
|
||||
struct TestStructList *next;
|
||||
} TestStructList;
|
||||
|
||||
static void visit_type_TestStruct(Visitor *v, TestStruct **obj, const char *name, Error **errp)
|
||||
{
|
||||
visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct), errp);
|
||||
visit_type_int(v, &(*obj)->x, "x", errp);
|
||||
visit_type_int(v, &(*obj)->y, "y", errp);
|
||||
visit_end_struct(v, errp);
|
||||
}
|
||||
|
||||
static void visit_type_TestStructList(Visitor *m, TestStructList ** obj, const char *name, Error **errp)
|
||||
{
|
||||
GenericList *i, **head = (GenericList **)obj;
|
||||
|
||||
visit_start_list(m, name, errp);
|
||||
|
||||
for (*head = i = visit_next_list(m, head, errp); i; i = visit_next_list(m, &i, errp)) {
|
||||
TestStructList *native_i = (TestStructList *)i;
|
||||
visit_type_TestStruct(m, &native_i->value, NULL, errp);
|
||||
}
|
||||
|
||||
visit_end_list(m, errp);
|
||||
}
|
||||
|
||||
/* test core visitor methods */
|
||||
static void test_visitor_core(void)
|
||||
{
|
||||
QmpOutputVisitor *mo;
|
||||
QmpInputVisitor *mi;
|
||||
Visitor *v;
|
||||
TestStruct ts = { 42, 82 };
|
||||
TestStruct *pts = &ts;
|
||||
TestStructList *lts = NULL;
|
||||
Error *err = NULL;
|
||||
QObject *obj;
|
||||
QList *qlist;
|
||||
QDict *qdict;
|
||||
QString *str;
|
||||
int64_t value = 0;
|
||||
|
||||
mo = qmp_output_visitor_new();
|
||||
v = qmp_output_get_visitor(mo);
|
||||
|
||||
visit_type_TestStruct(v, &pts, NULL, &err);
|
||||
|
||||
obj = qmp_output_get_qobject(mo);
|
||||
|
||||
str = qobject_to_json(obj);
|
||||
|
||||
printf("%s\n", qstring_get_str(str));
|
||||
|
||||
QDECREF(str);
|
||||
|
||||
obj = QOBJECT(qint_from_int(0x42));
|
||||
|
||||
mi = qmp_input_visitor_new(obj);
|
||||
v = qmp_input_get_visitor(mi);
|
||||
|
||||
visit_type_int(v, &value, NULL, &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
|
||||
g_assert(value == 0x42);
|
||||
|
||||
qobject_decref(obj);
|
||||
|
||||
obj = qobject_from_json("{'x': 42, 'y': 84}");
|
||||
mi = qmp_input_visitor_new(obj);
|
||||
v = qmp_input_get_visitor(mi);
|
||||
|
||||
pts = NULL;
|
||||
|
||||
visit_type_TestStruct(v, &pts, NULL, &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
|
||||
g_assert(pts != NULL);
|
||||
g_assert(pts->x == 42);
|
||||
g_assert(pts->y == 84);
|
||||
|
||||
qobject_decref(obj);
|
||||
g_free(pts);
|
||||
|
||||
/* test list input visitor */
|
||||
obj = qobject_from_json("[{'x': 42, 'y': 84}, {'x': 12, 'y': 24}]");
|
||||
mi = qmp_input_visitor_new(obj);
|
||||
v = qmp_input_get_visitor(mi);
|
||||
|
||||
visit_type_TestStructList(v, <s, NULL, &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
|
||||
g_assert(lts != NULL);
|
||||
g_assert(lts->value->x == 42);
|
||||
g_assert(lts->value->y == 84);
|
||||
|
||||
g_assert(lts->next != NULL);
|
||||
g_assert(lts->next->value->x == 12);
|
||||
g_assert(lts->next->value->y == 24);
|
||||
g_assert(lts->next->next == NULL);
|
||||
|
||||
qobject_decref(obj);
|
||||
|
||||
/* test list output visitor */
|
||||
mo = qmp_output_visitor_new();
|
||||
v = qmp_output_get_visitor(mo);
|
||||
visit_type_TestStructList(v, <s, NULL, &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
obj = qmp_output_get_qobject(mo);
|
||||
g_print("obj: %s\n", qstring_get_str(qobject_to_json(obj)));
|
||||
|
||||
qlist = qobject_to_qlist(obj);
|
||||
assert(qlist);
|
||||
obj = qlist_pop(qlist);
|
||||
qdict = qobject_to_qdict(obj);
|
||||
assert(qdict);
|
||||
assert(qdict_get_int(qdict, "x") == 42);
|
||||
assert(qdict_get_int(qdict, "y") == 84);
|
||||
qobject_decref(obj);
|
||||
|
||||
obj = qlist_pop(qlist);
|
||||
qdict = qobject_to_qdict(obj);
|
||||
assert(qdict);
|
||||
assert(qdict_get_int(qdict, "x") == 12);
|
||||
assert(qdict_get_int(qdict, "y") == 24);
|
||||
qobject_decref(obj);
|
||||
|
||||
qmp_output_visitor_cleanup(mo);
|
||||
QDECREF(qlist);
|
||||
}
|
||||
|
||||
/* test deep nesting with refs to other user-defined types */
|
||||
static void test_nested_structs(void)
|
||||
{
|
||||
QmpOutputVisitor *mo;
|
||||
QmpInputVisitor *mi;
|
||||
Visitor *v;
|
||||
UserDefOne ud1;
|
||||
UserDefOne *ud1_p = &ud1, *ud1c_p = NULL;
|
||||
UserDefTwo ud2;
|
||||
UserDefTwo *ud2_p = &ud2, *ud2c_p = NULL;
|
||||
Error *err = NULL;
|
||||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
ud1.integer = 42;
|
||||
ud1.string = strdup("forty two");
|
||||
|
||||
/* sanity check */
|
||||
mo = qmp_output_visitor_new();
|
||||
v = qmp_output_get_visitor(mo);
|
||||
visit_type_UserDefOne(v, &ud1_p, "o_O", &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
obj = qmp_output_get_qobject(mo);
|
||||
g_assert(obj);
|
||||
qobject_decref(obj);
|
||||
|
||||
ud2.string = strdup("forty three");
|
||||
ud2.dict.string = strdup("forty four");
|
||||
ud2.dict.dict.userdef = ud1_p;
|
||||
ud2.dict.dict.string = strdup("forty five");
|
||||
ud2.dict.has_dict2 = true;
|
||||
ud2.dict.dict2.userdef = ud1_p;
|
||||
ud2.dict.dict2.string = strdup("forty six");
|
||||
|
||||
/* c type -> qobject */
|
||||
mo = qmp_output_visitor_new();
|
||||
v = qmp_output_get_visitor(mo);
|
||||
visit_type_UserDefTwo(v, &ud2_p, "unused", &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
obj = qmp_output_get_qobject(mo);
|
||||
g_assert(obj);
|
||||
str = qobject_to_json_pretty(obj);
|
||||
g_print("%s\n", qstring_get_str(str));
|
||||
QDECREF(str);
|
||||
|
||||
/* qobject -> c type, should match original struct */
|
||||
mi = qmp_input_visitor_new(obj);
|
||||
v = qmp_input_get_visitor(mi);
|
||||
visit_type_UserDefTwo(v, &ud2c_p, NULL, &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
|
||||
g_assert(!g_strcmp0(ud2c_p->string, ud2.string));
|
||||
g_assert(!g_strcmp0(ud2c_p->dict.string, ud2.dict.string));
|
||||
|
||||
ud1c_p = ud2c_p->dict.dict.userdef;
|
||||
g_assert(ud1c_p->integer == ud1_p->integer);
|
||||
g_assert(!g_strcmp0(ud1c_p->string, ud1_p->string));
|
||||
|
||||
g_assert(!g_strcmp0(ud2c_p->dict.dict.string, ud2.dict.dict.string));
|
||||
|
||||
ud1c_p = ud2c_p->dict.dict2.userdef;
|
||||
g_assert(ud1c_p->integer == ud1_p->integer);
|
||||
g_assert(!g_strcmp0(ud1c_p->string, ud1_p->string));
|
||||
|
||||
g_assert(!g_strcmp0(ud2c_p->dict.dict2.string, ud2.dict.dict2.string));
|
||||
g_free(ud1.string);
|
||||
g_free(ud2.string);
|
||||
g_free(ud2.dict.string);
|
||||
g_free(ud2.dict.dict.string);
|
||||
g_free(ud2.dict.dict2.string);
|
||||
|
||||
qapi_free_UserDefTwo(ud2c_p);
|
||||
|
||||
qobject_decref(obj);
|
||||
}
|
||||
|
||||
/* test enum values */
|
||||
static void test_enums(void)
|
||||
{
|
||||
QmpOutputVisitor *mo;
|
||||
QmpInputVisitor *mi;
|
||||
Visitor *v;
|
||||
EnumOne enum1 = ENUM_ONE_VALUE2, enum1_cpy = ENUM_ONE_VALUE1;
|
||||
Error *err = NULL;
|
||||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
/* C type -> QObject */
|
||||
mo = qmp_output_visitor_new();
|
||||
v = qmp_output_get_visitor(mo);
|
||||
visit_type_EnumOne(v, &enum1, "unused", &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
obj = qmp_output_get_qobject(mo);
|
||||
g_assert(obj);
|
||||
str = qobject_to_json_pretty(obj);
|
||||
g_print("%s\n", qstring_get_str(str));
|
||||
QDECREF(str);
|
||||
g_assert(g_strcmp0(qstring_get_str(qobject_to_qstring(obj)), "value2") == 0);
|
||||
|
||||
/* QObject -> C type */
|
||||
mi = qmp_input_visitor_new(obj);
|
||||
v = qmp_input_get_visitor(mi);
|
||||
visit_type_EnumOne(v, &enum1_cpy, "unused", &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
g_debug("enum1_cpy, enum1: %d, %d", enum1_cpy, enum1);
|
||||
g_assert(enum1_cpy == enum1);
|
||||
|
||||
qobject_decref(obj);
|
||||
}
|
||||
|
||||
/* test enum values nested in schema-defined structs */
|
||||
static void test_nested_enums(void)
|
||||
{
|
||||
QmpOutputVisitor *mo;
|
||||
QmpInputVisitor *mi;
|
||||
Visitor *v;
|
||||
NestedEnumsOne *nested_enums, *nested_enums_cpy = NULL;
|
||||
Error *err = NULL;
|
||||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
nested_enums = g_malloc0(sizeof(NestedEnumsOne));
|
||||
nested_enums->enum1 = ENUM_ONE_VALUE1;
|
||||
nested_enums->enum2 = ENUM_ONE_VALUE2;
|
||||
nested_enums->enum3 = ENUM_ONE_VALUE3;
|
||||
nested_enums->enum4 = ENUM_ONE_VALUE3;
|
||||
nested_enums->has_enum2 = false;
|
||||
nested_enums->has_enum4 = true;
|
||||
|
||||
/* C type -> QObject */
|
||||
mo = qmp_output_visitor_new();
|
||||
v = qmp_output_get_visitor(mo);
|
||||
visit_type_NestedEnumsOne(v, &nested_enums, NULL, &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
obj = qmp_output_get_qobject(mo);
|
||||
g_assert(obj);
|
||||
str = qobject_to_json_pretty(obj);
|
||||
g_print("%s\n", qstring_get_str(str));
|
||||
QDECREF(str);
|
||||
|
||||
/* QObject -> C type */
|
||||
mi = qmp_input_visitor_new(obj);
|
||||
v = qmp_input_get_visitor(mi);
|
||||
visit_type_NestedEnumsOne(v, &nested_enums_cpy, NULL, &err);
|
||||
if (err) {
|
||||
g_error("%s", error_get_pretty(err));
|
||||
}
|
||||
g_assert(nested_enums_cpy);
|
||||
g_assert(nested_enums_cpy->enum1 == nested_enums->enum1);
|
||||
g_assert(nested_enums_cpy->enum3 == nested_enums->enum3);
|
||||
g_assert(nested_enums_cpy->enum4 == nested_enums->enum4);
|
||||
g_assert(nested_enums_cpy->has_enum2 == false);
|
||||
g_assert(nested_enums_cpy->has_enum4 == true);
|
||||
|
||||
qmp_output_visitor_cleanup(mo);
|
||||
qmp_input_visitor_cleanup(mi);
|
||||
qapi_free_NestedEnumsOne(nested_enums);
|
||||
qapi_free_NestedEnumsOne(nested_enums_cpy);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func("/0.15/visitor_core", test_visitor_core);
|
||||
g_test_add_func("/0.15/nested_structs", test_nested_structs);
|
||||
g_test_add_func("/0.15/enums", test_enums);
|
||||
g_test_add_func("/0.15/nested_enums", test_nested_enums);
|
||||
|
||||
g_test_run();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue