From 4cf2d837340589155acfda993c51e66eb5800416 Mon Sep 17 00:00:00 2001 From: Eduardo Habkost Date: Sat, 25 Apr 2015 12:28:06 -0300 Subject: [PATCH 1/9] QJSON: Use OBJECT_CHECK The QJSON code used casts to (QJSON*) directly, instead of OBJECT_CHECK. There were even some functions using object_dynamic_cast() calls followed by assert(), which is exactly what OBJECT_CHECK does (by calling object_dynamic_cast_assert()). Signed-off-by: Eduardo Habkost Signed-off-by: Luiz Capitulino --- qjson.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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)); } From a7c31816288a8f20fc387d69d441413e7a8c9ff1 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 29 Apr 2015 15:35:04 -0600 Subject: [PATCH 2/9] qobject: Clean up around qtype_code QTYPE_NONE is a sentinel value. No QObject has this type code. Document it properly. Fix dump_qobject() to abort() on QTYPE_NONE, just like for any other invalid type code. Fix to_json() to abort() on all invalid type codes, not just QTYPE_MAX. Clean up Property member qtype's type: it's a qtype_code. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Signed-off-by: Luiz Capitulino --- block/qapi.c | 3 --- include/hw/qdev-core.h | 2 +- include/qapi/qmp/qobject.h | 2 +- qobject/qjson.c | 3 +-- 4 files changed, 3 insertions(+), 7 deletions(-) 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 4e673f9d29..9a0ee30db5 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..099129657a 100644 --- a/include/qapi/qmp/qobject.h +++ b/include/qapi/qmp/qobject.h @@ -36,7 +36,7 @@ #include typedef enum { - QTYPE_NONE, + QTYPE_NONE, /* sentinel value, no QObject has this type code */ QTYPE_QINT, QTYPE_QSTRING, QTYPE_QDICT, diff --git a/qobject/qjson.c b/qobject/qjson.c index 12c576d548..f2857c13ea 100644 --- a/qobject/qjson.c +++ b/qobject/qjson.c @@ -260,9 +260,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(); } } From 481b002cc81ed7fc7b06e32e9d4d495d81739d14 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 29 Apr 2015 15:35:05 -0600 Subject: [PATCH 3/9] qobject: Add a special null QObject I'm going to fix the JSON parser to recognize null. The obvious representation of JSON null as (QObject *)NULL doesn't work, because the parser already uses it as an error value. Perhaps we should change it to free NULL for null, but that's more than I can do right now. Create a special null QObject instead. The existing QDict, QList, and QString all represent something that is a pointer in C and could therefore be associated with NULL. But right now, all three of these sub-types are always non-null once created, so the new null sentinel object is intentionally unrelated to them. Signed-off-by: Markus Armbruster Signed-off-by: Eric Blake Signed-off-by: Luiz Capitulino --- include/qapi/qmp/qobject.h | 11 ++++++++++- qobject/Makefile.objs | 2 +- qobject/qjson.c | 3 +++ qobject/qnull.c | 29 +++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 qobject/qnull.c diff --git a/include/qapi/qmp/qobject.h b/include/qapi/qmp/qobject.h index 099129657a..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 @@ -37,6 +37,7 @@ typedef enum { 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/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/qjson.c b/qobject/qjson.c index f2857c13ea..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]; 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, +}; From e549e7161f37416ff66971d77d021d30057045ca Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Wed, 29 Apr 2015 15:35:06 -0600 Subject: [PATCH 4/9] json-parser: Accept 'null' in QMP We document that in QMP, the client may send any json-value for the optional "id" key, and then return that same value on reply (both success and failures, insofar as the failure happened after parsing the id). [Note that the output may not be identical to the input, as whitespace may change and since we may reorder keys within a json-object, but that this still constitutes the same json-value]. However, we were not handling the JSON literal null, which counts as a json-value per RFC 7159. Also, down the road, given the QAPI schema of {'*foo':'str'} or {'*foo':'ComplexType'}, we could decide to allow the QMP client to pass { "foo":null } instead of the current representation of { } where omitting the key is the only way to get at the default NULL value. Such a change might be useful for argument introspection (if a type in older qemu lacks 'foo' altogether, then an explicit "foo":null probe will force an easily distinguished error message for whether the optional "foo" key is even understood in newer qemu). And if we add default values to optional arguments, allowing an explicit null would be required for getting a NULL value associated with an optional string that has a non-null default. But all that can come at a later day. The 'check-unit' testsuite is enhanced to test that parsing produces the same object as explicitly requesting a reference to the special qnull object. In addition, I tested with: $ ./x86_64-softmmu/qemu-system-x86_64 -qmp stdio -nodefaults {"QMP": {"version": {"qemu": {"micro": 91, "minor": 2, "major": 2}, "package": ""}, "capabilities": []}} {"execute":"qmp_capabilities","id":null} {"return": {}, "id": null} {"id":{"a":null,"b":[1,null]},"execute":"quit"} {"return": {}, "id": {"a": null, "b": [1, null]}} {"timestamp": {"seconds": 1427742379, "microseconds": 423128}, "event": "SHUTDOWN"} Signed-off-by: Eric Blake Signed-off-by: Markus Armbruster Signed-off-by: Luiz Capitulino --- qobject/json-parser.c | 2 ++ tests/check-qjson.c | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) 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/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; From 9740618cd2a0ed85b9c1648f05f3066f525f4b2e Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 5 May 2015 10:39:15 -0400 Subject: [PATCH 5/9] MAINTAINERS: New maintainer for QMP and QAPI Markus is taking over maintership of QMP and the QAPI from me. Markus has always been a great reviewer and contributor to those subsystems. In the last few months he's also doing pull requests that are a lot more relevant than the ones I was able to do. So, this is a natural move. I'm still the maintainer of HMP and QObjects, but I'm looking for someone to take over those too. PS: This commit also fixes the file listing for the QMP entry. Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Markus Armbruster --- MAINTAINERS | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 From a7430a0badc59bd6295936e06c1869e8fe32649d Mon Sep 17 00:00:00 2001 From: John Snow Date: Wed, 29 Apr 2015 15:14:01 -0400 Subject: [PATCH 6/9] scripts: qmp-shell: refactor helpers Refactor the qmp-shell command line processing function into two components. This will be used to allow sub-expressions, which will assist us in adding transactional support to qmp-shell. Signed-off-by: John Snow Reviewed-by: Eric Blake Tested-by: Kashyap Chamarthy Signed-off-by: Luiz Capitulino --- scripts/qmp/qmp-shell | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index e0e848bc30..a9632ecdd8 100755 --- a/scripts/qmp/qmp-shell +++ b/scripts/qmp/qmp-shell @@ -88,16 +88,8 @@ 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: - - < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] - """ - cmdargs = cmdline.split() - qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } - for arg in cmdargs[1:]: + def __cli_expr(self, tokens, parent): + for arg in tokens: opt = arg.split('=') try: if(len(opt) > 2): @@ -113,7 +105,6 @@ class QMPShell(qmp.QEMUMonitorProtocol): else: value = opt[1] optpath = opt[0].split('.') - parent = qmpcmd['arguments'] curpath = [] for p in optpath[:-1]: curpath.append(p) @@ -128,6 +119,17 @@ class QMPShell(qmp.QEMUMonitorProtocol): else: raise QMPShellError('Cannot set "%s" multiple times' % opt[0]) 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() + qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } + self.__cli_expr(cmdargs[1:], qmpcmd['arguments']) return qmpcmd def _execute_cmd(self, cmdline): From 6092c3ecc4bdafee5bf07061be78a4a2cc5a5088 Mon Sep 17 00:00:00 2001 From: John Snow Date: Wed, 29 Apr 2015 15:14:02 -0400 Subject: [PATCH 7/9] scripts: qmp-shell: Expand support for QMP expressions This includes support for [] expressions, single-quotes in QMP expressions (which is not strictly a part of JSON), and the ability to use "True", "False" and "None" literals instead of JSON's equivalent true, false, and null literals. qmp-shell currently allows you to describe values as JSON expressions: key={"key":{"key2":"val"}} But it does not currently support arrays, which are needed for serializing and deserializing transactions: key=[{"type":"drive-backup","data":{...}}] qmp-shell also only currently accepts doubly quoted strings as-per JSON spec, but QMP allows single quotes. Lastly, python allows you to utilize "True" or "False" as boolean literals, but JSON expects "true" or "false". Expand qmp-shell to allow the user to type either, converting to the correct type. As a consequence of the above, the key=val parsing is also improved to give better error messages if a key=val token is not provided. CAVEAT: The parser is still extremely rudimentary and does not expect to find spaces in {} nor [] expressions. This patch does not improve this functionality. Signed-off-by: John Snow Reviewed-by: Eric Blake Tested-by: Kashyap Chamarthy Signed-off-by: Luiz Capitulino --- scripts/qmp/qmp-shell | 63 ++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index a9632ecdd8..7f2c554b47 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): @@ -88,23 +102,40 @@ class QMPShell(qmp.QEMUMonitorProtocol): # clearing everything as it doesn't seem to matter readline.set_completer_delims('') + def __parse_value(self, val): + try: + return int(val) + except ValueError: + pass + + if val.lower() == 'true': + return True + if val.lower() == 'false': + return False + if val.startswith(('{', '[')): + # Try first as pure JSON: + try: + return json.loads(val) + except ValueError: + 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: - opt = arg.split('=') - try: - if(len(opt) > 2): - opt[1] = '='.join(opt[1:]) - value = int(opt[1]) - 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('.') + (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) @@ -117,7 +148,7 @@ 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): From 30bd6815efbaf5bae70885feac9a35e149e2f1ad Mon Sep 17 00:00:00 2001 From: John Snow Date: Wed, 29 Apr 2015 15:14:03 -0400 Subject: [PATCH 8/9] scripts: qmp-shell: add transaction subshell Add a special processing mode to craft transactions. By entering "transaction(" the shell will enter a special mode where each subsequent command will be saved as a transaction instead of executed as an individual command. The transaction can be submitted by entering ")" on a line by itself. Examples: Separate lines: (QEMU) transaction( TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1 TRANS> block-dirty-bitmap-clear node=drive0 name=bitmap0 TRANS> ) With a transaction action included on the first line: (QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap2 TRANS> block-dirty-bitmap-add node=drive0 name=bitmap3 TRANS> ) As a one-liner, with just one transaction action: (QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap0 ) As a side-effect of this patch, blank lines are now parsed as no-ops, regardless of which shell mode you are in. Signed-off-by: John Snow Reviewed-by: Eric Blake Tested-by: Kashyap Chamarthy Signed-off-by: Luiz Capitulino --- scripts/qmp/qmp-shell | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index 7f2c554b47..1df2ca7eef 100755 --- a/scripts/qmp/qmp-shell +++ b/scripts/qmp/qmp-shell @@ -73,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): """ @@ -159,6 +161,36 @@ class QMPShell(qmp.QEMUMonitorProtocol): < 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 @@ -171,6 +203,9 @@ 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 resp = self.cmd_obj(qmpcmd) if resp is None: print 'Disconnected' @@ -191,6 +226,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. @@ -330,7 +370,7 @@ def main(): die('Could not connect to %s' % addr) qemu.show_banner() - while qemu.read_exec_command('(QEMU) '): + while qemu.read_exec_command(qemu.get_prompt()): pass qemu.close() From 1ceca07e48ead0dd2e41576c81d40e6a91cafefd Mon Sep 17 00:00:00 2001 From: John Snow Date: Wed, 29 Apr 2015 15:14:04 -0400 Subject: [PATCH 9/9] scripts: qmp-shell: Add verbose flag Add a verbose flag that shows the QMP command that was constructed, to allow for later copy/pasting, reference, debugging, etc. The QMP is converted from a Python literal to JSON first, to ensure that it is viable input to the actual QMP parser. As a side-effect, this JSON output will helpfully show all the necessary conversions that were performed on the input, illustrating that "True" was transformed back into "true", literal values are now escaped with "" instead of '', and so on. Signed-off-by: John Snow Reviewed-by: Eric Blake Tested-by: Kashyap Chamarthy Signed-off-by: Luiz Capitulino --- scripts/qmp/qmp-shell | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index 1df2ca7eef..65280d29d1 100755 --- a/scripts/qmp/qmp-shell +++ b/scripts/qmp/qmp-shell @@ -195,6 +195,13 @@ class QMPShell(qmp.QEMUMonitorProtocol): 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) @@ -206,15 +213,13 @@ class QMPShell(qmp.QEMUMonitorProtocol): # 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): @@ -250,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) @@ -327,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(): @@ -335,6 +343,7 @@ def main(): qemu = None hmp = False pp = None + verbose = False try: for arg in sys.argv[1:]: @@ -346,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) @@ -370,6 +381,7 @@ def main(): die('Could not connect to %s' % addr) qemu.show_banner() + qemu.set_verbosity(verbose) while qemu.read_exec_command(qemu.get_prompt()): pass qemu.close()