diff options
-rw-r--r-- | Makefile | 9 | ||||
-rwxr-xr-x | QMP/qmp | 126 | ||||
-rw-r--r-- | balloon.c | 28 | ||||
-rw-r--r-- | balloon.h | 3 | ||||
-rw-r--r-- | blockdev.c | 89 | ||||
-rw-r--r-- | blockdev.h | 4 | ||||
-rwxr-xr-x | configure | 5 | ||||
-rw-r--r-- | console.h | 2 | ||||
-rw-r--r-- | cpus.c | 90 | ||||
-rw-r--r-- | docs/writing-qmp-commands.txt | 642 | ||||
-rw-r--r-- | hmp-commands.hx | 36 | ||||
-rw-r--r-- | hmp.c | 148 | ||||
-rw-r--r-- | hmp.h | 12 | ||||
-rw-r--r-- | migration.c | 28 | ||||
-rw-r--r-- | migration.h | 7 | ||||
-rw-r--r-- | monitor.c | 201 | ||||
-rw-r--r-- | monitor.h | 3 | ||||
-rw-r--r-- | net.c | 10 | ||||
-rw-r--r-- | net.h | 1 | ||||
-rw-r--r-- | qapi-schema-test.json | 6 | ||||
-rw-r--r-- | qapi-schema.json | 267 | ||||
-rw-r--r-- | qerror.c | 4 | ||||
-rw-r--r-- | qerror.h | 3 | ||||
-rw-r--r-- | qmp-commands.hx | 77 | ||||
-rw-r--r-- | qmp.c | 37 | ||||
-rw-r--r-- | test-qmp-input-visitor.c | 270 | ||||
-rw-r--r-- | test-qmp-output-visitor.c | 423 | ||||
-rw-r--r-- | test-visitor.c | 338 |
28 files changed, 2153 insertions, 716 deletions
@@ -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:])) @@ -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 @@ -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 @@ -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 +} diff --git a/docs/writing-qmp-commands.txt b/docs/writing-qmp-commands.txt new file mode 100644 index 0000000..0472fc3 --- /dev/null +++ b/docs/writing-qmp-commands.txt @@ -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); +} diff --git a/hmp-commands.hx b/hmp-commands.hx index 79a9195..fdbed15 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -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 @@ -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); +} @@ -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 diff --git a/migration.c b/migration.c index 8280d71..75d4f02 100644 --- a/migration.c +++ b/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; } diff --git a/migration.h b/migration.h index 0682179..999d60f 100644 --- a/migration.h +++ b/migration.h @@ -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); @@ -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); @@ -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) @@ -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 */ diff --git a/qapi-schema-test.json b/qapi-schema-test.json index 3acedad..2b38919 100644 --- a/qapi-schema-test.json +++ b/qapi-schema-test.json @@ -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'} } diff --git a/qapi-schema.json b/qapi-schema.json index fbbdbe0..f358b49 100644 --- a/qapi-schema.json +++ b/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'} } @@ -149,6 +149,10 @@ static const QErrorStringTable qerror_table[] = { .desc = "Password incorrect", }, { + .error_fmt = QERR_IO_ERROR, + .desc = "An IO error has occurred", + }, + { .error_fmt = QERR_JSON_PARSING, .desc = "Invalid JSON syntax", }, @@ -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': {} }" diff --git a/qmp-commands.hx b/qmp-commands.hx index 94da2a8..002e7e8 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -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 @@ -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(); +} diff --git a/test-qmp-input-visitor.c b/test-qmp-input-visitor.c new file mode 100644 index 0000000..1f3ab03 --- /dev/null +++ b/test-qmp-input-visitor.c @@ -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; +} diff --git a/test-qmp-output-visitor.c b/test-qmp-output-visitor.c new file mode 100644 index 0000000..c94c208 --- /dev/null +++ b/test-qmp-output-visitor.c @@ -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; +} diff --git a/test-visitor.c b/test-visitor.c deleted file mode 100644 index f90b711..0000000 --- a/test-visitor.c +++ /dev/null @@ -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; -} |