Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2015-09-21' into staging

qapi: QMP introspection

# gpg: Signature made Mon 21 Sep 2015 08:59:17 BST using RSA key ID EB918653
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>"
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>"

* remotes/armbru/tags/pull-qapi-2015-09-21: (26 commits)
  qapi-introspect: Hide type names
  qapi: New QMP command query-qmp-schema for QMP introspection
  qapi: Pseudo-type '**' is now unused, drop it
  qapi-schema: Fix up misleading specification of netdev_add
  qom: Don't use 'gen': false for qom-get, qom-set, object-add
  qapi: Introduce a first class 'any' type
  qapi: Make output visitor return qnull() instead of NULL
  qapi: Improve built-in type documentation
  qapi-commands: De-duplicate output marshaling functions
  qapi: De-duplicate parameter list generation
  qapi: Rename qmp_marshal_input_FOO() to qmp_marshal_FOO()
  qapi-commands: Rearrange code
  qapi-visit: Rearrange code a bit
  qapi: Clean up after recent conversions to QAPISchemaVisitor
  qapi: Replace dirty is_c_ptr() by method c_null()
  qapi-event: Convert to QAPISchemaVisitor, fixing data with base
  qapi-event: Eliminate global variable event_enum_value
  qapi: De-duplicate enum code generation
  qapi-commands: Convert to QAPISchemaVisitor
  qapi-visit: Convert to QAPISchemaVisitor, fixing bugs
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell
2015-09-21 22:33:51 +01:00
60 changed files with 2630 additions and 1201 deletions
+1
View File
@@ -34,6 +34,7 @@
/qapi-visit.[ch]
/qapi-event.[ch]
/qmp-commands.h
/qmp-introspect.[ch]
/qmp-marshal.c
/qemu-doc.html
/qemu-tech.html
+8 -1
View File
@@ -52,6 +52,8 @@ endif
GENERATED_HEADERS = config-host.h qemu-options.def
GENERATED_HEADERS += qmp-commands.h qapi-types.h qapi-visit.h qapi-event.h
GENERATED_SOURCES += qmp-marshal.c qapi-types.c qapi-visit.c qapi-event.c
GENERATED_HEADERS += qmp-introspect.h
GENERATED_SOURCES += qmp-introspect.c
GENERATED_HEADERS += trace/generated-events.h
GENERATED_SOURCES += trace/generated-events.c
@@ -269,7 +271,7 @@ $(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
$(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
$(SRC_PATH)/qapi/event.json
$(SRC_PATH)/qapi/event.json $(SRC_PATH)/qapi/introspect.json
qapi-types.c qapi-types.h :\
$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
@@ -291,6 +293,11 @@ $(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
$(gen-out-type) -o "." -m $<, \
" GEN $@")
qmp-introspect.h qmp-introspect.c :\
$(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
$(gen-out-type) -o "." $<, \
" GEN $@")
QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
$(qga-obj-y) qemu-ga.o: $(QGALIB_GEN)
+3 -1
View File
@@ -1,7 +1,8 @@
#######################################################################
# Common libraries for tools and emulators
stub-obj-y = stubs/
util-obj-y = util/ qobject/ qapi/ qapi-types.o qapi-visit.o qapi-event.o
util-obj-y = util/ qobject/ qapi/
util-obj-y += qmp-introspect.o qapi-types.o qapi-visit.o qapi-event.o
#######################################################################
# block-obj-y is code used by both qemu system emulation and qemu-img
@@ -92,6 +93,7 @@ common-obj-$(CONFIG_FDT) += device_tree.o
# qapi
common-obj-y += qmp-marshal.o
common-obj-y += qmp-introspect.o
common-obj-y += qmp.o hmp.o
endif
+291 -64
View File
@@ -111,10 +111,7 @@ and field names within a type, should be all lower case with words
separated by a hyphen. However, some existing older commands and
complex types use underscore; when extending such expressions,
consistency is preferred over blindly avoiding underscore. Event
names should be ALL_CAPS with words separated by underscore. The
special string '**' appears for some commands that manually perform
their own type checking rather than relying on the type-safe code
produced by the qapi code generators.
names should be ALL_CAPS with words separated by underscore.
Any name (command, event, type, field, or enum value) beginning with
"x-" is marked experimental, and may be withdrawn or changed
@@ -140,17 +137,25 @@ must have a value that forms a struct name.
=== Built-in Types ===
The following types are built-in to the parser:
'str' - arbitrary UTF-8 string
'int' - 64-bit signed integer (although the C code may place further
restrictions on acceptable range)
'number' - floating point number
'bool' - JSON value of true or false
'int8', 'int16', 'int32', 'int64' - like 'int', but enforce maximum
bit size
'uint8', 'uint16', 'uint32', 'uint64' - unsigned counterparts
'size' - like 'uint64', but allows scaled suffix from command line
visitor
The following types are predefined, and map to C as follows:
Schema C JSON
str char * any JSON string, UTF-8
number double any JSON number
int int64_t a JSON number without fractional part
that fits into the C integer type
int8 int8_t likewise
int16 int16_t likewise
int32 int32_t likewise
int64 int64_t likewise
uint8 uint8_t likewise
uint16 uint16_t likewise
uint32 uint32_t likewise
uint64 uint64_t likewise
size uint64_t like uint64_t, except StringInputVisitor
accepts size suffixes
bool bool JSON true or false
any QObject * any JSON value
=== Includes ===
@@ -453,17 +458,14 @@ which would validate this Client JSON Protocol transaction:
<= { "return": [ { "value": "one" }, { } ] }
In rare cases, QAPI cannot express a type-safe representation of a
corresponding Client JSON Protocol command. In these cases, if the
command expression includes the key 'gen' with boolean value false,
then the 'data' or 'returns' member that intends to bypass generated
type-safety and do its own manual validation should use an inline
dictionary definition, with a value of '**' rather than a valid type
name for the keys that the generated code will not validate. Please
try to avoid adding new commands that rely on this, and instead use
type-safe unions. For an example of bypass usage:
corresponding Client JSON Protocol command. You then have to suppress
generation of a marshalling function by including a key 'gen' with
boolean value false, and instead write your own function. Please try
to avoid adding new commands that rely on this, and instead use
type-safe unions. For an example of this usage:
{ 'command': 'netdev_add',
'data': {'type': 'str', 'id': 'str', '*props': '**'},
'data': {'type': 'str', 'id': 'str'},
'gen': false }
Normally, the QAPI schema is used to describe synchronous exchanges,
@@ -500,13 +502,204 @@ Resulting in this JSON object:
"timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
== Client JSON Protocol introspection ==
Clients of a Client JSON Protocol commonly need to figure out what
exactly the server (QEMU) supports.
For this purpose, QMP provides introspection via command
query-qmp-schema. QGA currently doesn't support introspection.
query-qmp-schema returns a JSON array of SchemaInfo objects. These
objects together describe the wire ABI, as defined in the QAPI schema.
However, the SchemaInfo can't reflect all the rules and restrictions
that apply to QMP. It's interface introspection (figuring out what's
there), not interface specification. The specification is in the QAPI
schema. To understand how QMP is to be used, you need to study the
QAPI schema.
Like any other command, query-qmp-schema is itself defined in the QAPI
schema, along with the SchemaInfo type. This text attempts to give an
overview how things work. For details you need to consult the QAPI
schema.
SchemaInfo objects have common members "name" and "meta-type", and
additional variant members depending on the value of meta-type.
Each SchemaInfo object describes a wire ABI entity of a certain
meta-type: a command, event or one of several kinds of type.
SchemaInfo for commands and events have the same name as in the QAPI
schema.
Command and event names are part of the wire ABI, but type names are
not. Therefore, the SchemaInfo for types have auto-generated
meaningless names. For readability, the examples in this section use
meaningful type names instead.
To examine a type, start with a command or event using it, then follow
references by name.
QAPI schema definitions not reachable that way are omitted.
The SchemaInfo for a command has meta-type "command", and variant
members "arg-type" and "ret-type". On the wire, the "arguments"
member of a client's "execute" command must conform to the object type
named by "arg-type". The "return" member that the server passes in a
success response conforms to the type named by "ret-type".
If the command takes no arguments, "arg-type" names an object type
without members. Likewise, if the command returns nothing, "ret-type"
names an object type without members.
Example: the SchemaInfo for command query-qmp-schema
{ "name": "query-qmp-schema", "meta-type": "command",
"arg-type": ":empty", "ret-type": "SchemaInfoList" }
Type ":empty" is an object type without members, and type
"SchemaInfoList" is the array of SchemaInfo type.
The SchemaInfo for an event has meta-type "event", and variant member
"arg-type". On the wire, a "data" member that the server passes in an
event conforms to the object type named by "arg-type".
If the event carries no additional information, "arg-type" names an
object type without members. The event may not have a data member on
the wire then.
Each command or event defined with dictionary-valued 'data' in the
QAPI schema implicitly defines an object type.
Example: the SchemaInfo for EVENT_C from section Events
{ "name": "EVENT_C", "meta-type": "event",
"arg-type": ":obj-EVENT_C-arg" }
Type ":obj-EVENT_C-arg" is an implicitly defined object type with
the two members from the event's definition.
The SchemaInfo for struct and union types has meta-type "object".
The SchemaInfo for a struct type has variant member "members".
The SchemaInfo for a union type additionally has variant members "tag"
and "variants".
"members" is a JSON array describing the object's common members, if
any. Each element is a JSON object with members "name" (the member's
name), "type" (the name of its type), and optionally "default". The
member is optional if "default" is present. Currently, "default" can
only have value null. Other values are reserved for future
extensions.
Example: the SchemaInfo for MyType from section Struct types
{ "name": "MyType", "meta-type": "object",
"members": [
{ "name": "member1", "type": "str" },
{ "name": "member2", "type": "int" },
{ "name": "member3", "type": "str", "default": null } ] }
"tag" is the name of the common member serving as type tag.
"variants" is a JSON array describing the object's variant members.
Each element is a JSON object with members "case" (the value of type
tag this element applies to) and "type" (the name of an object type
that provides the variant members for this type tag value).
Example: the SchemaInfo for flat union BlockdevOptions from section
Union types
{ "name": "BlockdevOptions", "meta-type": "object",
"members": [
{ "name": "driver", "type": "BlockdevDriver" },
{ "name": "readonly", "type": "bool"} ],
"tag": "driver",
"variants": [
{ "case": "file", "type": "FileOptions" },
{ "case": "qcow2", "type": "Qcow2Options" } ] }
Note that base types are "flattened": its members are included in the
"members" array.
A simple union implicitly defines an enumeration type for its implicit
discriminator (called "type" on the wire, see section Union types).
A simple union implicitly defines an object type for each of its
variants.
Example: the SchemaInfo for simple union BlockdevOptions from section
Union types
{ "name": "BlockdevOptions", "meta-type": "object",
"members": [
{ "name": "kind", "type": "BlockdevOptionsKind" } ],
"tag": "type",
"variants": [
{ "case": "file", "type": ":obj-FileOptions-wrapper" },
{ "case": "qcow2", "type": ":obj-Qcow2Options-wrapper" } ] }
Enumeration type "BlockdevOptionsKind" and the object types
":obj-FileOptions-wrapper", ":obj-Qcow2Options-wrapper" are
implicitly defined.
The SchemaInfo for an alternate type has meta-type "alternate", and
variant member "members". "members" is a JSON array. Each element is
a JSON object with member "type", which names a type. Values of the
alternate type conform to exactly one of its member types.
Example: the SchemaInfo for BlockRef from section Alternate types
{ "name": "BlockRef", "meta-type": "alternate",
"members": [
{ "type": "BlockdevOptions" },
{ "type": "str" } ] }
The SchemaInfo for an array type has meta-type "array", and variant
member "element-type", which names the array's element type. Array
types are implicitly defined.
Example: the SchemaInfo for ['str']
{ "name": "strList", "meta-type": "array",
"element-type": "str" }
The SchemaInfo for an enumeration type has meta-type "enum" and
variant member "values".
Example: the SchemaInfo for MyEnum from section Enumeration types
{ "name": "MyEnum", "meta-type": "enum",
"values": [ "value1", "value2", "value3" ] }
The SchemaInfo for a built-in type has the same name as the type in
the QAPI schema (see section Built-in Types), with one exception
detailed below. It has variant member "json-type" that shows how
values of this type are encoded on the wire.
Example: the SchemaInfo for str
{ "name": "str", "meta-type": "builtin", "json-type": "string" }
The QAPI schema supports a number of integer types that only differ in
how they map to C. They are identical as far as SchemaInfo is
concerned. Therefore, they get all mapped to a single type "int" in
SchemaInfo.
As explained above, type names are not part of the wire ABI. Not even
the names of built-in types. Clients should examine member
"json-type" instead of hard-coding names of built-in types.
== Code generation ==
Schemas are fed into 3 scripts to generate all the code/files that, paired
with the core QAPI libraries, comprise everything required to take JSON
commands read in by a Client JSON Protocol server, unmarshal the arguments into
the underlying C types, call into the corresponding C function, and map the
response back to a Client JSON Protocol response to be returned to the user.
Schemas are fed into four scripts to generate all the code/files that,
paired with the core QAPI libraries, comprise everything required to
take JSON commands read in by a Client JSON Protocol server, unmarshal
the arguments into the underlying C types, call into the corresponding
C function, and map the response back to a Client JSON Protocol
response to be returned to the user.
As an example, we'll use the following schema, which describes a single
complex user-defined type (which will produce a C struct, along with a list
@@ -545,22 +738,6 @@ Example:
$ cat qapi-generated/example-qapi-types.c
[Uninteresting stuff omitted...]
void qapi_free_UserDefOneList(UserDefOneList *obj)
{
QapiDeallocVisitor *md;
Visitor *v;
if (!obj) {
return;
}
md = qapi_dealloc_visitor_new();
v = qapi_dealloc_get_visitor(md);
visit_type_UserDefOneList(v, &obj, NULL, NULL);
qapi_dealloc_visitor_cleanup(md);
}
void qapi_free_UserDefOne(UserDefOne *obj)
{
QapiDeallocVisitor *md;
@@ -575,6 +752,21 @@ Example:
visit_type_UserDefOne(v, &obj, NULL, NULL);
qapi_dealloc_visitor_cleanup(md);
}
void qapi_free_UserDefOneList(UserDefOneList *obj)
{
QapiDeallocVisitor *md;
Visitor *v;
if (!obj) {
return;
}
md = qapi_dealloc_visitor_new();
v = qapi_dealloc_get_visitor(md);
visit_type_UserDefOneList(v, &obj, NULL, NULL);
qapi_dealloc_visitor_cleanup(md);
}
$ cat qapi-generated/example-qapi-types.h
[Uninteresting stuff omitted...]
@@ -585,25 +777,25 @@ Example:
typedef struct UserDefOne UserDefOne;
typedef struct UserDefOneList {
union {
UserDefOne *value;
uint64_t padding;
};
struct UserDefOneList *next;
} UserDefOneList;
[Functions on built-in types omitted...]
typedef struct UserDefOneList UserDefOneList;
struct UserDefOne {
int64_t integer;
char *string;
};
void qapi_free_UserDefOneList(UserDefOneList *obj);
void qapi_free_UserDefOne(UserDefOne *obj);
struct UserDefOneList {
union {
UserDefOne *value;
uint64_t padding;
};
UserDefOneList *next;
};
void qapi_free_UserDefOneList(UserDefOneList *obj);
#endif
=== scripts/qapi-visit.py ===
@@ -722,7 +914,7 @@ Example:
$ cat qapi-generated/example-qmp-marshal.c
[Uninteresting stuff omitted...]
static void qmp_marshal_output_my_command(UserDefOne *ret_in, QObject **ret_out, Error **errp)
static void qmp_marshal_output_UserDefOne(UserDefOne *ret_in, QObject **ret_out, Error **errp)
{
Error *local_err = NULL;
QmpOutputVisitor *mo = qmp_output_visitor_new();
@@ -745,7 +937,7 @@ Example:
qapi_dealloc_visitor_cleanup(md);
}
static void qmp_marshal_input_my_command(QDict *args, QObject **ret, Error **errp)
static void qmp_marshal_my_command(QDict *args, QObject **ret, Error **errp)
{
Error *local_err = NULL;
UserDefOne *retval;
@@ -765,7 +957,7 @@ Example:
goto out;
}
qmp_marshal_output_my_command(retval, ret, &local_err);
qmp_marshal_output_UserDefOne(retval, ret, &local_err);
out:
error_propagate(errp, local_err);
@@ -778,7 +970,7 @@ Example:
static void qmp_init_marshal(void)
{
qmp_register_command("my-command", qmp_marshal_input_my_command, QCO_NO_OPTIONS);
qmp_register_command("my-command", qmp_marshal_my_command, QCO_NO_OPTIONS);
}
qapi_init(qmp_init_marshal);
@@ -830,9 +1022,9 @@ Example:
QDECREF(qmp);
}
const char *example_QAPIEvent_lookup[] = {
"MY_EVENT",
NULL,
const char *const example_QAPIEvent_lookup[] = {
[EXAMPLE_QAPI_EVENT_MY_EVENT] = "MY_EVENT",
[EXAMPLE_QAPI_EVENT_MAX] = NULL,
};
$ cat qapi-generated/example-qapi-event.h
[Uninteresting stuff omitted...]
@@ -847,10 +1039,45 @@ Example:
void qapi_event_send_my_event(Error **errp);
extern const char *example_QAPIEvent_lookup[];
typedef enum example_QAPIEvent {
EXAMPLE_QAPI_EVENT_MY_EVENT = 0,
EXAMPLE_QAPI_EVENT_MAX = 1,
} example_QAPIEvent;
extern const char *const example_QAPIEvent_lookup[];
#endif
=== scripts/qapi-introspect.py ===
Used to generate the introspection C code for a schema. The following
files are created:
$(prefix)qmp-introspect.c - Defines a string holding a JSON
description of the schema.
$(prefix)qmp-introspect.h - Declares the above string.
Example:
$ python scripts/qapi-introspect.py --output-dir="qapi-generated"
--prefix="example-" example-schema.json
$ cat qapi-generated/example-qmp-introspect.c
[Uninteresting stuff omitted...]
const char example_qmp_schema_json[] = "["
"{\"arg-type\": \"0\", \"meta-type\": \"event\", \"name\": \"MY_EVENT\"}, "
"{\"arg-type\": \"1\", \"meta-type\": \"command\", \"name\": \"my-command\", \"ret-type\": \"2\"}, "
"{\"members\": [], \"meta-type\": \"object\", \"name\": \"0\"}, "
"{\"members\": [{\"name\": \"arg1\", \"type\": \"2\"}], \"meta-type\": \"object\", \"name\": \"1\"}, "
"{\"members\": [{\"name\": \"integer\", \"type\": \"int\"}, {\"name\": \"string\", \"type\": \"str\"}], \"meta-type\": \"object\", \"name\": \"2\"}, "
"{\"json-type\": \"int\", \"meta-type\": \"builtin\", \"name\": \"int\"}, "
"{\"json-type\": \"string\", \"meta-type\": \"builtin\", \"name\": \"str\"}]";
$ cat qapi-generated/example-qmp-introspect.h
[Uninteresting stuff omitted...]
#ifndef EXAMPLE_QMP_INTROSPECT_H
#define EXAMPLE_QMP_INTROSPECT_H
extern const char example_qmp_schema_json[];
#endif
+4 -4
View File
@@ -127,7 +127,7 @@ following at the bottom:
{
.name = "hello-world",
.args_type = "",
.mhandler.cmd_new = qmp_marshal_input_hello_world,
.mhandler.cmd_new = qmp_marshal_hello_world,
},
You're done. Now build qemu, run it as suggested in the "Testing" section,
@@ -179,7 +179,7 @@ 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,
.mhandler.cmd_new = qmp_marshal_hello_world,
},
Notice that the "args_type" member got our "message" argument. The character
@@ -461,7 +461,7 @@ 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,
.mhandler.cmd_new = qmp_marshal_query_alarm_clock,
},
Time to test the new command. Build qemu, run it as described in the "Testing"
@@ -607,7 +607,7 @@ 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,
.mhandler.cmd_new = qmp_marshal_query_alarm_methods,
},
Now Build qemu, run it as explained in the "Testing" section and try our new
-3
View File
@@ -42,9 +42,6 @@ void monitor_read_command(Monitor *mon, int show_prompt);
int monitor_read_password(Monitor *mon, ReadLineFunc *readline_func,
void *opaque);
void qmp_qom_set(QDict *qdict, QObject **ret, Error **errp);
void qmp_qom_get(QDict *qdict, QObject **ret, Error **errp);
void qmp_object_add(QDict *qdict, QObject **ret, Error **errp);
void object_add(const char *type, const char *id, const QDict *qdict,
Visitor *v, Error **errp);
+2
View File
@@ -40,6 +40,8 @@ struct Visitor
void (*type_str)(Visitor *v, char **obj, const char *name, Error **errp);
void (*type_number)(Visitor *v, double *obj, const char *name,
Error **errp);
void (*type_any)(Visitor *v, QObject **obj, const char *name,
Error **errp);
/* May be NULL */
void (*optional)(Visitor *v, bool *present, const char *name,
+1
View File
@@ -58,6 +58,7 @@ void visit_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp);
void visit_type_bool(Visitor *v, bool *obj, const char *name, Error **errp);
void visit_type_str(Visitor *v, char **obj, const char *name, Error **errp);
void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp);
void visit_type_any(Visitor *v, QObject **obj, const char *name, Error **errp);
bool visit_start_union(Visitor *v, bool data_present, Error **errp);
void visit_end_union(Visitor *v, bool data_present, Error **errp);
+17 -1
View File
@@ -74,6 +74,7 @@
#include "block/qapi.h"
#include "qapi/qmp-event.h"
#include "qapi-event.h"
#include "qmp-introspect.h"
#include "sysemu/block-backend.h"
/* for hmp_info_irq/pic */
@@ -928,6 +929,21 @@ EventInfoList *qmp_query_events(Error **errp)
return ev_list;
}
/*
* Minor hack: generated marshalling suppressed for this command
* ('gen': false in the schema) so we can parse the JSON string
* directly into QObject instead of first parsing it with
* visit_type_SchemaInfoList() into a SchemaInfoList, then marshal it
* to QObject with generated output marshallers, every time. Instead,
* we do it in test-qmp-input-visitor.c, just to make sure
* qapi-introspect.py's output actually conforms to the schema.
*/
static void qmp_query_qmp_schema(QDict *qdict, QObject **ret_data,
Error **errp)
{
*ret_data = qobject_from_json(qmp_schema_json);
}
/* set the current CPU defined by the user */
int monitor_set_cpu(int cpu_index)
{
@@ -3912,7 +3928,7 @@ static QObject *get_qmp_greeting(void)
{
QObject *ver = NULL;
qmp_marshal_input_query_version(NULL, &ver, NULL);
qmp_marshal_query_version(NULL, &ver, NULL);
return qobject_from_jsonf("{'QMP':{'version': %p,'capabilities': []}}",ver);
}
+13 -12
View File
@@ -17,6 +17,9 @@
# Tracing commands
{ 'include': 'qapi/trace.json' }
# QAPI introspection
{ 'include': 'qapi/introspect.json' }
##
# @LostTickPolicy:
#
@@ -1698,8 +1701,7 @@
##
{ 'command': 'qom-get',
'data': { 'path': 'str', 'property': 'str' },
'returns': '**',
'gen': false }
'returns': 'any' }
##
# @qom-set:
@@ -1716,8 +1718,7 @@
# Since: 1.2
##
{ 'command': 'qom-set',
'data': { 'path': 'str', 'property': 'str', 'value': '**' },
'gen': false }
'data': { 'path': 'str', 'property': 'str', 'value': 'any' } }
##
# @set_password:
@@ -2081,11 +2082,12 @@
#
# @id: the name of the new network backend
#
# @props: #optional a list of properties to be passed to the backend in
# the format 'name=value', like 'ifname=tap0,script=no'
# Additional arguments depend on the type.
#
# Notes: The semantics of @props is not well defined. Future commands will be
# introduced that provide stronger typing for backend creation.
# TODO This command effectively bypasses QAPI completely due to its
# "additional arguments" business. It shouldn't have been added to
# the schema in this form. It should be qapified properly, or
# replaced by a properly qapified command.
#
# Since: 0.14.0
#
@@ -2093,8 +2095,8 @@
# If @type is not a valid network backend, DeviceNotFound
##
{ 'command': 'netdev_add',
'data': {'type': 'str', 'id': 'str', '*props': '**'},
'gen': false }
'data': {'type': 'str', 'id': 'str'},
'gen': false } # so we can get the additional arguments
##
# @netdev_del:
@@ -2127,8 +2129,7 @@
# Since: 2.0
##
{ 'command': 'object-add',
'data': {'qom-type': 'str', 'id': 'str', '*props': '**'},
'gen': false }
'data': {'qom-type': 'str', 'id': 'str', '*props': 'any'} }
##
# @object-del:
+273
View File
@@ -0,0 +1,273 @@
# -*- Mode: Python -*-
#
# QAPI/QMP introspection
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Authors:
# Markus Armbruster <armbru@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.
##
# @query-qmp-schema
#
# Command query-qmp-schema exposes the QMP wire ABI as an array of
# SchemaInfo. This lets QMP clients figure out what commands and
# events are available in this QEMU, and their parameters and results.
#
# However, the SchemaInfo can't reflect all the rules and restrictions
# that apply to QMP. It's interface introspection (figuring out
# what's there), not interface specification. The specification is in
# the QAPI schema.
#
# Returns: array of @SchemaInfo, where each element describes an
# entity in the ABI: command, event, type, ...
#
# Note: the QAPI schema is also used to help define *internal*
# interfaces, by defining QAPI types. These are not part of the QMP
# wire ABI, and therefore not returned by this command.
#
# Since: 2.5
##
{ 'command': 'query-qmp-schema',
'returns': [ 'SchemaInfo' ],
'gen': false } # just to simplify qmp_query_json()
##
# @SchemaMetaType
#
# This is a @SchemaInfo's meta type, i.e. the kind of entity it
# describes.
#
# @builtin: a predefined type such as 'int' or 'bool'.
#
# @enum: an enumeration type
#
# @array: an array type
#
# @object: an object type (struct or union)
#
# @alternate: an alternate type
#
# @command: a QMP command
#
# @event: a QMP event
#
# Since: 2.5
##
{ 'enum': 'SchemaMetaType',
'data': [ 'builtin', 'enum', 'array', 'object', 'alternate',
'command', 'event' ] }
##
# @SchemaInfoBase
#
# Members common to any @SchemaInfo.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoBase',
'data': { 'name': 'str', 'meta-type': 'SchemaMetaType' } }
##
# @SchemaInfo
#
# @name: the entity's name, inherited from @base.
# Commands and events have the name defined in the QAPI schema.
# Unlike command and event names, type names are not part of
# the wire ABI. Consequently, type names are meaningless
# strings here.
#
# All references to other SchemaInfo are by name.
#
# @meta-type: the entity's meta type, inherited from @base.
#
# Additional members depend on the value of @meta-type.
#
# Since: 2.5
##
{ 'union': 'SchemaInfo',
'base': 'SchemaInfoBase',
'discriminator': 'meta-type',
'data': {
'builtin': 'SchemaInfoBuiltin',
'enum': 'SchemaInfoEnum',
'array': 'SchemaInfoArray',
'object': 'SchemaInfoObject',
'alternate': 'SchemaInfoAlternate',
'command': 'SchemaInfoCommand',
'event': 'SchemaInfoEvent' } }
##
# @SchemaInfoBuiltin
#
# Additional SchemaInfo members for meta-type 'builtin'.
#
# @json-type: the JSON type used for this type on the wire.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoBuiltin',
'data': { 'json-type': 'JSONType' } }
##
# @JSONType
#
# The four primitive and two structured types according to RFC 7159
# section 1, plus 'int' (split off 'number'), plus the obvious top
# type 'value'.
#
# Since: 2.5
##
{ 'enum': 'JSONType',
'data': [ 'string', 'number', 'int', 'boolean', 'null',
'object', 'array', 'value' ] }
##
# @SchemaInfoEnum
#
# Additional SchemaInfo members for meta-type 'enum'.
#
# @values: the enumeration type's values.
#
# Values of this type are JSON string on the wire.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoEnum',
'data': { 'values': ['str'] } }
##
# @SchemaInfoArray
#
# Additional SchemaInfo members for meta-type 'array'.
#
# @element-type: the array type's element type.
#
# Values of this type are JSON array on the wire.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoArray',
'data': { 'element-type': 'str' } }
##
# @SchemaInfoObject
#
# Additional SchemaInfo members for meta-type 'object'.
#
# @members: the object type's (non-variant) members.
#
# @tag: #optional the name of the member serving as type tag.
# An element of @members with this name must exist.
#
# @variants: #optional variant members, i.e. additional members that
# depend on the type tag's value. Present exactly when
# @tag is present.
#
# Values of this type are JSON object on the wire.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoObject',
'data': { 'members': [ 'SchemaInfoObjectMember' ],
'*tag': 'str',
'*variants': [ 'SchemaInfoObjectVariant' ] } }
##
# @SchemaInfoObjectMember
#
# An object member.
#
# @name: the member's name, as defined in the QAPI schema.
#
# @type: the name of the member's type.
#
# @default: #optional default when used as command parameter.
# If absent, the parameter is mandatory.
# If present, the value must be null. The parameter is
# optional, and behavior when it's missing is not specified
# here.
# Future extension: if present and non-null, the parameter
# is optional, and defaults to this value.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoObjectMember',
'data': { 'name': 'str', 'type': 'str', '*default': 'any' } }
# @default's type must be null or match @type
##
# @SchemaInfoObjectVariant
#
# The variant members for a value of the type tag.
#
# @case: a value of the type tag.
#
# @type: the name of the object type that provides the variant members
# when the type tag has value @case.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoObjectVariant',
'data': { 'case': 'str', 'type': 'str' } }
##
# @SchemaInfoAlternate
#
# Additional SchemaInfo members for meta-type 'alternate'.
#
# @members: the alternate type's members.
# The members' wire encoding is distinct, see
# docs/qapi-code-gen.txt section Alternate types.
#
# On the wire, this can be any of the members.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoAlternate',
'data': { 'members': [ 'SchemaInfoAlternateMember' ] } }
##
# @SchemaInfoAlternateMember
#
# An alternate member.
#
# @type: the name of the member's type.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoAlternateMember',
'data': { 'type': 'str' } }
##
# @SchemaInfoCommand
#
# Additional SchemaInfo members for meta-type 'command'.
#
# @arg-type: the name of the object type that provides the command's
# parameters.
#
# @ret-type: the name of the command's result type.
#
# TODO @success-response (currently irrelevant, because it's QGA, not QMP)
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoCommand',
'data': { 'arg-type': 'str', 'ret-type': 'str' } }
##
# @SchemaInfoEvent
#
# Additional SchemaInfo members for meta-type 'event'.
#
# @arg-type: the name of the object type that provides the event's
# parameters.
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoEvent',
'data': { 'arg-type': 'str' } }
+9
View File
@@ -151,6 +151,14 @@ static void qapi_dealloc_type_number(Visitor *v, double *obj, const char *name,
{
}
static void qapi_dealloc_type_anything(Visitor *v, QObject **obj,
const char *name, Error **errp)
{
if (obj) {
qobject_decref(*obj);
}
}
static void qapi_dealloc_type_size(Visitor *v, uint64_t *obj, const char *name,
Error **errp)
{
@@ -216,6 +224,7 @@ QapiDeallocVisitor *qapi_dealloc_visitor_new(void)
v->visitor.type_bool = qapi_dealloc_type_bool;
v->visitor.type_str = qapi_dealloc_type_str;
v->visitor.type_number = qapi_dealloc_type_number;
v->visitor.type_any = qapi_dealloc_type_anything;
v->visitor.type_size = qapi_dealloc_type_size;
v->visitor.start_union = qapi_dealloc_start_union;
+6
View File
@@ -260,6 +260,12 @@ void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp)
v->type_number(v, obj, name, errp);
}
void visit_type_any(Visitor *v, QObject **obj, const char *name,
Error **errp)
{
v->type_any(v, obj, name, errp);
}
void output_type_enum(Visitor *v, int *obj, const char * const strings[],
const char *kind, const char *name,
Error **errp)
+11
View File
@@ -286,6 +286,16 @@ static void qmp_input_type_number(Visitor *v, double *obj, const char *name,
}
}
static void qmp_input_type_any(Visitor *v, QObject **obj, const char *name,
Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
QObject *qobj = qmp_input_get_object(qiv, name, true);
qobject_incref(qobj);
*obj = qobj;
}
static void qmp_input_optional(Visitor *v, bool *present, const char *name,
Error **errp)
{
@@ -329,6 +339,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
v->visitor.type_bool = qmp_input_type_bool;
v->visitor.type_str = qmp_input_type_str;
v->visitor.type_number = qmp_input_type_number;
v->visitor.type_any = qmp_input_type_any;
v->visitor.optional = qmp_input_optional;
v->visitor.get_next_type = qmp_input_get_next_type;
+15 -2
View File
@@ -66,9 +66,13 @@ static QObject *qmp_output_first(QmpOutputVisitor *qov)
{
QStackEntry *e = QTAILQ_LAST(&qov->stack, QStack);
/* FIXME - find a better way to deal with NULL values */
/*
* FIXME Wrong, because qmp_output_get_qobject() will increment
* the refcnt *again*. We need to think through how visitors
* handle null.
*/
if (!e) {
return NULL;
return qnull();
}
return e->value;
@@ -186,6 +190,14 @@ static void qmp_output_type_number(Visitor *v, double *obj, const char *name,
qmp_output_add(qov, name, qfloat_from_double(*obj));
}
static void qmp_output_type_any(Visitor *v, QObject **obj, const char *name,
Error **errp)
{
QmpOutputVisitor *qov = to_qov(v);
qobject_incref(*obj);
qmp_output_add_obj(qov, name, *obj);
}
QObject *qmp_output_get_qobject(QmpOutputVisitor *qov)
{
QObject *obj = qmp_output_first(qov);
@@ -233,6 +245,7 @@ QmpOutputVisitor *qmp_output_visitor_new(void)
v->visitor.type_bool = qmp_output_type_bool;
v->visitor.type_str = qmp_output_type_str;
v->visitor.type_number = qmp_output_type_number;
v->visitor.type_any = qmp_output_type_any;
QTAILQ_INIT(&v->stack);
+144 -125
View File
File diff suppressed because it is too large Load Diff
+10 -17
View File
@@ -157,9 +157,9 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
* #ifdef CONFIG_SPICE. Necessary for an accurate query-commands
* result. However, the QAPI schema is blissfully unaware of that,
* and the QAPI code generator happily generates a dead
* qmp_marshal_input_query_spice() that calls qmp_query_spice().
* Provide it one, or else linking fails.
* FIXME Educate the QAPI schema on CONFIG_SPICE.
* qmp_marshal_query_spice() that calls qmp_query_spice(). Provide it
* one, or else linking fails. FIXME Educate the QAPI schema on
* CONFIG_SPICE.
*/
SpiceInfo *qmp_query_spice(Error **errp)
{
@@ -234,12 +234,9 @@ ObjectPropertyInfoList *qmp_qom_list(const char *path, Error **errp)
return props;
}
/* FIXME: teach qapi about how to pass through Visitors */
void qmp_qom_set(QDict *qdict, QObject **ret, Error **errp)
void qmp_qom_set(const char *path, const char *property, QObject *value,
Error **errp)
{
const char *path = qdict_get_str(qdict, "path");
const char *property = qdict_get_str(qdict, "property");
QObject *value = qdict_get(qdict, "value");
Object *obj;
obj = object_resolve_path(path, NULL);
@@ -252,20 +249,18 @@ void qmp_qom_set(QDict *qdict, QObject **ret, Error **errp)
object_property_set_qobject(obj, value, property, errp);
}
void qmp_qom_get(QDict *qdict, QObject **ret, Error **errp)
QObject *qmp_qom_get(const char *path, const char *property, Error **errp)
{
const char *path = qdict_get_str(qdict, "path");
const char *property = qdict_get_str(qdict, "property");
Object *obj;
obj = object_resolve_path(path, NULL);
if (!obj) {
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
"Device '%s' not found", path);
return;
return NULL;
}
*ret = object_property_get_qobject(obj, property, errp);
return object_property_get_qobject(obj, property, errp);
}
void qmp_set_password(const char *protocol, const char *password,
@@ -661,11 +656,9 @@ out:
object_unref(obj);
}
void qmp_object_add(QDict *qdict, QObject **ret, Error **errp)
void qmp_object_add(const char *type, const char *id,
bool has_props, QObject *props, Error **errp)
{
const char *type = qdict_get_str(qdict, "qom-type");
const char *id = qdict_get_str(qdict, "id");
QObject *props = qdict_get(qdict, "props");
const QDict *pdict = NULL;
QmpInputVisitor *qiv;
+156 -140
View File
@@ -12,22 +12,18 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
from ordereddict import OrderedDict
from qapi import *
import re
def generate_command_decl(name, args, ret_type):
arglist=""
for argname, argtype, optional in parse_args(args):
argtype = c_type(argtype, is_param=True)
if optional:
arglist += "bool has_%s, " % c_name(argname)
arglist += "%s %s, " % (argtype, c_name(argname))
def gen_command_decl(name, arg_type, ret_type):
return mcgen('''
%(ret_type)s qmp_%(name)s(%(args)sError **errp);
%(c_type)s qmp_%(c_name)s(%(params)s);
''',
ret_type=c_type(ret_type), name=c_name(name),
args=arglist)
c_type=(ret_type and ret_type.c_type()) or 'void',
c_name=c_name(name),
params=gen_params(arg_type, 'Error **errp'))
def gen_err_check(err):
if not err:
@@ -39,110 +35,124 @@ if (%(err)s) {
''',
err=err)
def gen_sync_call(name, args, ret_type):
ret = ""
arglist=""
retval=""
def gen_call(name, arg_type, ret_type):
ret = ''
argstr = ''
if arg_type:
for memb in arg_type.members:
if memb.optional:
argstr += 'has_%s, ' % c_name(memb.name)
argstr += '%s, ' % c_name(memb.name)
lhs = ''
if ret_type:
retval = "retval = "
for argname, argtype, optional in parse_args(args):
if optional:
arglist += "has_%s, " % c_name(argname)
arglist += "%s, " % (c_name(argname))
lhs = 'retval = '
push_indent()
ret = mcgen('''
%(retval)sqmp_%(name)s(%(args)s&local_err);
%(lhs)sqmp_%(c_name)s(%(args)s&local_err);
''',
name=c_name(name), args=arglist, retval=retval)
c_name=c_name(name), args=argstr, lhs=lhs)
if ret_type:
ret += gen_err_check('local_err')
ret += mcgen('''
qmp_marshal_output_%(c_name)s(retval, ret, &local_err);
''',
c_name=c_name(name))
c_name=ret_type.c_name())
pop_indent()
return ret
def gen_visitor_input_containers_decl(args):
ret = ""
def gen_marshal_vars(arg_type, ret_type):
ret = mcgen('''
Error *local_err = NULL;
''')
push_indent()
if len(args) > 0:
if ret_type:
ret += mcgen('''
%(c_type)s retval;
''',
c_type=ret_type.c_type())
if arg_type:
ret += mcgen('''
QmpInputVisitor *mi = qmp_input_visitor_new_strict(QOBJECT(args));
QapiDeallocVisitor *md;
Visitor *v;
''')
pop_indent()
return ret
for memb in arg_type.members:
if memb.optional:
ret += mcgen('''
bool has_%(c_name)s = false;
''',
c_name=c_name(memb.name))
ret += mcgen('''
%(c_type)s %(c_name)s = %(c_null)s;
''',
c_name=c_name(memb.name),
c_type=memb.type.c_type(),
c_null=memb.type.c_null())
ret += '\n'
else:
ret += mcgen('''
def gen_visitor_input_vars_decl(args):
ret = ""
push_indent()
for argname, argtype, optional in parse_args(args):
if optional:
ret += mcgen('''
bool has_%(argname)s = false;
''',
argname=c_name(argname))
if is_c_ptr(argtype):
ret += mcgen('''
%(argtype)s %(argname)s = NULL;
''',
argname=c_name(argname), argtype=c_type(argtype))
else:
ret += mcgen('''
%(argtype)s %(argname)s = {0};
''',
argname=c_name(argname), argtype=c_type(argtype))
(void)args;
''')
pop_indent()
return ret
def gen_visitor_input_block(args, dealloc=False):
ret = ""
errparg = '&local_err'
errarg = 'local_err'
if len(args) == 0:
def gen_marshal_input_visit(arg_type, dealloc=False):
ret = ''
if not arg_type:
return ret
push_indent()
if dealloc:
errparg = 'NULL'
errarg = None;
errarg = None
ret += mcgen('''
qmp_input_visitor_cleanup(mi);
md = qapi_dealloc_visitor_new();
v = qapi_dealloc_get_visitor(md);
''')
else:
errparg = '&local_err'
errarg = 'local_err'
ret += mcgen('''
v = qmp_input_get_visitor(mi);
''')
for argname, argtype, optional in parse_args(args):
if optional:
for memb in arg_type.members:
if memb.optional:
ret += mcgen('''
visit_optional(v, &has_%(c_name)s, "%(name)s", %(errp)s);
''',
c_name=c_name(argname), name=argname, errp=errparg)
c_name=c_name(memb.name), name=memb.name,
errp=errparg)
ret += gen_err_check(errarg)
ret += mcgen('''
if (has_%(c_name)s) {
''',
c_name=c_name(argname))
c_name=c_name(memb.name))
push_indent()
ret += mcgen('''
visit_type_%(visitor)s(v, &%(c_name)s, "%(name)s", %(errp)s);
visit_type_%(c_type)s(v, &%(c_name)s, "%(name)s", %(errp)s);
''',
c_name=c_name(argname), name=argname, argtype=argtype,
visitor=type_name(argtype), errp=errparg)
c_name=c_name(memb.name), name=memb.name,
c_type=memb.type.c_name(), errp=errparg)
ret += gen_err_check(errarg)
if optional:
if memb.optional:
pop_indent()
ret += mcgen('''
}
@@ -155,12 +165,11 @@ qapi_dealloc_visitor_cleanup(md);
pop_indent()
return ret
def gen_marshal_output(name, ret_type):
if not ret_type:
return ""
ret = mcgen('''
static void qmp_marshal_output_%(c_name)s(%(c_ret_type)s ret_in, QObject **ret_out, Error **errp)
def gen_marshal_output(ret_type):
return mcgen('''
static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out, Error **errp)
{
Error *local_err = NULL;
QmpOutputVisitor *mo = qmp_output_visitor_new();
@@ -168,7 +177,7 @@ static void qmp_marshal_output_%(c_name)s(%(c_ret_type)s ret_in, QObject **ret_o
Visitor *v;
v = qmp_output_get_visitor(mo);
visit_type_%(visitor)s(v, &ret_in, "unused", &local_err);
visit_type_%(c_name)s(v, &ret_in, "unused", &local_err);
if (local_err) {
goto out;
}
@@ -179,51 +188,40 @@ out:
qmp_output_visitor_cleanup(mo);
md = qapi_dealloc_visitor_new();
v = qapi_dealloc_get_visitor(md);
visit_type_%(visitor)s(v, &ret_in, "unused", NULL);
visit_type_%(c_name)s(v, &ret_in, "unused", NULL);
qapi_dealloc_visitor_cleanup(md);
}
''',
c_ret_type=c_type(ret_type), c_name=c_name(name),
visitor=type_name(ret_type))
c_type=ret_type.c_type(), c_name=ret_type.c_name())
return ret
def gen_marshal_input_decl(name, middle_mode):
ret = 'void qmp_marshal_input_%s(QDict *args, QObject **ret, Error **errp)' % c_name(name)
def gen_marshal_proto(name):
ret = 'void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)' % c_name(name)
if not middle_mode:
ret = "static " + ret
ret = 'static ' + ret
return ret
def gen_marshal_input(name, args, ret_type, middle_mode):
hdr = gen_marshal_input_decl(name, middle_mode)
def gen_marshal_decl(name):
return mcgen('''
%(proto)s;
''',
proto=gen_marshal_proto(name))
def gen_marshal(name, arg_type, ret_type):
ret = mcgen('''
%(header)s
%(proto)s
{
Error *local_err = NULL;
''',
header=hdr)
proto=gen_marshal_proto(name))
if ret_type:
ret += mcgen('''
%(c_type)s retval;
''',
c_type=c_type(ret_type))
ret += gen_marshal_vars(arg_type, ret_type)
ret += gen_marshal_input_visit(arg_type)
ret += gen_call(name, arg_type, ret_type)
if len(args) > 0:
ret += gen_visitor_input_containers_decl(args)
ret += gen_visitor_input_vars_decl(args) + '\n'
ret += gen_visitor_input_block(args) + '\n'
else:
ret += mcgen('''
(void)args;
''')
ret += gen_sync_call(name, args, ret_type)
if re.search('^ *goto out\\;', ret, re.MULTILINE):
if re.search('^ *goto out;', ret, re.MULTILINE):
ret += mcgen('''
out:
@@ -231,27 +229,31 @@ out:
ret += mcgen('''
error_propagate(errp, local_err);
''')
ret += gen_visitor_input_block(args, dealloc=True)
ret += gen_marshal_input_visit(arg_type, dealloc=True)
ret += mcgen('''
}
''')
return ret
def gen_registry(commands):
registry=""
push_indent()
for cmd in commands:
options = 'QCO_NO_OPTIONS'
if not cmd.get('success-response', True):
options = 'QCO_NO_SUCCESS_RESP'
registry += mcgen('''
qmp_register_command("%(name)s", qmp_marshal_input_%(c_name)s, %(opts)s);
''',
name=cmd['command'], c_name=c_name(cmd['command']),
opts=options)
pop_indent()
def gen_register_command(name, success_response):
push_indent()
options = 'QCO_NO_OPTIONS'
if not success_response:
options = 'QCO_NO_SUCCESS_RESP'
ret = mcgen('''
qmp_register_command("%(name)s", qmp_marshal_%(c_name)s, %(opts)s);
''',
name=name, c_name=c_name(name),
opts=options)
pop_indent()
return ret
def gen_registry(registry):
ret = mcgen('''
static void qmp_init_marshal(void)
{
''')
@@ -263,6 +265,41 @@ qapi_init(qmp_init_marshal);
''')
return ret
class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
def __init__(self):
self.decl = None
self.defn = None
self._regy = None
self._visited_ret_types = None
def visit_begin(self, schema):
self.decl = ''
self.defn = ''
self._regy = ''
self._visited_ret_types = set()
def visit_end(self):
if not middle_mode:
self.defn += gen_registry(self._regy)
self._regy = None
self._visited_ret_types = None
def visit_command(self, name, info, arg_type, ret_type,
gen, success_response):
if not gen:
return
self.decl += gen_command_decl(name, arg_type, ret_type)
if ret_type and ret_type not in self._visited_ret_types:
self._visited_ret_types.add(ret_type)
self.defn += gen_marshal_output(ret_type)
if middle_mode:
self.decl += gen_marshal_decl(name)
self.defn += gen_marshal(name, arg_type, ret_type)
if not middle_mode:
self._regy += gen_register_command(name, success_response)
middle_mode = False
(input_file, output_dir, do_c, do_h, prefix, opts) = \
@@ -272,10 +309,6 @@ for o, a in opts:
if o in ("-m", "--middle"):
middle_mode = True
exprs = parse_schema(input_file)
commands = filter(lambda expr: expr.has_key('command'), exprs)
commands = filter(lambda expr: not expr.has_key('gen'), commands)
c_comment = '''
/*
* schema-defined QMP->QAPI command dispatch
@@ -323,7 +356,7 @@ fdef.write(mcgen('''
#include "%(prefix)sqmp-commands.h"
''',
prefix=prefix))
prefix=prefix))
fdecl.write(mcgen('''
#include "%(prefix)sqapi-types.h"
@@ -331,29 +364,12 @@ fdecl.write(mcgen('''
#include "qapi/error.h"
''',
prefix=prefix))
prefix=prefix))
for cmd in commands:
arglist = []
ret_type = None
if cmd.has_key('data'):
arglist = cmd['data']
if cmd.has_key('returns'):
ret_type = cmd['returns']
ret = generate_command_decl(cmd['command'], arglist, ret_type)
fdecl.write(ret)
if ret_type:
ret = gen_marshal_output(cmd['command'], ret_type) + "\n"
fdef.write(ret)
if middle_mode:
fdecl.write('%s;\n' % gen_marshal_input_decl(cmd['command'], middle_mode))
ret = gen_marshal_input(cmd['command'], arglist, ret_type, middle_mode) + "\n"
fdef.write(ret)
if not middle_mode:
ret = gen_registry(commands)
fdef.write(ret)
schema = QAPISchema(input_file)
gen = QAPISchemaGenCommandVisitor()
schema.visit(gen)
fdef.write(gen.defn)
fdecl.write(gen.decl)
close_output(fdef, fdecl)
+85 -158
View File
@@ -2,78 +2,64 @@
# QAPI event generator
#
# Copyright (c) 2014 Wenchao Xia
# Copyright (c) 2015 Red Hat Inc.
#
# Authors:
# Wenchao Xia <wenchaoqemu@gmail.com>
# Markus Armbruster <armbru@redhat.com>
#
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
from ordereddict import OrderedDict
from qapi import *
def _generate_event_api_name(event_name, params):
api_name = "void qapi_event_send_%s(" % c_name(event_name).lower();
l = len(api_name)
if params:
for argname, argentry, optional in parse_args(params):
if optional:
api_name += "bool has_%s,\n" % c_name(argname)
api_name += "".ljust(l)
api_name += "%s %s,\n" % (c_type(argentry, is_param=True),
c_name(argname))
api_name += "".ljust(l)
api_name += "Error **errp)"
return api_name;
def gen_event_send_proto(name, arg_type):
return 'void qapi_event_send_%(c_name)s(%(param)s)' % {
'c_name': c_name(name.lower()),
'param': gen_params(arg_type, 'Error **errp')}
# Following are the core functions that generate C APIs to emit event.
def generate_event_declaration(api_name):
def gen_event_send_decl(name, arg_type):
return mcgen('''
%(api_name)s;
%(proto)s;
''',
api_name = api_name)
proto=gen_event_send_proto(name, arg_type))
def generate_event_implement(api_name, event_name, params):
# step 1: declare any variables
ret = mcgen("""
%(api_name)s
def gen_event_send(name, arg_type):
ret = mcgen('''
%(proto)s
{
QDict *qmp;
Error *local_err = NULL;
QMPEventFuncEmit emit;
""",
api_name = api_name)
''',
proto=gen_event_send_proto(name, arg_type))
if params:
ret += mcgen("""
if arg_type and arg_type.members:
ret += mcgen('''
QmpOutputVisitor *qov;
Visitor *v;
QObject *obj;
""")
''')
# step 2: check emit function, create a dict
ret += mcgen("""
ret += mcgen('''
emit = qmp_event_get_func_emit();
if (!emit) {
return;
}
qmp = qmp_event_build_dict("%(event_name)s");
qmp = qmp_event_build_dict("%(name)s");
""",
event_name = event_name)
''',
name=name)
# step 3: visit the params if params != None
if params:
ret += mcgen("""
if arg_type and arg_type.members:
ret += mcgen('''
qov = qmp_output_visitor_new();
g_assert(qov);
@@ -81,45 +67,46 @@ def generate_event_implement(api_name, event_name, params):
g_assert(v);
/* Fake visit, as if all members are under a structure */
visit_start_struct(v, NULL, "", "%(event_name)s", 0, &local_err);
visit_start_struct(v, NULL, "", "%(name)s", 0, &local_err);
if (local_err) {
goto clean;
}
""",
event_name = event_name)
''',
name=name)
for argname, argentry, optional in parse_args(params):
if optional:
ret += mcgen("""
if (has_%(var)s) {
""",
var = c_name(argname))
for memb in arg_type.members:
if memb.optional:
ret += mcgen('''
if (has_%(c_name)s) {
''',
c_name=c_name(memb.name))
push_indent()
if argentry == "str":
var_type = "(char **)"
# Ugly: need to cast away the const
if memb.type.name == "str":
cast = '(char **)'
else:
var_type = ""
cast = ''
ret += mcgen("""
visit_type_%(type)s(v, %(var_type)s&%(var)s, "%(name)s", &local_err);
ret += mcgen('''
visit_type_%(c_type)s(v, %(cast)s&%(c_name)s, "%(name)s", &local_err);
if (local_err) {
goto clean;
}
""",
var_type = var_type,
var = c_name(argname),
type = type_name(argentry),
name = argname)
''',
cast=cast,
c_name=c_name(memb.name),
c_type=memb.type.c_name(),
name=memb.name)
if optional:
if memb.optional:
pop_indent()
ret += mcgen("""
ret += mcgen('''
}
""")
''')
ret += mcgen("""
ret += mcgen('''
visit_end_struct(v, &local_err);
if (local_err) {
@@ -130,86 +117,49 @@ def generate_event_implement(api_name, event_name, params):
g_assert(obj != NULL);
qdict_put_obj(qmp, "data", obj);
""")
''')
# step 4: call qmp event api
ret += mcgen("""
emit(%(event_enum_value)s, qmp, &local_err);
ret += mcgen('''
emit(%(c_enum)s, qmp, &local_err);
""",
event_enum_value = event_enum_value)
''',
c_enum=c_enum_const(event_enum_name, name))
# step 5: clean up
if params:
ret += mcgen("""
if arg_type and arg_type.members:
ret += mcgen('''
clean:
qmp_output_visitor_cleanup(qov);
""")
ret += mcgen("""
''')
ret += mcgen('''
error_propagate(errp, local_err);
QDECREF(qmp);
}
""")
return ret
# Following are the functions that generate an enum type for all defined
# events, similar to qapi-types.py. Here we already have enum name and
# values which were generated before and recorded in event_enum_*. It also
# works around the issue that "import qapi-types" can't work.
def generate_event_enum_decl(event_enum_name, event_enum_values):
lookup_decl = mcgen('''
extern const char *%(event_enum_name)s_lookup[];
''',
event_enum_name = event_enum_name)
enum_decl = mcgen('''
typedef enum %(event_enum_name)s {
''',
event_enum_name = event_enum_name)
# append automatically generated _MAX value
enum_max_value = c_enum_const(event_enum_name, "MAX")
enum_values = event_enum_values + [ enum_max_value ]
i = 0
for value in enum_values:
enum_decl += mcgen('''
%(value)s = %(i)d,
''',
value = value,
i = i)
i += 1
enum_decl += mcgen('''
} %(event_enum_name)s;
''',
event_enum_name = event_enum_name)
return lookup_decl + enum_decl
def generate_event_enum_lookup(event_enum_name, event_enum_strings):
ret = mcgen('''
const char *%(event_enum_name)s_lookup[] = {
''',
event_enum_name = event_enum_name)
for string in event_enum_strings:
ret += mcgen('''
"%(string)s",
''',
string = string)
ret += mcgen('''
NULL,
};
''')
return ret
class QAPISchemaGenEventVisitor(QAPISchemaVisitor):
def __init__(self):
self.decl = None
self.defn = None
self._event_names = None
def visit_begin(self, schema):
self.decl = ''
self.defn = ''
self._event_names = []
def visit_end(self):
self.decl += gen_enum(event_enum_name, self._event_names)
self.defn += gen_enum_lookup(event_enum_name, self._event_names)
self._event_names = None
def visit_event(self, name, info, arg_type):
self.decl += gen_event_send_decl(name, arg_type)
self.defn += gen_event_send(name, arg_type)
self._event_names.append(name)
(input_file, output_dir, do_c, do_h, prefix, dummy) = parse_command_line()
c_comment = '''
@@ -263,35 +213,12 @@ fdecl.write(mcgen('''
''',
prefix=prefix))
exprs = parse_schema(input_file)
event_enum_name = c_name(prefix + "QAPIEvent", protect=False)
event_enum_values = []
event_enum_strings = []
for expr in exprs:
if expr.has_key('event'):
event_name = expr['event']
params = expr.get('data')
if params and len(params) == 0:
params = None
api_name = _generate_event_api_name(event_name, params)
ret = generate_event_declaration(api_name)
fdecl.write(ret)
# We need an enum value per event
event_enum_value = c_enum_const(event_enum_name, event_name)
ret = generate_event_implement(api_name, event_name, params)
fdef.write(ret)
# Record it, and generate enum later
event_enum_values.append(event_enum_value)
event_enum_strings.append(event_name)
ret = generate_event_enum_decl(event_enum_name, event_enum_values)
fdecl.write(ret)
ret = generate_event_enum_lookup(event_enum_name, event_enum_strings)
fdef.write(ret)
schema = QAPISchema(input_file)
gen = QAPISchemaGenEventVisitor()
schema.visit(gen)
fdef.write(gen.defn)
fdecl.write(gen.decl)
close_output(fdef, fdecl)
+213
View File
@@ -0,0 +1,213 @@
#
# QAPI introspection generator
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Authors:
# Markus Armbruster <armbru@redhat.com>
#
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
from qapi import *
# Caveman's json.dumps() replacement (we're stuck at Python 2.4)
# TODO try to use json.dumps() once we get unstuck
def to_json(obj, level=0):
if obj is None:
ret = 'null'
elif isinstance(obj, str):
ret = '"' + obj.replace('"', r'\"') + '"'
elif isinstance(obj, list):
elts = [to_json(elt, level + 1)
for elt in obj]
ret = '[' + ', '.join(elts) + ']'
elif isinstance(obj, dict):
elts = ['"%s": %s' % (key.replace('"', r'\"'),
to_json(obj[key], level + 1))
for key in sorted(obj.keys())]
ret = '{' + ', '.join(elts) + '}'
else:
assert False # not implemented
if level == 1:
ret = '\n' + ret
return ret
def to_c_string(string):
return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'
class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
def __init__(self, unmask):
self._unmask = unmask
self.defn = None
self.decl = None
self._schema = None
self._jsons = None
self._used_types = None
self._name_map = None
def visit_begin(self, schema):
self._schema = schema
self._jsons = []
self._used_types = []
self._name_map = {}
return QAPISchemaType # don't visit types for now
def visit_end(self):
# visit the types that are actually used
jsons = self._jsons
self._jsons = []
for typ in self._used_types:
typ.visit(self)
# generate C
# TODO can generate awfully long lines
jsons.extend(self._jsons)
name = prefix + 'qmp_schema_json'
self.decl = mcgen('''
extern const char %(c_name)s[];
''',
c_name=c_name(name))
lines = to_json(jsons).split('\n')
c_string = '\n '.join([to_c_string(line) for line in lines])
self.defn = mcgen('''
const char %(c_name)s[] = %(c_string)s;
''',
c_name=c_name(name),
c_string=c_string)
self._schema = None
self._jsons = None
self._used_types = None
self._name_map = None
def _name(self, name):
if self._unmask:
return name
if name not in self._name_map:
self._name_map[name] = '%d' % len(self._name_map)
return self._name_map[name]
def _use_type(self, typ):
# Map the various integer types to plain int
if typ.json_type() == 'int':
typ = self._schema.lookup_type('int')
elif (isinstance(typ, QAPISchemaArrayType) and
typ.element_type.json_type() == 'int'):
typ = self._schema.lookup_type('intList')
# Add type to work queue if new
if typ not in self._used_types:
self._used_types.append(typ)
# Clients should examine commands and events, not types. Hide
# type names to reduce the temptation. Also saves a few
# characters.
if isinstance(typ, QAPISchemaBuiltinType):
return typ.name
return self._name(typ.name)
def _gen_json(self, name, mtype, obj):
if mtype != 'command' and mtype != 'event' and mtype != 'builtin':
name = self._name(name)
obj['name'] = name
obj['meta-type'] = mtype
self._jsons.append(obj)
def _gen_member(self, member):
ret = {'name': member.name, 'type': self._use_type(member.type)}
if member.optional:
ret['default'] = None
return ret
def _gen_variants(self, tag_name, variants):
return {'tag': tag_name,
'variants': [self._gen_variant(v) for v in variants]}
def _gen_variant(self, variant):
return {'case': variant.name, 'type': self._use_type(variant.type)}
def visit_builtin_type(self, name, info, json_type):
self._gen_json(name, 'builtin', {'json-type': json_type})
def visit_enum_type(self, name, info, values, prefix):
self._gen_json(name, 'enum', {'values': values})
def visit_array_type(self, name, info, element_type):
self._gen_json(name, 'array',
{'element-type': self._use_type(element_type)})
def visit_object_type_flat(self, name, info, members, variants):
obj = {'members': [self._gen_member(m) for m in members]}
if variants:
obj.update(self._gen_variants(variants.tag_member.name,
variants.variants))
self._gen_json(name, 'object', obj)
def visit_alternate_type(self, name, info, variants):
self._gen_json(name, 'alternate',
{'members': [{'type': self._use_type(m.type)}
for m in variants.variants]})
def visit_command(self, name, info, arg_type, ret_type,
gen, success_response):
arg_type = arg_type or self._schema.the_empty_object_type
ret_type = ret_type or self._schema.the_empty_object_type
self._gen_json(name, 'command',
{'arg-type': self._use_type(arg_type),
'ret-type': self._use_type(ret_type)})
def visit_event(self, name, info, arg_type):
arg_type = arg_type or self._schema.the_empty_object_type
self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
# Debugging aid: unmask QAPI schema's type names
# We normally mask them, because they're not QMP wire ABI
opt_unmask = False
(input_file, output_dir, do_c, do_h, prefix, opts) = \
parse_command_line("u", ["unmask-non-abi-names"])
for o, a in opts:
if o in ("-u", "--unmask-non-abi-names"):
opt_unmask = True
c_comment = '''
/*
* QAPI/QMP schema introspection
*
* Copyright (C) 2015 Red Hat, Inc.
*
* 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.
*
*/
'''
h_comment = '''
/*
* QAPI/QMP schema introspection
*
* Copyright (C) 2015 Red Hat, Inc.
*
* 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.
*
*/
'''
(fdef, fdecl) = open_output(output_dir, do_c, do_h, prefix,
'qmp-introspect.c', 'qmp-introspect.h',
c_comment, h_comment)
fdef.write(mcgen('''
#include "%(prefix)sqmp-introspect.h"
''',
prefix=prefix))
schema = QAPISchema(input_file)
gen = QAPISchemaGenIntrospectVisitor(opt_unmask)
schema.visit(gen)
fdef.write(gen.defn)
fdecl.write(gen.decl)
close_output(fdef, fdecl)

Some files were not shown because too many files have changed in this diff Show More