diff --git a/MAINTAINERS b/MAINTAINERS index 0b67c4826a..d858c49566 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -926,20 +926,19 @@ K: srat|SRAT T: git git://github.com/ehabkost/qemu.git numa QAPI -M: Luiz Capitulino +M: Markus Armbruster M: Michael Roth -S: Maintained +S: Supported F: qapi/ F: tests/qapi-schema/ -T: git git://repo.or.cz/qemu/qmp-unstable.git queue/qmp +T: git git://repo.or.cz/qemu/armbru.git qapi-next QAPI Schema M: Eric Blake -M: Luiz Capitulino M: Markus Armbruster S: Supported F: qapi-schema.json -T: git git://repo.or.cz/qemu/qmp-unstable.git queue/qmp +T: git git://repo.or.cz/qemu/armbru.git qapi-next QObject M: Luiz Capitulino @@ -964,13 +963,14 @@ X: qom/cpu.c F: tests/qom-test.c QMP -M: Luiz Capitulino -S: Maintained +M: Markus Armbruster +S: Supported F: qmp.c F: monitor.c F: qmp-commands.hx -F: QMP/ -T: git git://repo.or.cz/qemu/qmp-unstable.git queue/qmp +F: docs/qmp/ +F: scripts/qmp/ +T: git git://repo.or.cz/qemu/armbru.git qapi-next SLIRP M: Jan Kiszka diff --git a/block/qapi.c b/block/qapi.c index 063dd1bc1f..18d2b95f54 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -523,9 +523,6 @@ static void dump_qobject(fprintf_function func_fprintf, void *f, QDECREF(value); break; } - case QTYPE_NONE: - break; - case QTYPE_MAX: default: abort(); } diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h index 5b7acf19fa..d4be92fbee 100644 --- a/include/hw/qdev-core.h +++ b/include/hw/qdev-core.h @@ -226,7 +226,7 @@ struct Property { PropertyInfo *info; int offset; uint8_t bitnr; - uint8_t qtype; + qtype_code qtype; int64_t defval; int arrayoffset; PropertyInfo *arrayinfo; diff --git a/include/qapi/qmp/qobject.h b/include/qapi/qmp/qobject.h index d0bbc7c4a6..84b2d9fef5 100644 --- a/include/qapi/qmp/qobject.h +++ b/include/qapi/qmp/qobject.h @@ -3,7 +3,7 @@ * * Based on ideas by Avi Kivity * - * Copyright (C) 2009 Red Hat Inc. + * Copyright (C) 2009, 2015 Red Hat Inc. * * Authors: * Luiz Capitulino @@ -36,7 +36,8 @@ #include typedef enum { - QTYPE_NONE, + QTYPE_NONE, /* sentinel value, no QObject has this type code */ + QTYPE_QNULL, QTYPE_QINT, QTYPE_QSTRING, QTYPE_QDICT, @@ -110,4 +111,12 @@ static inline qtype_code qobject_type(const QObject *obj) return obj->type->code; } +extern QObject qnull_; + +static inline QObject *qnull(void) +{ + qobject_incref(&qnull_); + return &qnull_; +} + #endif /* QOBJECT_H */ diff --git a/qjson.c b/qjson.c index 0cda2690f5..e478802a46 100644 --- a/qjson.c +++ b/qjson.c @@ -24,6 +24,8 @@ struct QJSON { bool omit_comma; }; +#define QJSON(obj) OBJECT_CHECK(QJSON, (obj), TYPE_QJSON) + static void json_emit_element(QJSON *json, const char *name) { /* Check whether we need to print a , before an element */ @@ -87,7 +89,7 @@ const char *qjson_get_str(QJSON *json) QJSON *qjson_new(void) { - QJSON *json = (QJSON *)object_new(TYPE_QJSON); + QJSON *json = QJSON(object_new(TYPE_QJSON)); return json; } @@ -98,8 +100,7 @@ void qjson_finish(QJSON *json) static void qjson_initfn(Object *obj) { - QJSON *json = (QJSON *)object_dynamic_cast(obj, TYPE_QJSON); - assert(json); + QJSON *json = QJSON(obj); json->str = qstring_from_str("{ "); json->omit_comma = true; @@ -107,9 +108,8 @@ static void qjson_initfn(Object *obj) static void qjson_finalizefn(Object *obj) { - QJSON *json = (QJSON *)object_dynamic_cast(obj, TYPE_QJSON); + QJSON *json = QJSON(obj); - assert(json); qobject_decref(QOBJECT(json->str)); } diff --git a/qobject/Makefile.objs b/qobject/Makefile.objs index c9ff59c6cc..f7595f56fe 100644 --- a/qobject/Makefile.objs +++ b/qobject/Makefile.objs @@ -1,3 +1,3 @@ -util-obj-y = qint.o qstring.o qdict.o qlist.o qfloat.o qbool.o +util-obj-y = qnull.o qint.o qstring.o qdict.o qlist.o qfloat.o qbool.o util-obj-y += qjson.o json-lexer.o json-streamer.o json-parser.o util-obj-y += qerror.o diff --git a/qobject/json-parser.c b/qobject/json-parser.c index 4288267bd3..717cb8fde7 100644 --- a/qobject/json-parser.c +++ b/qobject/json-parser.c @@ -561,6 +561,8 @@ static QObject *parse_keyword(JSONParserContext *ctxt) ret = QOBJECT(qbool_from_int(true)); } else if (token_is_keyword(token, "false")) { ret = QOBJECT(qbool_from_int(false)); + } else if (token_is_keyword(token, "null")) { + ret = qnull(); } else { parse_error(ctxt, token, "invalid keyword `%s'", token_get_value(token)); goto out; diff --git a/qobject/qjson.c b/qobject/qjson.c index 12c576d548..846733dafb 100644 --- a/qobject/qjson.c +++ b/qobject/qjson.c @@ -127,6 +127,9 @@ static void to_json_list_iter(QObject *obj, void *opaque) static void to_json(const QObject *obj, QString *str, int pretty, int indent) { switch (qobject_type(obj)) { + case QTYPE_QNULL: + qstring_append(str, "null"); + break; case QTYPE_QINT: { QInt *val = qobject_to_qint(obj); char buffer[1024]; @@ -260,9 +263,8 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent) } case QTYPE_QERROR: /* XXX: should QError be emitted? */ - case QTYPE_NONE: break; - case QTYPE_MAX: + default: abort(); } } diff --git a/qobject/qnull.c b/qobject/qnull.c new file mode 100644 index 0000000000..9873e266e6 --- /dev/null +++ b/qobject/qnull.c @@ -0,0 +1,29 @@ +/* + * QNull + * + * Copyright (C) 2015 Red Hat, Inc. + * + * Authors: + * Markus Armbruster + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 + * or later. See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu-common.h" +#include "qapi/qmp/qobject.h" + +static void qnull_destroy_obj(QObject *obj) +{ + assert(0); +} + +static const QType qnull_type = { + .code = QTYPE_QNULL, + .destroy = qnull_destroy_obj, +}; + +QObject qnull_ = { + .type = &qnull_type, + .refcnt = 1, +}; diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index e0e848bc30..65280d29d1 100755 --- a/scripts/qmp/qmp-shell +++ b/scripts/qmp/qmp-shell @@ -32,6 +32,7 @@ import qmp import json +import ast import readline import sys import pprint @@ -51,6 +52,19 @@ class QMPShellError(Exception): class QMPShellBadPort(QMPShellError): pass +class FuzzyJSON(ast.NodeTransformer): + '''This extension of ast.NodeTransformer filters literal "true/false/null" + values in an AST and replaces them by proper "True/False/None" values that + Python can properly evaluate.''' + def visit_Name(self, node): + if node.id == 'true': + node.id = 'True' + if node.id == 'false': + node.id = 'False' + if node.id == 'null': + node.id = 'None' + return node + # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and # _execute_cmd()). Let's design a better one. class QMPShell(qmp.QEMUMonitorProtocol): @@ -59,6 +73,8 @@ class QMPShell(qmp.QEMUMonitorProtocol): self._greeting = None self._completer = None self._pp = pp + self._transmode = False + self._actions = list() def __get_address(self, arg): """ @@ -88,32 +104,40 @@ class QMPShell(qmp.QEMUMonitorProtocol): # clearing everything as it doesn't seem to matter readline.set_completer_delims('') - def __build_cmd(self, cmdline): - """ - Build a QMP input object from a user provided command-line in the - following format: + def __parse_value(self, val): + try: + return int(val) + except ValueError: + pass - < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] - """ - cmdargs = cmdline.split() - qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } - for arg in cmdargs[1:]: - opt = arg.split('=') + if val.lower() == 'true': + return True + if val.lower() == 'false': + return False + if val.startswith(('{', '[')): + # Try first as pure JSON: try: - if(len(opt) > 2): - opt[1] = '='.join(opt[1:]) - value = int(opt[1]) + return json.loads(val) except ValueError: - if opt[1] == 'true': - value = True - elif opt[1] == 'false': - value = False - elif opt[1].startswith('{'): - value = json.loads(opt[1]) - else: - value = opt[1] - optpath = opt[0].split('.') - parent = qmpcmd['arguments'] + pass + # Try once again as FuzzyJSON: + try: + st = ast.parse(val, mode='eval') + return ast.literal_eval(FuzzyJSON().visit(st)) + except SyntaxError: + pass + except ValueError: + pass + return val + + def __cli_expr(self, tokens, parent): + for arg in tokens: + (key, _, val) = arg.partition('=') + if not val: + raise QMPShellError("Expected a key=value pair, got '%s'" % arg) + + value = self.__parse_value(val) + optpath = key.split('.') curpath = [] for p in optpath[:-1]: curpath.append(p) @@ -126,10 +150,58 @@ class QMPShell(qmp.QEMUMonitorProtocol): if type(parent[optpath[-1]]) is dict: raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath)) else: - raise QMPShellError('Cannot set "%s" multiple times' % opt[0]) + raise QMPShellError('Cannot set "%s" multiple times' % key) parent[optpath[-1]] = value + + def __build_cmd(self, cmdline): + """ + Build a QMP input object from a user provided command-line in the + following format: + + < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] + """ + cmdargs = cmdline.split() + + # Transactional CLI entry/exit: + if cmdargs[0] == 'transaction(': + self._transmode = True + cmdargs.pop(0) + elif cmdargs[0] == ')' and self._transmode: + self._transmode = False + if len(cmdargs) > 1: + raise QMPShellError("Unexpected input after close of Transaction sub-shell") + qmpcmd = { 'execute': 'transaction', + 'arguments': { 'actions': self._actions } } + self._actions = list() + return qmpcmd + + # Nothing to process? + if not cmdargs: + return None + + # Parse and then cache this Transactional Action + if self._transmode: + finalize = False + action = { 'type': cmdargs[0], 'data': {} } + if cmdargs[-1] == ')': + cmdargs.pop(-1) + finalize = True + self.__cli_expr(cmdargs[1:], action['data']) + self._actions.append(action) + return self.__build_cmd(')') if finalize else None + + # Standard command: parse and return it to be executed. + qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } + self.__cli_expr(cmdargs[1:], qmpcmd['arguments']) return qmpcmd + def _print(self, qmp): + jsobj = json.dumps(qmp) + if self._pp is not None: + self._pp.pprint(jsobj) + else: + print str(jsobj) + def _execute_cmd(self, cmdline): try: qmpcmd = self.__build_cmd(cmdline) @@ -138,15 +210,16 @@ class QMPShell(qmp.QEMUMonitorProtocol): print 'command format: ', print '[arg-name1=arg1] ... [arg-nameN=argN]' return True + # For transaction mode, we may have just cached the action: + if qmpcmd is None: + return True + if self._verbose: + self._print(qmpcmd) resp = self.cmd_obj(qmpcmd) if resp is None: print 'Disconnected' return False - - if self._pp is not None: - self._pp.pprint(resp) - else: - print resp + self._print(resp) return True def connect(self): @@ -158,6 +231,11 @@ class QMPShell(qmp.QEMUMonitorProtocol): version = self._greeting['QMP']['version']['qemu'] print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']) + def get_prompt(self): + if self._transmode: + return "TRANS> " + return "(QEMU) " + def read_exec_command(self, prompt): """ Read and execute a command. @@ -177,6 +255,9 @@ class QMPShell(qmp.QEMUMonitorProtocol): else: return self._execute_cmd(cmdline) + def set_verbosity(self, verbose): + self._verbose = verbose + class HMPShell(QMPShell): def __init__(self, address): QMPShell.__init__(self, address) @@ -254,7 +335,7 @@ def die(msg): def fail_cmdline(option=None): if option: sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) - sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n') + sys.stderr.write('qemu-shell [ -v ] [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n') sys.exit(1) def main(): @@ -262,6 +343,7 @@ def main(): qemu = None hmp = False pp = None + verbose = False try: for arg in sys.argv[1:]: @@ -273,6 +355,8 @@ def main(): if pp is not None: fail_cmdline(arg) pp = pprint.PrettyPrinter(indent=4) + elif arg == "-v": + verbose = True else: if qemu is not None: fail_cmdline(arg) @@ -297,7 +381,8 @@ def main(): die('Could not connect to %s' % addr) qemu.show_banner() - while qemu.read_exec_command('(QEMU) '): + qemu.set_verbosity(verbose) + while qemu.read_exec_command(qemu.get_prompt()): pass qemu.close() diff --git a/tests/check-qjson.c b/tests/check-qjson.c index 95497a037e..60e5b22a98 100644 --- a/tests/check-qjson.c +++ b/tests/check-qjson.c @@ -1,6 +1,6 @@ /* * Copyright IBM, Corp. 2009 - * Copyright (c) 2013 Red Hat Inc. + * Copyright (c) 2013, 2015 Red Hat Inc. * * Authors: * Anthony Liguori @@ -1005,6 +1005,7 @@ static void keyword_literal(void) { QObject *obj; QBool *qbool; + QObject *null; QString *str; obj = qobject_from_json("true"); @@ -1041,7 +1042,7 @@ static void keyword_literal(void) g_assert(qbool_get_int(qbool) == 0); QDECREF(qbool); - + obj = qobject_from_jsonf("%i", true); g_assert(obj != NULL); g_assert(qobject_type(obj) == QTYPE_QBOOL); @@ -1050,6 +1051,16 @@ static void keyword_literal(void) g_assert(qbool_get_int(qbool) != 0); QDECREF(qbool); + + obj = qobject_from_json("null"); + g_assert(obj != NULL); + g_assert(qobject_type(obj) == QTYPE_QNULL); + + null = qnull(); + g_assert(null == obj); + + qobject_decref(obj); + qobject_decref(null); } typedef struct LiteralQDictEntry LiteralQDictEntry;