diff --git a/.gitignore b/.gitignore index 367bc706d8..fdd721dba1 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index 291fb54d35..8ec9b69b2f 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/Makefile.objs b/Makefile.objs index 3df2efca1c..ce87778828 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -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 diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index d960dc0234..b1c8361d22 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -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 diff --git a/docs/writing-qmp-commands.txt b/docs/writing-qmp-commands.txt index f7693ca267..8647cac4e7 100644 --- a/docs/writing-qmp-commands.txt +++ b/docs/writing-qmp-commands.txt @@ -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 diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h index 9aff47e699..bc6cb6d185 100644 --- a/include/monitor/monitor.h +++ b/include/monitor/monitor.h @@ -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); diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h index f4a2f746c8..8c0ba57292 100644 --- a/include/qapi/visitor-impl.h +++ b/include/qapi/visitor-impl.h @@ -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, diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h index 00ba104cd4..cfc19a616e 100644 --- a/include/qapi/visitor.h +++ b/include/qapi/visitor.h @@ -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); diff --git a/monitor.c b/monitor.c index 1f432635ad..d0edb3bb15 100644 --- a/monitor.c +++ b/monitor.c @@ -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); } diff --git a/qapi-schema.json b/qapi-schema.json index 2bada607f1..821362d637 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -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: diff --git a/qapi/introspect.json b/qapi/introspect.json new file mode 100644 index 0000000000..cc50dc6bcb --- /dev/null +++ b/qapi/introspect.json @@ -0,0 +1,273 @@ +# -*- Mode: Python -*- +# +# QAPI/QMP introspection +# +# Copyright (C) 2015 Red Hat, Inc. +# +# Authors: +# Markus Armbruster +# +# 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' } } diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c index d7f92c5d68..737deab9e5 100644 --- a/qapi/qapi-dealloc-visitor.c +++ b/qapi/qapi-dealloc-visitor.c @@ -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; diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c index 5a7c900504..59ed5067fa 100644 --- a/qapi/qapi-visit-core.c +++ b/qapi/qapi-visit-core.c @@ -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) diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c index e97b8a4282..5dd9ed5ce5 100644 --- a/qapi/qmp-input-visitor.c +++ b/qapi/qmp-input-visitor.c @@ -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; diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c index efc19d5841..29899acd46 100644 --- a/qapi/qmp-output-visitor.c +++ b/qapi/qmp-output-visitor.c @@ -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); diff --git a/qmp-commands.hx b/qmp-commands.hx index 9848fd88f0..66f03007ec 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -63,7 +63,7 @@ EQMP { .name = "quit", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_quit, + .mhandler.cmd_new = qmp_marshal_quit, }, SQMP @@ -84,7 +84,7 @@ EQMP { .name = "eject", .args_type = "force:-f,device:B", - .mhandler.cmd_new = qmp_marshal_input_eject, + .mhandler.cmd_new = qmp_marshal_eject, }, SQMP @@ -110,7 +110,7 @@ EQMP { .name = "change", .args_type = "device:B,target:F,arg:s?", - .mhandler.cmd_new = qmp_marshal_input_change, + .mhandler.cmd_new = qmp_marshal_change, }, SQMP @@ -146,7 +146,7 @@ EQMP { .name = "screendump", .args_type = "filename:F", - .mhandler.cmd_new = qmp_marshal_input_screendump, + .mhandler.cmd_new = qmp_marshal_screendump, }, SQMP @@ -169,7 +169,7 @@ EQMP { .name = "stop", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_stop, + .mhandler.cmd_new = qmp_marshal_stop, }, SQMP @@ -190,7 +190,7 @@ EQMP { .name = "cont", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_cont, + .mhandler.cmd_new = qmp_marshal_cont, }, SQMP @@ -211,7 +211,7 @@ EQMP { .name = "system_wakeup", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_system_wakeup, + .mhandler.cmd_new = qmp_marshal_system_wakeup, }, SQMP @@ -232,7 +232,7 @@ EQMP { .name = "system_reset", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_system_reset, + .mhandler.cmd_new = qmp_marshal_system_reset, }, SQMP @@ -253,7 +253,7 @@ EQMP { .name = "system_powerdown", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_system_powerdown, + .mhandler.cmd_new = qmp_marshal_system_powerdown, }, SQMP @@ -310,7 +310,7 @@ EQMP { .name = "device_del", .args_type = "id:s", - .mhandler.cmd_new = qmp_marshal_input_device_del, + .mhandler.cmd_new = qmp_marshal_device_del, }, SQMP @@ -333,7 +333,7 @@ EQMP { .name = "send-key", .args_type = "keys:q,hold-time:i?", - .mhandler.cmd_new = qmp_marshal_input_send_key, + .mhandler.cmd_new = qmp_marshal_send_key, }, SQMP @@ -364,7 +364,7 @@ EQMP { .name = "cpu", .args_type = "index:i", - .mhandler.cmd_new = qmp_marshal_input_cpu, + .mhandler.cmd_new = qmp_marshal_cpu, }, SQMP @@ -389,7 +389,7 @@ EQMP { .name = "cpu-add", .args_type = "id:i", - .mhandler.cmd_new = qmp_marshal_input_cpu_add, + .mhandler.cmd_new = qmp_marshal_cpu_add, }, SQMP @@ -412,7 +412,7 @@ EQMP { .name = "memsave", .args_type = "val:l,size:i,filename:s,cpu:i?", - .mhandler.cmd_new = qmp_marshal_input_memsave, + .mhandler.cmd_new = qmp_marshal_memsave, }, SQMP @@ -441,7 +441,7 @@ EQMP { .name = "pmemsave", .args_type = "val:l,size:i,filename:s", - .mhandler.cmd_new = qmp_marshal_input_pmemsave, + .mhandler.cmd_new = qmp_marshal_pmemsave, }, SQMP @@ -469,7 +469,7 @@ EQMP { .name = "inject-nmi", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_inject_nmi, + .mhandler.cmd_new = qmp_marshal_inject_nmi, }, SQMP @@ -492,7 +492,7 @@ EQMP { .name = "ringbuf-write", .args_type = "device:s,data:s,format:s?", - .mhandler.cmd_new = qmp_marshal_input_ringbuf_write, + .mhandler.cmd_new = qmp_marshal_ringbuf_write, }, SQMP @@ -523,7 +523,7 @@ EQMP { .name = "ringbuf-read", .args_type = "device:s,size:i,format:s?", - .mhandler.cmd_new = qmp_marshal_input_ringbuf_read, + .mhandler.cmd_new = qmp_marshal_ringbuf_read, }, SQMP @@ -559,7 +559,7 @@ EQMP { .name = "xen-save-devices-state", .args_type = "filename:F", - .mhandler.cmd_new = qmp_marshal_input_xen_save_devices_state, + .mhandler.cmd_new = qmp_marshal_xen_save_devices_state, }, SQMP @@ -586,7 +586,7 @@ EQMP { .name = "xen-set-global-dirty-log", .args_type = "enable:b", - .mhandler.cmd_new = qmp_marshal_input_xen_set_global_dirty_log, + .mhandler.cmd_new = qmp_marshal_xen_set_global_dirty_log, }, SQMP @@ -610,7 +610,7 @@ EQMP { .name = "migrate", .args_type = "detach:-d,blk:-b,inc:-i,uri:s", - .mhandler.cmd_new = qmp_marshal_input_migrate, + .mhandler.cmd_new = qmp_marshal_migrate, }, SQMP @@ -643,7 +643,7 @@ EQMP { .name = "migrate_cancel", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_migrate_cancel, + .mhandler.cmd_new = qmp_marshal_migrate_cancel, }, SQMP @@ -664,7 +664,7 @@ EQMP { .name = "migrate-incoming", .args_type = "uri:s", - .mhandler.cmd_new = qmp_marshal_input_migrate_incoming, + .mhandler.cmd_new = qmp_marshal_migrate_incoming, }, SQMP @@ -692,7 +692,7 @@ EQMP { .name = "migrate-set-cache-size", .args_type = "value:o", - .mhandler.cmd_new = qmp_marshal_input_migrate_set_cache_size, + .mhandler.cmd_new = qmp_marshal_migrate_set_cache_size, }, SQMP @@ -715,7 +715,7 @@ EQMP { .name = "query-migrate-cache-size", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_migrate_cache_size, + .mhandler.cmd_new = qmp_marshal_query_migrate_cache_size, }, SQMP @@ -737,7 +737,7 @@ EQMP { .name = "migrate_set_speed", .args_type = "value:o", - .mhandler.cmd_new = qmp_marshal_input_migrate_set_speed, + .mhandler.cmd_new = qmp_marshal_migrate_set_speed, }, SQMP @@ -760,7 +760,7 @@ EQMP { .name = "migrate_set_downtime", .args_type = "value:T", - .mhandler.cmd_new = qmp_marshal_input_migrate_set_downtime, + .mhandler.cmd_new = qmp_marshal_migrate_set_downtime, }, SQMP @@ -785,7 +785,7 @@ EQMP .args_type = "protocol:s,hostname:s,port:i?,tls-port:i?,cert-subject:s?", .params = "protocol hostname port tls-port cert-subject", .help = "set migration information for remote display", - .mhandler.cmd_new = qmp_marshal_input_client_migrate_info, + .mhandler.cmd_new = qmp_marshal_client_migrate_info, }, SQMP @@ -819,7 +819,7 @@ EQMP .args_type = "paging:b,protocol:s,begin:i?,end:i?,format:s?", .params = "-p protocol [begin] [length] [format]", .help = "dump guest memory to file", - .mhandler.cmd_new = qmp_marshal_input_dump_guest_memory, + .mhandler.cmd_new = qmp_marshal_dump_guest_memory, }, SQMP @@ -855,7 +855,7 @@ EQMP { .name = "query-dump-guest-memory-capability", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_dump_guest_memory_capability, + .mhandler.cmd_new = qmp_marshal_query_dump_guest_memory_capability, }, SQMP @@ -876,7 +876,7 @@ EQMP { .name = "dump-skeys", .args_type = "filename:F", - .mhandler.cmd_new = qmp_marshal_input_dump_skeys, + .mhandler.cmd_new = qmp_marshal_dump_skeys, }, #endif @@ -917,7 +917,9 @@ Arguments: Example: --> { "execute": "netdev_add", "arguments": { "type": "user", "id": "netdev1" } } +-> { "execute": "netdev_add", + "arguments": { "type": "user", "id": "netdev1", + "dnssearch": "example.org" } } <- { "return": {} } Note: The supported device options are the same ones supported by the '-netdev' @@ -929,7 +931,7 @@ EQMP { .name = "netdev_del", .args_type = "id:s", - .mhandler.cmd_new = qmp_marshal_input_netdev_del, + .mhandler.cmd_new = qmp_marshal_netdev_del, }, SQMP @@ -953,7 +955,7 @@ EQMP { .name = "object-add", .args_type = "qom-type:s,id:s,props:q?", - .mhandler.cmd_new = qmp_object_add, + .mhandler.cmd_new = qmp_marshal_object_add, }, SQMP @@ -979,7 +981,7 @@ EQMP { .name = "object-del", .args_type = "id:s", - .mhandler.cmd_new = qmp_marshal_input_object_del, + .mhandler.cmd_new = qmp_marshal_object_del, }, SQMP @@ -1004,7 +1006,7 @@ EQMP { .name = "block_resize", .args_type = "device:s?,node-name:s?,size:o", - .mhandler.cmd_new = qmp_marshal_input_block_resize, + .mhandler.cmd_new = qmp_marshal_block_resize, }, SQMP @@ -1029,7 +1031,7 @@ EQMP { .name = "block-stream", .args_type = "device:B,base:s?,speed:o?,backing-file:s?,on-error:s?", - .mhandler.cmd_new = qmp_marshal_input_block_stream, + .mhandler.cmd_new = qmp_marshal_block_stream, }, SQMP @@ -1072,7 +1074,7 @@ EQMP { .name = "block-commit", .args_type = "device:B,base:s?,top:s?,backing-file:s?,speed:o?", - .mhandler.cmd_new = qmp_marshal_input_block_commit, + .mhandler.cmd_new = qmp_marshal_block_commit, }, SQMP @@ -1136,7 +1138,7 @@ EQMP .name = "drive-backup", .args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?," "bitmap:s?,on-source-error:s?,on-target-error:s?", - .mhandler.cmd_new = qmp_marshal_input_drive_backup, + .mhandler.cmd_new = qmp_marshal_drive_backup, }, SQMP @@ -1190,7 +1192,7 @@ EQMP .name = "blockdev-backup", .args_type = "sync:s,device:B,target:B,speed:i?," "on-source-error:s?,on-target-error:s?", - .mhandler.cmd_new = qmp_marshal_input_blockdev_backup, + .mhandler.cmd_new = qmp_marshal_blockdev_backup, }, SQMP @@ -1230,33 +1232,33 @@ EQMP { .name = "block-job-set-speed", .args_type = "device:B,speed:o", - .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed, + .mhandler.cmd_new = qmp_marshal_block_job_set_speed, }, { .name = "block-job-cancel", .args_type = "device:B,force:b?", - .mhandler.cmd_new = qmp_marshal_input_block_job_cancel, + .mhandler.cmd_new = qmp_marshal_block_job_cancel, }, { .name = "block-job-pause", .args_type = "device:B", - .mhandler.cmd_new = qmp_marshal_input_block_job_pause, + .mhandler.cmd_new = qmp_marshal_block_job_pause, }, { .name = "block-job-resume", .args_type = "device:B", - .mhandler.cmd_new = qmp_marshal_input_block_job_resume, + .mhandler.cmd_new = qmp_marshal_block_job_resume, }, { .name = "block-job-complete", .args_type = "device:B", - .mhandler.cmd_new = qmp_marshal_input_block_job_complete, + .mhandler.cmd_new = qmp_marshal_block_job_complete, }, { .name = "transaction", .args_type = "actions:q", - .mhandler.cmd_new = qmp_marshal_input_transaction, + .mhandler.cmd_new = qmp_marshal_transaction, }, SQMP @@ -1335,7 +1337,7 @@ EQMP { .name = "block-dirty-bitmap-add", .args_type = "node:B,name:s,granularity:i?", - .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_add, + .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_add, }, SQMP @@ -1363,7 +1365,7 @@ EQMP { .name = "block-dirty-bitmap-remove", .args_type = "node:B,name:s", - .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_remove, + .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_remove, }, SQMP @@ -1391,7 +1393,7 @@ EQMP { .name = "block-dirty-bitmap-clear", .args_type = "node:B,name:s", - .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_clear, + .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_clear, }, SQMP @@ -1420,7 +1422,7 @@ EQMP { .name = "blockdev-snapshot-sync", .args_type = "device:s?,node-name:s?,snapshot-file:s,snapshot-node-name:s?,format:s?,mode:s?", - .mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync, + .mhandler.cmd_new = qmp_marshal_blockdev_snapshot_sync, }, SQMP @@ -1456,7 +1458,7 @@ EQMP { .name = "blockdev-snapshot-internal-sync", .args_type = "device:B,name:s", - .mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_internal_sync, + .mhandler.cmd_new = qmp_marshal_blockdev_snapshot_internal_sync, }, SQMP @@ -1486,7 +1488,7 @@ EQMP .name = "blockdev-snapshot-delete-internal-sync", .args_type = "device:B,id:s?,name:s?", .mhandler.cmd_new = - qmp_marshal_input_blockdev_snapshot_delete_internal_sync, + qmp_marshal_blockdev_snapshot_delete_internal_sync, }, SQMP @@ -1530,7 +1532,7 @@ EQMP "on-source-error:s?,on-target-error:s?," "unmap:b?," "granularity:i?,buf-size:i?", - .mhandler.cmd_new = qmp_marshal_input_drive_mirror, + .mhandler.cmd_new = qmp_marshal_drive_mirror, }, SQMP @@ -1590,7 +1592,7 @@ EQMP { .name = "change-backing-file", .args_type = "device:s,image-node-name:s,backing-file:s", - .mhandler.cmd_new = qmp_marshal_input_change_backing_file, + .mhandler.cmd_new = qmp_marshal_change_backing_file, }, SQMP @@ -1629,7 +1631,7 @@ EQMP { .name = "balloon", .args_type = "value:M", - .mhandler.cmd_new = qmp_marshal_input_balloon, + .mhandler.cmd_new = qmp_marshal_balloon, }, SQMP @@ -1652,7 +1654,7 @@ EQMP { .name = "set_link", .args_type = "name:s,up:b", - .mhandler.cmd_new = qmp_marshal_input_set_link, + .mhandler.cmd_new = qmp_marshal_set_link, }, SQMP @@ -1678,7 +1680,7 @@ EQMP .args_type = "fdname:s", .params = "getfd name", .help = "receive a file descriptor via SCM rights and assign it a name", - .mhandler.cmd_new = qmp_marshal_input_getfd, + .mhandler.cmd_new = qmp_marshal_getfd, }, SQMP @@ -1711,7 +1713,7 @@ EQMP .args_type = "fdname:s", .params = "closefd name", .help = "close a file descriptor previously passed via SCM rights", - .mhandler.cmd_new = qmp_marshal_input_closefd, + .mhandler.cmd_new = qmp_marshal_closefd, }, SQMP @@ -1736,7 +1738,7 @@ EQMP .args_type = "fdset-id:i?,opaque:s?", .params = "add-fd fdset-id opaque", .help = "Add a file descriptor, that was passed via SCM rights, to an fd set", - .mhandler.cmd_new = qmp_marshal_input_add_fd, + .mhandler.cmd_new = qmp_marshal_add_fd, }, SQMP @@ -1775,7 +1777,7 @@ EQMP .args_type = "fdset-id:i,fd:i?", .params = "remove-fd fdset-id fd", .help = "Remove a file descriptor from an fd set", - .mhandler.cmd_new = qmp_marshal_input_remove_fd, + .mhandler.cmd_new = qmp_marshal_remove_fd, }, SQMP @@ -1807,7 +1809,7 @@ EQMP .name = "query-fdsets", .args_type = "", .help = "Return information describing all fd sets", - .mhandler.cmd_new = qmp_marshal_input_query_fdsets, + .mhandler.cmd_new = qmp_marshal_query_fdsets, }, SQMP @@ -1856,7 +1858,7 @@ EQMP { .name = "block_passwd", .args_type = "device:s?,node-name:s?,password:s", - .mhandler.cmd_new = qmp_marshal_input_block_passwd, + .mhandler.cmd_new = qmp_marshal_block_passwd, }, SQMP @@ -1882,7 +1884,7 @@ EQMP { .name = "block_set_io_throttle", .args_type = "device:B,bps:l,bps_rd:l,bps_wr:l,iops:l,iops_rd:l,iops_wr:l,bps_max:l?,bps_rd_max:l?,bps_wr_max:l?,iops_max:l?,iops_rd_max:l?,iops_wr_max:l?,iops_size:l?,group:s?", - .mhandler.cmd_new = qmp_marshal_input_block_set_io_throttle, + .mhandler.cmd_new = qmp_marshal_block_set_io_throttle, }, SQMP @@ -1932,7 +1934,7 @@ EQMP { .name = "set_password", .args_type = "protocol:s,password:s,connected:s?", - .mhandler.cmd_new = qmp_marshal_input_set_password, + .mhandler.cmd_new = qmp_marshal_set_password, }, SQMP @@ -1958,7 +1960,7 @@ EQMP { .name = "expire_password", .args_type = "protocol:s,time:s", - .mhandler.cmd_new = qmp_marshal_input_expire_password, + .mhandler.cmd_new = qmp_marshal_expire_password, }, SQMP @@ -1983,7 +1985,7 @@ EQMP { .name = "add_client", .args_type = "protocol:s,fdname:s,skipauth:b?,tls:b?", - .mhandler.cmd_new = qmp_marshal_input_add_client, + .mhandler.cmd_new = qmp_marshal_add_client, }, SQMP @@ -2034,7 +2036,7 @@ EQMP { .name = "human-monitor-command", .args_type = "command-line:s,cpu-index:i?", - .mhandler.cmd_new = qmp_marshal_input_human_monitor_command, + .mhandler.cmd_new = qmp_marshal_human_monitor_command, }, SQMP @@ -2113,7 +2115,7 @@ EQMP { .name = "query-version", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_version, + .mhandler.cmd_new = qmp_marshal_query_version, }, SQMP @@ -2150,7 +2152,7 @@ EQMP { .name = "query-commands", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_commands, + .mhandler.cmd_new = qmp_marshal_query_commands, }, SQMP @@ -2187,7 +2189,24 @@ EQMP { .name = "query-events", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_events, + .mhandler.cmd_new = qmp_marshal_query_events, + }, + +SQMP +query-qmp-schema +---------------- + +Return the QMP wire schema. The returned value is a json-array of +named schema entities. Entities are commands, events and various +types. See docs/qapi-code-gen.txt for information on their structure +and intended use. + +EQMP + + { + .name = "query-qmp-schema", + .args_type = "", + .mhandler.cmd_new = qmp_query_qmp_schema, }, SQMP @@ -2232,7 +2251,7 @@ EQMP { .name = "query-chardev", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_chardev, + .mhandler.cmd_new = qmp_marshal_query_chardev, }, SQMP @@ -2273,7 +2292,7 @@ EQMP { .name = "query-chardev-backends", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_chardev_backends, + .mhandler.cmd_new = qmp_marshal_query_chardev_backends, }, SQMP @@ -2457,7 +2476,7 @@ EQMP { .name = "query-block", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_block, + .mhandler.cmd_new = qmp_marshal_query_block, }, SQMP @@ -2584,7 +2603,7 @@ EQMP { .name = "query-blockstats", .args_type = "query-nodes:b?", - .mhandler.cmd_new = qmp_marshal_input_query_blockstats, + .mhandler.cmd_new = qmp_marshal_query_blockstats, }, SQMP @@ -2635,7 +2654,7 @@ EQMP { .name = "query-cpus", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_cpus, + .mhandler.cmd_new = qmp_marshal_query_cpus, }, SQMP @@ -2674,7 +2693,7 @@ EQMP { .name = "query-iothreads", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_iothreads, + .mhandler.cmd_new = qmp_marshal_query_iothreads, }, SQMP @@ -2891,7 +2910,7 @@ EQMP { .name = "query-pci", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_pci, + .mhandler.cmd_new = qmp_marshal_query_pci, }, SQMP @@ -2915,7 +2934,7 @@ EQMP { .name = "query-kvm", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_kvm, + .mhandler.cmd_new = qmp_marshal_query_kvm, }, SQMP @@ -2955,7 +2974,7 @@ EQMP { .name = "query-status", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_status, + .mhandler.cmd_new = qmp_marshal_query_status, }, SQMP @@ -2999,7 +3018,7 @@ EQMP { .name = "query-mice", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_mice, + .mhandler.cmd_new = qmp_marshal_query_mice, }, SQMP @@ -3062,12 +3081,12 @@ EQMP { .name = "query-vnc", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_vnc, + .mhandler.cmd_new = qmp_marshal_query_vnc, }, { .name = "query-vnc-servers", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_vnc_servers, + .mhandler.cmd_new = qmp_marshal_query_vnc_servers, }, SQMP @@ -3144,7 +3163,7 @@ EQMP { .name = "query-spice", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_spice, + .mhandler.cmd_new = qmp_marshal_query_spice, }, #endif @@ -3168,7 +3187,7 @@ EQMP { .name = "query-name", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_name, + .mhandler.cmd_new = qmp_marshal_query_name, }, SQMP @@ -3191,7 +3210,7 @@ EQMP { .name = "query-uuid", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_uuid, + .mhandler.cmd_new = qmp_marshal_query_uuid, }, SQMP @@ -3240,7 +3259,7 @@ EQMP { .name = "query-command-line-options", .args_type = "option:s?", - .mhandler.cmd_new = qmp_marshal_input_query_command_line_options, + .mhandler.cmd_new = qmp_marshal_query_command_line_options, }, SQMP @@ -3418,7 +3437,7 @@ EQMP { .name = "query-migrate", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_migrate, + .mhandler.cmd_new = qmp_marshal_query_migrate, }, SQMP @@ -3446,7 +3465,7 @@ EQMP .name = "migrate-set-capabilities", .args_type = "capabilities:q", .params = "capability:s,state:b", - .mhandler.cmd_new = qmp_marshal_input_migrate_set_capabilities, + .mhandler.cmd_new = qmp_marshal_migrate_set_capabilities, }, SQMP query-migrate-capabilities @@ -3472,7 +3491,7 @@ EQMP { .name = "query-migrate-capabilities", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_migrate_capabilities, + .mhandler.cmd_new = qmp_marshal_query_migrate_capabilities, }, SQMP @@ -3498,7 +3517,7 @@ EQMP .name = "migrate-set-parameters", .args_type = "compress-level:i?,compress-threads:i?,decompress-threads:i?", - .mhandler.cmd_new = qmp_marshal_input_migrate_set_parameters, + .mhandler.cmd_new = qmp_marshal_migrate_set_parameters, }, SQMP query-migrate-parameters @@ -3529,7 +3548,7 @@ EQMP { .name = "query-migrate-parameters", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_migrate_parameters, + .mhandler.cmd_new = qmp_marshal_query_migrate_parameters, }, SQMP @@ -3557,88 +3576,88 @@ EQMP { .name = "query-balloon", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_balloon, + .mhandler.cmd_new = qmp_marshal_query_balloon, }, { .name = "query-block-jobs", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_block_jobs, + .mhandler.cmd_new = qmp_marshal_query_block_jobs, }, { .name = "qom-list", .args_type = "path:s", - .mhandler.cmd_new = qmp_marshal_input_qom_list, + .mhandler.cmd_new = qmp_marshal_qom_list, }, { .name = "qom-set", .args_type = "path:s,property:s,value:q", - .mhandler.cmd_new = qmp_qom_set, + .mhandler.cmd_new = qmp_marshal_qom_set, }, { .name = "qom-get", .args_type = "path:s,property:s", - .mhandler.cmd_new = qmp_qom_get, + .mhandler.cmd_new = qmp_marshal_qom_get, }, { .name = "nbd-server-start", .args_type = "addr:q", - .mhandler.cmd_new = qmp_marshal_input_nbd_server_start, + .mhandler.cmd_new = qmp_marshal_nbd_server_start, }, { .name = "nbd-server-add", .args_type = "device:B,writable:b?", - .mhandler.cmd_new = qmp_marshal_input_nbd_server_add, + .mhandler.cmd_new = qmp_marshal_nbd_server_add, }, { .name = "nbd-server-stop", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_nbd_server_stop, + .mhandler.cmd_new = qmp_marshal_nbd_server_stop, }, { .name = "change-vnc-password", .args_type = "password:s", - .mhandler.cmd_new = qmp_marshal_input_change_vnc_password, + .mhandler.cmd_new = qmp_marshal_change_vnc_password, }, { .name = "qom-list-types", .args_type = "implements:s?,abstract:b?", - .mhandler.cmd_new = qmp_marshal_input_qom_list_types, + .mhandler.cmd_new = qmp_marshal_qom_list_types, }, { .name = "device-list-properties", .args_type = "typename:s", - .mhandler.cmd_new = qmp_marshal_input_device_list_properties, + .mhandler.cmd_new = qmp_marshal_device_list_properties, }, { .name = "query-machines", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_machines, + .mhandler.cmd_new = qmp_marshal_query_machines, }, { .name = "query-cpu-definitions", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_cpu_definitions, + .mhandler.cmd_new = qmp_marshal_query_cpu_definitions, }, { .name = "query-target", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_target, + .mhandler.cmd_new = qmp_marshal_query_target, }, { .name = "query-tpm", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_tpm, + .mhandler.cmd_new = qmp_marshal_query_tpm, }, SQMP @@ -3672,7 +3691,7 @@ EQMP { .name = "query-tpm-models", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_tpm_models, + .mhandler.cmd_new = qmp_marshal_query_tpm_models, }, SQMP @@ -3693,7 +3712,7 @@ EQMP { .name = "query-tpm-types", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_tpm_types, + .mhandler.cmd_new = qmp_marshal_query_tpm_types, }, SQMP @@ -3714,7 +3733,7 @@ EQMP { .name = "chardev-add", .args_type = "id:s,backend:q", - .mhandler.cmd_new = qmp_marshal_input_chardev_add, + .mhandler.cmd_new = qmp_marshal_chardev_add, }, SQMP @@ -3751,7 +3770,7 @@ EQMP { .name = "chardev-remove", .args_type = "id:s", - .mhandler.cmd_new = qmp_marshal_input_chardev_remove, + .mhandler.cmd_new = qmp_marshal_chardev_remove, }, @@ -3774,7 +3793,7 @@ EQMP { .name = "query-rx-filter", .args_type = "name:s?", - .mhandler.cmd_new = qmp_marshal_input_query_rx_filter, + .mhandler.cmd_new = qmp_marshal_query_rx_filter, }, SQMP @@ -3840,7 +3859,7 @@ EQMP { .name = "blockdev-add", .args_type = "options:q", - .mhandler.cmd_new = qmp_marshal_input_blockdev_add, + .mhandler.cmd_new = qmp_marshal_blockdev_add, }, SQMP @@ -3899,7 +3918,7 @@ EQMP { .name = "query-named-block-nodes", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_named_block_nodes, + .mhandler.cmd_new = qmp_marshal_query_named_block_nodes, }, SQMP @@ -3961,7 +3980,7 @@ EQMP { .name = "query-memdev", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_memdev, + .mhandler.cmd_new = qmp_marshal_query_memdev, }, SQMP @@ -3999,7 +4018,7 @@ EQMP { .name = "query-memory-devices", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_memory_devices, + .mhandler.cmd_new = qmp_marshal_query_memory_devices, }, SQMP @@ -4026,7 +4045,7 @@ EQMP { .name = "query-acpi-ospm-status", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_query_acpi_ospm_status, + .mhandler.cmd_new = qmp_marshal_query_acpi_ospm_status, }, SQMP @@ -4049,7 +4068,7 @@ EQMP { .name = "rtc-reset-reinjection", .args_type = "", - .mhandler.cmd_new = qmp_marshal_input_rtc_reset_reinjection, + .mhandler.cmd_new = qmp_marshal_rtc_reset_reinjection, }, #endif @@ -4070,7 +4089,7 @@ EQMP { .name = "trace-event-get-state", .args_type = "name:s", - .mhandler.cmd_new = qmp_marshal_input_trace_event_get_state, + .mhandler.cmd_new = qmp_marshal_trace_event_get_state, }, SQMP @@ -4088,7 +4107,7 @@ EQMP { .name = "trace-event-set-state", .args_type = "name:s,enable:b,ignore-unavailable:b?", - .mhandler.cmd_new = qmp_marshal_input_trace_event_set_state, + .mhandler.cmd_new = qmp_marshal_trace_event_set_state, }, SQMP @@ -4106,7 +4125,7 @@ EQMP { .name = "x-input-send-event", .args_type = "console:i?,events:q", - .mhandler.cmd_new = qmp_marshal_input_x_input_send_event, + .mhandler.cmd_new = qmp_marshal_x_input_send_event, }, SQMP @@ -4171,7 +4190,7 @@ EQMP { .name = "block-set-write-threshold", .args_type = "node-name:s,write-threshold:l", - .mhandler.cmd_new = qmp_marshal_input_block_set_write_threshold, + .mhandler.cmd_new = qmp_marshal_block_set_write_threshold, }, SQMP @@ -4199,7 +4218,7 @@ EQMP { .name = "query-rocker", .args_type = "name:s", - .mhandler.cmd_new = qmp_marshal_input_query_rocker, + .mhandler.cmd_new = qmp_marshal_query_rocker, }, SQMP @@ -4220,7 +4239,7 @@ EQMP { .name = "query-rocker-ports", .args_type = "name:s", - .mhandler.cmd_new = qmp_marshal_input_query_rocker_ports, + .mhandler.cmd_new = qmp_marshal_query_rocker_ports, }, SQMP @@ -4245,7 +4264,7 @@ EQMP { .name = "query-rocker-of-dpa-flows", .args_type = "name:s,tbl-id:i?", - .mhandler.cmd_new = qmp_marshal_input_query_rocker_of_dpa_flows, + .mhandler.cmd_new = qmp_marshal_query_rocker_of_dpa_flows, }, SQMP @@ -4274,7 +4293,7 @@ EQMP { .name = "query-rocker-of-dpa-groups", .args_type = "name:s,type:i?", - .mhandler.cmd_new = qmp_marshal_input_query_rocker_of_dpa_groups, + .mhandler.cmd_new = qmp_marshal_query_rocker_of_dpa_groups, }, SQMP diff --git a/qmp.c b/qmp.c index 9623c802ab..057a7cb5e2 100644 --- a/qmp.c +++ b/qmp.c @@ -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; diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py index 890ce5db92..810a897625 100644 --- a/scripts/qapi-commands.py +++ b/scripts/qapi-commands.py @@ -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) diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py index 7f238df5b2..d15fad98f3 100644 --- a/scripts/qapi-event.py +++ b/scripts/qapi-event.py @@ -2,78 +2,64 @@ # QAPI event generator # # Copyright (c) 2014 Wenchao Xia +# Copyright (c) 2015 Red Hat Inc. # # Authors: # Wenchao Xia +# Markus Armbruster # # 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) diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py new file mode 100644 index 0000000000..7d39320174 --- /dev/null +++ b/scripts/qapi-introspect.py @@ -0,0 +1,213 @@ +# +# QAPI introspection generator +# +# Copyright (C) 2015 Red Hat, Inc. +# +# Authors: +# Markus Armbruster +# +# 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) diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index a8453d1365..b292682df6 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -2,96 +2,81 @@ # QAPI types generator # # Copyright IBM, Corp. 2011 +# Copyright (c) 2013-2015 Red Hat Inc. # # Authors: # Anthony Liguori +# Markus Armbruster # # 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_fwd_builtin(name): + +def gen_fwd_object_or_array(name): return mcgen(''' -typedef struct %(name)sList { - union { - %(type)s value; - uint64_t padding; - }; - struct %(name)sList *next; -} %(name)sList; +typedef struct %(c_name)s %(c_name)s; ''', - type=c_type(name), - name=name) + c_name=c_name(name)) -def generate_fwd_struct(name): + +def gen_array(name, element_type): return mcgen(''' -typedef struct %(name)s %(name)s; - -typedef struct %(name)sList { +struct %(c_name)s { union { - %(name)s *value; + %(c_type)s value; uint64_t padding; }; - struct %(name)sList *next; -} %(name)sList; + %(c_name)s *next; +}; ''', - name=c_name(name)) + c_name=c_name(name), c_type=element_type.c_type()) -def generate_fwd_enum_struct(name): - return mcgen(''' -typedef struct %(name)sList { - union { - %(name)s value; - uint64_t padding; - }; - struct %(name)sList *next; -} %(name)sList; -''', - name=c_name(name)) - -def generate_struct_fields(members): +def gen_struct_field(name, typ, optional): ret = '' - for argname, argentry, optional in parse_args(members): - if optional: - ret += mcgen(''' + if optional: + ret += mcgen(''' bool has_%(c_name)s; ''', - c_name=c_name(argname)) - ret += mcgen(''' + c_name=c_name(name)) + ret += mcgen(''' %(c_type)s %(c_name)s; ''', - c_type=c_type(argentry), c_name=c_name(argname)) - + c_type=typ.c_type(), c_name=c_name(name)) return ret -def generate_struct(expr): - structname = expr.get('struct', "") - members = expr['data'] - base = expr.get('base') +def gen_struct_fields(members): + ret = '' + for memb in members: + ret += gen_struct_field(memb.name, memb.type, memb.optional) + return ret + + +def gen_struct(name, base, members): ret = mcgen(''' -struct %(name)s { +struct %(c_name)s { ''', - name=c_name(structname)) + c_name=c_name(name)) if base: - ret += generate_struct_fields({'base': base}) + ret += gen_struct_field('base', base, False) - ret += generate_struct_fields(members) + ret += gen_struct_fields(members) # Make sure that all structs have at least one field; this avoids - # potential issues with attempting to malloc space for zero-length structs - # in C, and also incompatibility with C++ (where an empty struct is size 1). + # potential issues with attempting to malloc space for zero-length + # structs in C, and also incompatibility with C++ (where an empty + # struct is size 1). if not base and not members: - ret += mcgen(''' + ret += mcgen(''' char qapi_dummy_field_for_empty_struct; ''') @@ -101,81 +86,32 @@ struct %(name)s { return ret -def generate_enum_lookup(name, values, prefix=None): + +def gen_alternate_qtypes_decl(name): + return mcgen(''' + +extern const int %(c_name)s_qtypes[]; +''', + c_name=c_name(name)) + + +def gen_alternate_qtypes(name, variants): ret = mcgen(''' -const char *const %(name)s_lookup[] = { +const int %(c_name)s_qtypes[QTYPE_MAX] = { ''', - name=c_name(name)) - for value in values: - index = c_enum_const(name, value, prefix) - ret += mcgen(''' - [%(index)s] = "%(value)s", -''', - index = index, value = value) + c_name=c_name(name)) - max_index = c_enum_const(name, 'MAX', prefix) - ret += mcgen(''' - [%(max_index)s] = NULL, -}; -''', - max_index=max_index) - return ret - -def generate_enum(name, values, prefix=None): - name = c_name(name) - lookup_decl = mcgen(''' - -extern const char *const %(name)s_lookup[]; -''', - name=name) - - enum_decl = mcgen(''' - -typedef enum %(name)s { -''', - name=name) - - # append automatically generated _MAX value - enum_values = values + [ 'MAX' ] - - i = 0 - for value in enum_values: - enum_full_value = c_enum_const(name, value, prefix) - enum_decl += mcgen(''' - %(enum_full_value)s = %(i)d, -''', - enum_full_value = enum_full_value, - i=i) - i += 1 - - enum_decl += mcgen(''' -} %(name)s; -''', - name=name) - - return enum_decl + lookup_decl - -def generate_alternate_qtypes(expr): - - name = expr['alternate'] - members = expr['data'] - - ret = mcgen(''' - -const int %(name)s_qtypes[QTYPE_MAX] = { -''', - name=c_name(name)) - - for key in members: - qtype = find_alternate_member_qtype(members[key]) - assert qtype, "Invalid alternate member" + for var in variants.variants: + qtype = var.type.alternate_qtype() + assert qtype ret += mcgen(''' [%(qtype)s] = %(enum_const)s, ''', - qtype = qtype, - enum_const = c_enum_const(name + 'Kind', key)) + qtype=qtype, + enum_const=c_enum_const(variants.tag_member.type.name, + var.name)) ret += mcgen(''' }; @@ -183,41 +119,26 @@ const int %(name)s_qtypes[QTYPE_MAX] = { return ret -def generate_union(expr, meta): - - name = c_name(expr[meta]) - typeinfo = expr['data'] - - base = expr.get('base') - discriminator = expr.get('discriminator') - - enum_define = discriminator_find_enum_define(expr) - if enum_define: - discriminator_type_name = enum_define['enum_name'] - else: - discriminator_type_name = '%sKind' % (name) - +def gen_union(name, base, variants): ret = mcgen(''' -struct %(name)s { +struct %(c_name)s { ''', - name=name) + c_name=c_name(name)) if base: ret += mcgen(''' /* Members inherited from %(c_name)s: */ ''', - c_name=c_name(base)) - base_fields = find_struct(base)['data'] - ret += generate_struct_fields(base_fields) + c_name=c_name(base.name)) + ret += gen_struct_fields(base.members) ret += mcgen(''' /* Own members: */ ''') else: - assert not discriminator ret += mcgen(''' - %(discriminator_type_name)s kind; + %(c_type)s kind; ''', - discriminator_type_name=c_name(discriminator_type_name)) + c_type=c_name(variants.tag_member.type.name)) # FIXME: What purpose does data serve, besides preventing a union that # has a branch named 'data'? We use it in qapi-visit.py to decide @@ -231,39 +152,41 @@ struct %(name)s { union { /* union tag is @%(c_name)s */ void *data; ''', - c_name=c_name(discriminator or 'kind')) + # TODO ugly special case for simple union + # Use same tag name in C as on the wire to get rid of + # it, then: c_name=c_name(variants.tag_member.name) + c_name=c_name(variants.tag_name or 'kind')) - for key in typeinfo: + for var in variants.variants: + # Ugly special case for simple union TODO get rid of it + typ = var.simple_union_type() or var.type ret += mcgen(''' %(c_type)s %(c_name)s; ''', - c_type=c_type(typeinfo[key]), - c_name=c_name(key)) + c_type=typ.c_type(), + c_name=c_name(var.name)) ret += mcgen(''' }; }; ''') - if meta == 'alternate': - ret += mcgen(''' -extern const int %(name)s_qtypes[]; -''', - name=name) - return ret -def generate_type_cleanup_decl(name): + +def gen_type_cleanup_decl(name): ret = mcgen(''' -void qapi_free_%(name)s(%(c_type)s obj); + +void qapi_free_%(c_name)s(%(c_name)s *obj); ''', - c_type=c_type(name), name=c_name(name)) + c_name=c_name(name)) return ret -def generate_type_cleanup(name): + +def gen_type_cleanup(name): ret = mcgen(''' -void qapi_free_%(name)s(%(c_type)s obj) +void qapi_free_%(c_name)s(%(c_name)s *obj) { QapiDeallocVisitor *md; Visitor *v; @@ -274,13 +197,83 @@ void qapi_free_%(name)s(%(c_type)s obj) md = qapi_dealloc_visitor_new(); v = qapi_dealloc_get_visitor(md); - visit_type_%(name)s(v, &obj, NULL, NULL); + visit_type_%(c_name)s(v, &obj, NULL, NULL); qapi_dealloc_visitor_cleanup(md); } ''', - c_type=c_type(name), name=c_name(name)) + c_name=c_name(name)) return ret + +class QAPISchemaGenTypeVisitor(QAPISchemaVisitor): + def __init__(self): + self.decl = None + self.defn = None + self._fwdecl = None + self._fwdefn = None + self._btin = None + + def visit_begin(self, schema): + self.decl = '' + self.defn = '' + self._fwdecl = '' + self._fwdefn = '' + self._btin = guardstart('QAPI_TYPES_BUILTIN') + + def visit_end(self): + self.decl = self._fwdecl + self.decl + self._fwdecl = None + self.defn = self._fwdefn + self.defn + self._fwdefn = None + # To avoid header dependency hell, we always generate + # declarations for built-in types in our header files and + # simply guard them. See also do_builtins (command line + # option -b). + self._btin += guardend('QAPI_TYPES_BUILTIN') + self.decl = self._btin + self.decl + self._btin = None + + def _gen_type_cleanup(self, name): + self.decl += gen_type_cleanup_decl(name) + self.defn += gen_type_cleanup(name) + + def visit_enum_type(self, name, info, values, prefix): + self._fwdecl += gen_enum(name, values, prefix) + self._fwdefn += gen_enum_lookup(name, values, prefix) + + def visit_array_type(self, name, info, element_type): + if isinstance(element_type, QAPISchemaBuiltinType): + self._btin += gen_fwd_object_or_array(name) + self._btin += gen_array(name, element_type) + self._btin += gen_type_cleanup_decl(name) + if do_builtins: + self.defn += gen_type_cleanup(name) + else: + self._fwdecl += gen_fwd_object_or_array(name) + self.decl += gen_array(name, element_type) + self._gen_type_cleanup(name) + + def visit_object_type(self, name, info, base, members, variants): + if info: + self._fwdecl += gen_fwd_object_or_array(name) + if variants: + assert not members # not implemented + self.decl += gen_union(name, base, variants) + else: + self.decl += gen_struct(name, base, members) + self._gen_type_cleanup(name) + + def visit_alternate_type(self, name, info, variants): + self._fwdecl += gen_fwd_object_or_array(name) + self._fwdefn += gen_alternate_qtypes(name, variants) + self.decl += gen_union(name, None, variants) + self.decl += gen_alternate_qtypes_decl(name) + self._gen_type_cleanup(name) + +# If you link code generated from multiple schemata, you want only one +# instance of the code for built-in types. Generate it only when +# do_builtins, enabled by command line option -b. See also +# QAPISchemaGenTypeVisitor.visit_end(). do_builtins = False (input_file, output_dir, do_c, do_h, prefix, opts) = \ @@ -334,81 +327,13 @@ fdef.write(mcgen(''' fdecl.write(mcgen(''' #include #include +#include "qapi/qmp/qobject.h" ''')) -exprs = parse_schema(input_file) - -fdecl.write(guardstart("QAPI_TYPES_BUILTIN_STRUCT_DECL")) -for typename in builtin_types.keys(): - fdecl.write(generate_fwd_builtin(typename)) -fdecl.write(guardend("QAPI_TYPES_BUILTIN_STRUCT_DECL")) - -for expr in exprs: - ret = "" - if expr.has_key('struct'): - ret += generate_fwd_struct(expr['struct']) - elif expr.has_key('enum'): - ret += generate_enum(expr['enum'], expr['data'], - expr.get('prefix')) - ret += generate_fwd_enum_struct(expr['enum']) - fdef.write(generate_enum_lookup(expr['enum'], expr['data'], - expr.get('prefix'))) - elif expr.has_key('union'): - ret += generate_fwd_struct(expr['union']) - enum_define = discriminator_find_enum_define(expr) - if not enum_define: - ret += generate_enum('%sKind' % expr['union'], expr['data'].keys()) - fdef.write(generate_enum_lookup('%sKind' % expr['union'], - expr['data'].keys())) - elif expr.has_key('alternate'): - ret += generate_fwd_struct(expr['alternate']) - ret += generate_enum('%sKind' % expr['alternate'], expr['data'].keys()) - fdef.write(generate_enum_lookup('%sKind' % expr['alternate'], - expr['data'].keys())) - fdef.write(generate_alternate_qtypes(expr)) - else: - continue - fdecl.write(ret) - -# to avoid header dependency hell, we always generate declarations -# for built-in types in our header files and simply guard them -fdecl.write(guardstart("QAPI_TYPES_BUILTIN_CLEANUP_DECL")) -for typename in builtin_types.keys(): - fdecl.write(generate_type_cleanup_decl(typename + "List")) -fdecl.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DECL")) - -# ...this doesn't work for cases where we link in multiple objects that -# have the functions defined, so we use -b option to provide control -# over these cases -if do_builtins: - for typename in builtin_types.keys(): - fdef.write(generate_type_cleanup(typename + "List")) - -for expr in exprs: - ret = "" - if expr.has_key('struct'): - ret += generate_struct(expr) + "\n" - ret += generate_type_cleanup_decl(expr['struct'] + "List") - fdef.write(generate_type_cleanup(expr['struct'] + "List")) - ret += generate_type_cleanup_decl(expr['struct']) - fdef.write(generate_type_cleanup(expr['struct'])) - elif expr.has_key('union'): - ret += generate_union(expr, 'union') + "\n" - ret += generate_type_cleanup_decl(expr['union'] + "List") - fdef.write(generate_type_cleanup(expr['union'] + "List")) - ret += generate_type_cleanup_decl(expr['union']) - fdef.write(generate_type_cleanup(expr['union'])) - elif expr.has_key('alternate'): - ret += generate_union(expr, 'alternate') + "\n" - ret += generate_type_cleanup_decl(expr['alternate'] + "List") - fdef.write(generate_type_cleanup(expr['alternate'] + "List")) - ret += generate_type_cleanup_decl(expr['alternate']) - fdef.write(generate_type_cleanup(expr['alternate'])) - elif expr.has_key('enum'): - ret += "\n" + generate_type_cleanup_decl(expr['enum'] + "List") - fdef.write(generate_type_cleanup(expr['enum'] + "List")) - else: - continue - fdecl.write(ret) +schema = QAPISchema(input_file) +gen = QAPISchemaGenTypeVisitor() +schema.visit(gen) +fdef.write(gen.defn) +fdecl.write(gen.decl) close_output(fdef, fdecl) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 3cd662bd6b..97343cf7e9 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -12,25 +12,36 @@ # 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 implicit_structs_seen = set() struct_fields_seen = set() -def generate_visit_implicit_struct(type): - if type in implicit_structs_seen: + +def gen_visit_decl(name, scalar=False): + c_type = c_name(name) + ' *' + if not scalar: + c_type += '*' + return mcgen(''' +void visit_type_%(c_name)s(Visitor *m, %(c_type)sobj, const char *name, Error **errp); +''', + c_name=c_name(name), c_type=c_type) + + +def gen_visit_implicit_struct(typ): + if typ in implicit_structs_seen: return '' - implicit_structs_seen.add(type) + implicit_structs_seen.add(typ) + ret = '' - if type not in struct_fields_seen: + if typ.name not in struct_fields_seen: # Need a forward declaration ret += mcgen(''' static void visit_type_%(c_type)s_fields(Visitor *m, %(c_type)s **obj, Error **errp); ''', - c_type=type_name(type)) + c_type=typ.c_name()) ret += mcgen(''' @@ -46,52 +57,53 @@ static void visit_type_implicit_%(c_type)s(Visitor *m, %(c_type)s **obj, Error * error_propagate(errp, err); } ''', - c_type=type_name(type)) + c_type=typ.c_name()) return ret -def generate_visit_struct_fields(name, members, base = None): + +def gen_visit_struct_fields(name, base, members): struct_fields_seen.add(name) ret = '' if base: - ret += generate_visit_implicit_struct(base) + ret += gen_visit_implicit_struct(base) ret += mcgen(''' -static void visit_type_%(name)s_fields(Visitor *m, %(name)s **obj, Error **errp) +static void visit_type_%(c_name)s_fields(Visitor *m, %(c_name)s **obj, Error **errp) { Error *err = NULL; ''', - name=c_name(name)) + c_name=c_name(name)) push_indent() if base: ret += mcgen(''' -visit_type_implicit_%(type)s(m, &(*obj)->%(c_name)s, &err); +visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err); if (err) { goto out; } ''', - type=type_name(base), c_name=c_name('base')) + c_type=base.c_name(), c_name=c_name('base')) - for argname, argentry, optional in parse_args(members): - if optional: + for memb in members: + if memb.optional: ret += mcgen(''' visit_optional(m, &(*obj)->has_%(c_name)s, "%(name)s", &err); if (!err && (*obj)->has_%(c_name)s) { ''', - c_name=c_name(argname), name=argname) + c_name=c_name(memb.name), name=memb.name) push_indent() ret += mcgen(''' -visit_type_%(type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err); +visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err); ''', - type=type_name(argentry), c_name=c_name(argname), - name=argname) + c_type=memb.type.c_name(), c_name=c_name(memb.name), + name=memb.name) - if optional: + if memb.optional: pop_indent() ret += mcgen(''' } @@ -103,7 +115,7 @@ if (err) { ''') pop_indent() - if re.search('^ *goto out\\;', ret, re.MULTILINE): + if re.search('^ *goto out;', ret, re.MULTILINE): ret += mcgen(''' out: @@ -115,12 +127,17 @@ out: return ret -def generate_visit_struct_body(name): +def gen_visit_struct(name, base, members): + ret = gen_visit_struct_fields(name, base, members) + # FIXME: if *obj is NULL on entry, and visit_start_struct() assigns to # *obj, but then visit_type_FOO_fields() fails, we should clean up *obj # rather than leaving it non-NULL. As currently written, the caller must # call qapi_free_FOO() to avoid a memory leak of the partial FOO. - ret = mcgen(''' + ret += mcgen(''' + +void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp) +{ Error *err = NULL; visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err); @@ -131,37 +148,17 @@ def generate_visit_struct_body(name): visit_end_struct(m, &err); } error_propagate(errp, err); -''', - name=name, c_name=c_name(name)) - - return ret - -def generate_visit_struct(expr): - - name = expr['struct'] - members = expr['data'] - base = expr.get('base') - - ret = generate_visit_struct_fields(name, members, base) - - ret += mcgen(''' - -void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp) -{ -''', - name=c_name(name)) - - ret += generate_visit_struct_body(name) - - ret += mcgen(''' } -''') +''', + name=name, c_name=c_name(name)) + return ret -def generate_visit_list(name): + +def gen_visit_list(name, element_type): return mcgen(''' -void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, Error **errp) +void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp) { Error *err = NULL; GenericList *i, **prev; @@ -174,8 +171,8 @@ void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, E for (prev = (GenericList **)obj; !err && (i = visit_next_list(m, prev, &err)) != NULL; prev = &i) { - %(name)sList *native_i = (%(name)sList *)i; - visit_type_%(name)s(m, &native_i->value, NULL, &err); + %(c_name)s *native_i = (%(c_name)s *)i; + visit_type_%(c_elt_type)s(m, &native_i->value, NULL, &err); } error_propagate(errp, err); @@ -185,9 +182,10 @@ out: error_propagate(errp, err); } ''', - name=type_name(name)) + c_name=c_name(name), c_elt_type=element_type.c_name()) -def generate_visit_enum(name): + +def gen_visit_enum(name): return mcgen(''' void visit_type_%(c_name)s(Visitor *m, %(c_name)s *obj, const char *name, Error **errp) @@ -197,44 +195,36 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s *obj, const char *name, Error ''', c_name=c_name(name), name=name) -def generate_visit_alternate(name, members): + +def gen_visit_alternate(name, variants): ret = mcgen(''' -void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp) +void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp) { Error *err = NULL; - visit_start_implicit_struct(m, (void**) obj, sizeof(%(name)s), &err); + visit_start_implicit_struct(m, (void**) obj, sizeof(%(c_name)s), &err); if (err) { goto out; } - visit_get_next_type(m, (int*) &(*obj)->kind, %(name)s_qtypes, name, &err); + visit_get_next_type(m, (int*) &(*obj)->kind, %(c_name)s_qtypes, name, &err); if (err) { goto out_end; } switch ((*obj)->kind) { ''', - name=c_name(name)) + c_name=c_name(name)) - # For alternate, always use the default enum type automatically generated - # as name + 'Kind' - disc_type = c_name(name) + 'Kind' - - for key in members: - assert (members[key] in builtin_types.keys() - or find_struct(members[key]) - or find_union(members[key]) - or find_enum(members[key])), "Invalid alternate member" - - enum_full_value = c_enum_const(disc_type, key) + for var in variants.variants: ret += mcgen(''' - case %(enum_full_value)s: + case %(case)s: visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, name, &err); break; ''', - enum_full_value = enum_full_value, - c_type = type_name(members[key]), - c_name = c_name(key)) + case=c_enum_const(variants.tag_member.type.name, + var.name), + c_type=var.type.c_name(), + c_name=c_name(var.name)) ret += mcgen(''' default: @@ -252,34 +242,17 @@ out: return ret -def generate_visit_union(expr): - - name = expr['union'] - members = expr['data'] - - base = expr.get('base') - discriminator = expr.get('discriminator') - - enum_define = discriminator_find_enum_define(expr) - if enum_define: - # Use the enum type as discriminator - ret = "" - disc_type = c_name(enum_define['enum_name']) - else: - # There will always be a discriminator in the C switch code, by default - # it is an enum type generated silently - ret = generate_visit_enum(name + 'Kind') - disc_type = c_name(name) + 'Kind' +def gen_visit_union(name, base, variants): + ret = '' if base: - assert discriminator - base_fields = find_struct(base)['data'].copy() - del base_fields[discriminator] - ret += generate_visit_struct_fields(name, base_fields) + members = [m for m in base.members if m != variants.tag_member] + ret += gen_visit_struct_fields(name, None, members) - if discriminator: - for key in members: - ret += generate_visit_implicit_struct(members[key]) + for var in variants.variants: + # Ugly special case for simple union TODO get rid of it + if not var.simple_union_type(): + ret += gen_visit_implicit_struct(var.type) ret += mcgen(''' @@ -297,48 +270,57 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error if base: ret += mcgen(''' - visit_type_%(name)s_fields(m, obj, &err); + visit_type_%(c_name)s_fields(m, obj, &err); if (err) { goto out_obj; } ''', - name=c_name(name)) + c_name=c_name(name)) - if not discriminator: - tag = 'kind' - disc_key = "type" - else: - tag = discriminator - disc_key = discriminator + tag_key = variants.tag_member.name + if not variants.tag_name: + # we pointlessly use a different key for simple unions + tag_key = 'type' ret += mcgen(''' - visit_type_%(disc_type)s(m, &(*obj)->%(c_tag)s, "%(disc_key)s", &err); + visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err); if (err) { goto out_obj; } if (!visit_start_union(m, !!(*obj)->data, &err) || err) { goto out_obj; } - switch ((*obj)->%(c_tag)s) { + switch ((*obj)->%(c_name)s) { ''', - disc_type = disc_type, - c_tag=c_name(tag), - disc_key = disc_key) + c_type=variants.tag_member.type.c_name(), + # TODO ugly special case for simple union + # Use same tag name in C as on the wire to get rid of + # it, then: c_name=c_name(variants.tag_member.name) + c_name=c_name(variants.tag_name or 'kind'), + name=tag_key) - for key in members: - if not discriminator: - fmt = 'visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);' - else: - fmt = 'visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err);' - - enum_full_value = c_enum_const(disc_type, key) + for var in variants.variants: + # TODO ugly special case for simple union + simple_union_type = var.simple_union_type() ret += mcgen(''' - case %(enum_full_value)s: - ''' + fmt + ''' - break; + case %(case)s: ''', - enum_full_value = enum_full_value, - c_type=type_name(members[key]), - c_name=c_name(key)) + case=c_enum_const(variants.tag_member.type.name, + var.name)) + if simple_union_type: + ret += mcgen(''' + visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err); +''', + c_type=simple_union_type.c_name(), + c_name=c_name(var.name)) + else: + ret += mcgen(''' + visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err); +''', + c_type=var.type.c_name(), + c_name=c_name(var.name)) + ret += mcgen(''' + break; +''') ret += mcgen(''' default: @@ -359,38 +341,59 @@ out: return ret -def generate_declaration(name, builtin_type=False): - ret = "" - if not builtin_type: - name = c_name(name) - ret += mcgen(''' -void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp); -''', - name=name) +class QAPISchemaGenVisitVisitor(QAPISchemaVisitor): + def __init__(self): + self.decl = None + self.defn = None + self._btin = None - ret += mcgen(''' -void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, Error **errp); -''', - name=name) + def visit_begin(self, schema): + self.decl = '' + self.defn = '' + self._btin = guardstart('QAPI_VISIT_BUILTIN') - return ret + def visit_end(self): + # To avoid header dependency hell, we always generate + # declarations for built-in types in our header files and + # simply guard them. See also do_builtins (command line + # option -b). + self._btin += guardend('QAPI_VISIT_BUILTIN') + self.decl = self._btin + self.decl + self._btin = None -def generate_enum_declaration(name): - ret = mcgen(''' -void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, Error **errp); -''', - name=c_name(name)) + def visit_enum_type(self, name, info, values, prefix): + self.decl += gen_visit_decl(name, scalar=True) + self.defn += gen_visit_enum(name) - return ret + def visit_array_type(self, name, info, element_type): + decl = gen_visit_decl(name) + defn = gen_visit_list(name, element_type) + if isinstance(element_type, QAPISchemaBuiltinType): + self._btin += decl + if do_builtins: + self.defn += defn + else: + self.decl += decl + self.defn += defn -def generate_decl_enum(name): - return mcgen(''' + def visit_object_type(self, name, info, base, members, variants): + if info: + self.decl += gen_visit_decl(name) + if variants: + assert not members # not implemented + self.defn += gen_visit_union(name, base, variants) + else: + self.defn += gen_visit_struct(name, base, members) -void visit_type_%(name)s(Visitor *m, %(name)s *obj, const char *name, Error **errp); -''', - name=c_name(name)) + def visit_alternate_type(self, name, info, variants): + self.decl += gen_visit_decl(name) + self.defn += gen_visit_alternate(name, variants) +# If you link code generated from multiple schemata, you want only one +# instance of the code for built-in types. Generate it only when +# do_builtins, enabled by command line option -b. See also +# QAPISchemaGenVisitVisitor.visit_end(). do_builtins = False (input_file, output_dir, do_c, do_h, prefix, opts) = \ @@ -437,7 +440,7 @@ fdef.write(mcgen(''' #include "qemu-common.h" #include "%(prefix)sqapi-visit.h" ''', - prefix = prefix)) + prefix=prefix)) fdecl.write(mcgen(''' #include "qapi/visitor.h" @@ -446,56 +449,10 @@ fdecl.write(mcgen(''' ''', prefix=prefix)) -exprs = parse_schema(input_file) - -# to avoid header dependency hell, we always generate declarations -# for built-in types in our header files and simply guard them -fdecl.write(guardstart("QAPI_VISIT_BUILTIN_VISITOR_DECL")) -for typename in builtin_types.keys(): - fdecl.write(generate_declaration(typename, builtin_type=True)) -fdecl.write(guardend("QAPI_VISIT_BUILTIN_VISITOR_DECL")) - -# ...this doesn't work for cases where we link in multiple objects that -# have the functions defined, so we use -b option to provide control -# over these cases -if do_builtins: - for typename in builtin_types.keys(): - fdef.write(generate_visit_list(typename)) - -for expr in exprs: - if expr.has_key('struct'): - ret = generate_visit_struct(expr) - ret += generate_visit_list(expr['struct']) - fdef.write(ret) - - ret = generate_declaration(expr['struct']) - fdecl.write(ret) - elif expr.has_key('union'): - ret = generate_visit_union(expr) - ret += generate_visit_list(expr['union']) - fdef.write(ret) - - enum_define = discriminator_find_enum_define(expr) - ret = "" - if not enum_define: - ret = generate_decl_enum('%sKind' % expr['union']) - ret += generate_declaration(expr['union']) - fdecl.write(ret) - elif expr.has_key('alternate'): - ret = generate_visit_alternate(expr['alternate'], expr['data']) - ret += generate_visit_list(expr['alternate']) - fdef.write(ret) - - ret = generate_decl_enum('%sKind' % expr['alternate']) - ret += generate_declaration(expr['alternate']) - fdecl.write(ret) - elif expr.has_key('enum'): - ret = generate_visit_list(expr['enum']) - ret += generate_visit_enum(expr['enum']) - fdef.write(ret) - - ret = generate_decl_enum(expr['enum']) - ret += generate_enum_declaration(expr['enum']) - fdecl.write(ret) +schema = QAPISchema(input_file) +gen = QAPISchemaGenVisitVisitor() +schema.visit(gen) +fdef.write(gen.defn) +fdecl.write(gen.decl) close_output(fdef, fdecl) diff --git a/scripts/qapi.py b/scripts/qapi.py index c4423b740c..06478bb269 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -33,12 +33,14 @@ builtin_types = { 'uint32': 'QTYPE_QINT', 'uint64': 'QTYPE_QINT', 'size': 'QTYPE_QINT', + 'any': None, # any qtype_code possible, actually } # Whitelist of commands allowed to return a non-dictionary returns_whitelist = [ # From QMP: 'human-monitor-command', + 'qom-get', 'query-migrate-cache-size', 'query-tpm-models', 'query-tpm-types', @@ -103,7 +105,7 @@ class QAPIExprError(Exception): return error_path(self.info['parent']) + \ "%s:%d: %s" % (self.info['file'], self.info['line'], self.msg) -class QAPISchema: +class QAPISchemaParser(object): def __init__(self, fp, previously_included = [], incl_info = None): abs_fname = os.path.abspath(fp.name) @@ -149,8 +151,8 @@ class QAPISchema: except IOError, e: raise QAPIExprError(expr_info, '%s: %s' % (e.strerror, include)) - exprs_include = QAPISchema(fobj, previously_included, - expr_info) + exprs_include = QAPISchemaParser(fobj, previously_included, + expr_info) self.exprs.extend(exprs_include.exprs) else: expr_elem = {'expr': expr, @@ -302,6 +304,8 @@ class QAPISchema: # # Semantic analysis of schema expressions +# TODO fold into QAPISchema +# TODO catching name collisions in generated code would be nice # def find_base_fields(base): @@ -424,15 +428,12 @@ def is_enum(name): def check_type(expr_info, source, value, allow_array = False, allow_dict = False, allow_optional = False, - allow_star = False, allow_metas = []): + allow_metas = []): global all_names if value is None: return - if allow_star and value == '**': - return - # Check if array type for value is okay if isinstance(value, list): if not allow_array: @@ -446,10 +447,6 @@ def check_type(expr_info, source, value, allow_array = False, # Check if type name for value is okay if isinstance(value, str): - if value == '**': - raise QAPIExprError(expr_info, - "%s uses '**' but did not request 'gen':false" - % source) if not value in all_names: raise QAPIExprError(expr_info, "%s uses unknown type '%s'" @@ -475,7 +472,7 @@ def check_type(expr_info, source, value, allow_array = False, # Todo: allow dictionaries to represent default values of # an optional argument. check_type(expr_info, "Member '%s' of %s" % (key, source), arg, - allow_array=True, allow_star=allow_star, + allow_array=True, allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']) @@ -495,18 +492,16 @@ def check_member_clash(expr_info, base_name, data, source = ""): def check_command(expr, expr_info): name = expr['command'] - allow_star = expr.has_key('gen') check_type(expr_info, "'data' for command '%s'" % name, expr.get('data'), allow_dict=True, allow_optional=True, - allow_metas=['struct'], allow_star=allow_star) + allow_metas=['struct']) returns_meta = ['union', 'struct'] if name in returns_whitelist: returns_meta += ['built-in', 'alternate', 'enum'] check_type(expr_info, "'returns' for command '%s'" % name, expr.get('returns'), allow_array=True, - allow_optional=True, allow_metas=returns_meta, - allow_star=allow_star) + allow_optional=True, allow_metas=returns_meta) def check_event(expr, expr_info): global events @@ -751,37 +746,539 @@ def check_exprs(exprs): else: assert False, 'unexpected meta type' - return map(lambda expr_elem: expr_elem['expr'], exprs) + return exprs + + +# +# Schema compiler frontend +# + +class QAPISchemaEntity(object): + def __init__(self, name, info): + assert isinstance(name, str) + self.name = name + self.info = info + + def c_name(self): + return c_name(self.name) + + def check(self, schema): + pass + + def visit(self, visitor): + pass + + +class QAPISchemaVisitor(object): + def visit_begin(self, schema): + pass + + def visit_end(self): + pass + + def visit_builtin_type(self, name, info, json_type): + pass + + def visit_enum_type(self, name, info, values, prefix): + pass + + def visit_array_type(self, name, info, element_type): + pass + + def visit_object_type(self, name, info, base, members, variants): + pass + + def visit_object_type_flat(self, name, info, members, variants): + pass + + def visit_alternate_type(self, name, info, variants): + pass + + def visit_command(self, name, info, arg_type, ret_type, + gen, success_response): + pass + + def visit_event(self, name, info, arg_type): + pass + + +class QAPISchemaType(QAPISchemaEntity): + def c_type(self, is_param=False): + return c_name(self.name) + pointer_suffix + + def c_null(self): + return 'NULL' + + def json_type(self): + pass + + def alternate_qtype(self): + json2qtype = { + 'string': 'QTYPE_QSTRING', + 'number': 'QTYPE_QFLOAT', + 'int': 'QTYPE_QINT', + 'boolean': 'QTYPE_QBOOL', + 'object': 'QTYPE_QDICT' + } + return json2qtype.get(self.json_type()) + + +class QAPISchemaBuiltinType(QAPISchemaType): + def __init__(self, name, json_type, c_type, c_null): + QAPISchemaType.__init__(self, name, None) + assert not c_type or isinstance(c_type, str) + assert json_type in ('string', 'number', 'int', 'boolean', 'null', + 'value') + self._json_type_name = json_type + self._c_type_name = c_type + self._c_null_val = c_null + + def c_name(self): + return self.name + + def c_type(self, is_param=False): + if is_param and self.name == 'str': + return 'const ' + self._c_type_name + return self._c_type_name + + def c_null(self): + return self._c_null_val + + def json_type(self): + return self._json_type_name + + def visit(self, visitor): + visitor.visit_builtin_type(self.name, self.info, self.json_type()) + + +class QAPISchemaEnumType(QAPISchemaType): + def __init__(self, name, info, values, prefix): + QAPISchemaType.__init__(self, name, info) + for v in values: + assert isinstance(v, str) + assert prefix is None or isinstance(prefix, str) + self.values = values + self.prefix = prefix + + def check(self, schema): + assert len(set(self.values)) == len(self.values) + + def c_type(self, is_param=False): + return c_name(self.name) + + def c_null(self): + return c_enum_const(self.name, (self.values + ['MAX'])[0], + self.prefix) + + def json_type(self): + return 'string' + + def visit(self, visitor): + visitor.visit_enum_type(self.name, self.info, + self.values, self.prefix) + + +class QAPISchemaArrayType(QAPISchemaType): + def __init__(self, name, info, element_type): + QAPISchemaType.__init__(self, name, info) + assert isinstance(element_type, str) + self._element_type_name = element_type + self.element_type = None + + def check(self, schema): + self.element_type = schema.lookup_type(self._element_type_name) + assert self.element_type + + def json_type(self): + return 'array' + + def visit(self, visitor): + visitor.visit_array_type(self.name, self.info, self.element_type) + + +class QAPISchemaObjectType(QAPISchemaType): + def __init__(self, name, info, base, local_members, variants): + QAPISchemaType.__init__(self, name, info) + assert base is None or isinstance(base, str) + for m in local_members: + assert isinstance(m, QAPISchemaObjectTypeMember) + assert (variants is None or + isinstance(variants, QAPISchemaObjectTypeVariants)) + self._base_name = base + self.base = None + self.local_members = local_members + self.variants = variants + self.members = None + + def check(self, schema): + assert self.members is not False # not running in cycles + if self.members: + return + self.members = False # mark as being checked + if self._base_name: + self.base = schema.lookup_type(self._base_name) + assert isinstance(self.base, QAPISchemaObjectType) + assert not self.base.variants # not implemented + self.base.check(schema) + members = list(self.base.members) + else: + members = [] + seen = {} + for m in members: + seen[m.name] = m + for m in self.local_members: + m.check(schema, members, seen) + if self.variants: + self.variants.check(schema, members, seen) + self.members = members + + def c_name(self): + assert self.info + return QAPISchemaType.c_name(self) + + def c_type(self, is_param=False): + assert self.info + return QAPISchemaType.c_type(self) + + def json_type(self): + return 'object' + + def visit(self, visitor): + visitor.visit_object_type(self.name, self.info, + self.base, self.local_members, self.variants) + visitor.visit_object_type_flat(self.name, self.info, + self.members, self.variants) + + +class QAPISchemaObjectTypeMember(object): + def __init__(self, name, typ, optional): + assert isinstance(name, str) + assert isinstance(typ, str) + assert isinstance(optional, bool) + self.name = name + self._type_name = typ + self.type = None + self.optional = optional + + def check(self, schema, all_members, seen): + assert self.name not in seen + self.type = schema.lookup_type(self._type_name) + assert self.type + all_members.append(self) + seen[self.name] = self + + +class QAPISchemaObjectTypeVariants(object): + def __init__(self, tag_name, tag_enum, variants): + assert tag_name is None or isinstance(tag_name, str) + assert tag_enum is None or isinstance(tag_enum, str) + for v in variants: + assert isinstance(v, QAPISchemaObjectTypeVariant) + self.tag_name = tag_name + if tag_name: + assert not tag_enum + self.tag_member = None + else: + self.tag_member = QAPISchemaObjectTypeMember('type', tag_enum, + False) + self.variants = variants + + def check(self, schema, members, seen): + if self.tag_name: + self.tag_member = seen[self.tag_name] + else: + self.tag_member.check(schema, members, seen) + assert isinstance(self.tag_member.type, QAPISchemaEnumType) + for v in self.variants: + vseen = dict(seen) + v.check(schema, self.tag_member.type, vseen) + +class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): + def __init__(self, name, typ): + QAPISchemaObjectTypeMember.__init__(self, name, typ, False) + + def check(self, schema, tag_type, seen): + QAPISchemaObjectTypeMember.check(self, schema, [], seen) + assert self.name in tag_type.values + + # This function exists to support ugly simple union special cases + # TODO get rid of them, and drop the function + def simple_union_type(self): + if isinstance(self.type, QAPISchemaObjectType) and not self.type.info: + assert len(self.type.members) == 1 + assert not self.type.variants + return self.type.members[0].type + return None + + +class QAPISchemaAlternateType(QAPISchemaType): + def __init__(self, name, info, variants): + QAPISchemaType.__init__(self, name, info) + assert isinstance(variants, QAPISchemaObjectTypeVariants) + assert not variants.tag_name + self.variants = variants + + def check(self, schema): + self.variants.check(schema, [], {}) + + def json_type(self): + return 'value' + + def visit(self, visitor): + visitor.visit_alternate_type(self.name, self.info, self.variants) + + +class QAPISchemaCommand(QAPISchemaEntity): + def __init__(self, name, info, arg_type, ret_type, gen, success_response): + QAPISchemaEntity.__init__(self, name, info) + assert not arg_type or isinstance(arg_type, str) + assert not ret_type or isinstance(ret_type, str) + self._arg_type_name = arg_type + self.arg_type = None + self._ret_type_name = ret_type + self.ret_type = None + self.gen = gen + self.success_response = success_response + + def check(self, schema): + if self._arg_type_name: + self.arg_type = schema.lookup_type(self._arg_type_name) + assert isinstance(self.arg_type, QAPISchemaObjectType) + assert not self.arg_type.variants # not implemented + if self._ret_type_name: + self.ret_type = schema.lookup_type(self._ret_type_name) + assert isinstance(self.ret_type, QAPISchemaType) + + def visit(self, visitor): + visitor.visit_command(self.name, self.info, + self.arg_type, self.ret_type, + self.gen, self.success_response) + + +class QAPISchemaEvent(QAPISchemaEntity): + def __init__(self, name, info, arg_type): + QAPISchemaEntity.__init__(self, name, info) + assert not arg_type or isinstance(arg_type, str) + self._arg_type_name = arg_type + self.arg_type = None + + def check(self, schema): + if self._arg_type_name: + self.arg_type = schema.lookup_type(self._arg_type_name) + assert isinstance(self.arg_type, QAPISchemaObjectType) + assert not self.arg_type.variants # not implemented + + def visit(self, visitor): + visitor.visit_event(self.name, self.info, self.arg_type) + + +class QAPISchema(object): + def __init__(self, fname): + try: + self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs) + except (QAPISchemaError, QAPIExprError), err: + print >>sys.stderr, err + exit(1) + self._entity_dict = {} + self._def_predefineds() + self._def_exprs() + self.check() + + def _def_entity(self, ent): + assert ent.name not in self._entity_dict + self._entity_dict[ent.name] = ent + + def lookup_entity(self, name, typ=None): + ent = self._entity_dict.get(name) + if typ and not isinstance(ent, typ): + return None + return ent + + def lookup_type(self, name): + return self.lookup_entity(name, QAPISchemaType) + + def _def_builtin_type(self, name, json_type, c_type, c_null): + self._def_entity(QAPISchemaBuiltinType(name, json_type, + c_type, c_null)) + self._make_array_type(name) # TODO really needed? + + def _def_predefineds(self): + for t in [('str', 'string', 'char' + pointer_suffix, 'NULL'), + ('number', 'number', 'double', '0'), + ('int', 'int', 'int64_t', '0'), + ('int8', 'int', 'int8_t', '0'), + ('int16', 'int', 'int16_t', '0'), + ('int32', 'int', 'int32_t', '0'), + ('int64', 'int', 'int64_t', '0'), + ('uint8', 'int', 'uint8_t', '0'), + ('uint16', 'int', 'uint16_t', '0'), + ('uint32', 'int', 'uint32_t', '0'), + ('uint64', 'int', 'uint64_t', '0'), + ('size', 'int', 'uint64_t', '0'), + ('bool', 'boolean', 'bool', 'false'), + ('any', 'value', 'QObject' + pointer_suffix, 'NULL')]: + self._def_builtin_type(*t) + self.the_empty_object_type = QAPISchemaObjectType(':empty', None, None, + [], None) + self._def_entity(self.the_empty_object_type) + + def _make_implicit_enum_type(self, name, values): + name = name + 'Kind' + self._def_entity(QAPISchemaEnumType(name, None, values, None)) + return name + + def _make_array_type(self, element_type): + name = element_type + 'List' + if not self.lookup_type(name): + self._def_entity(QAPISchemaArrayType(name, None, element_type)) + return name + + def _make_implicit_object_type(self, name, role, members): + if not members: + return None + name = ':obj-%s-%s' % (name, role) + if not self.lookup_entity(name, QAPISchemaObjectType): + self._def_entity(QAPISchemaObjectType(name, None, None, + members, None)) + return name + + def _def_enum_type(self, expr, info): + name = expr['enum'] + data = expr['data'] + prefix = expr.get('prefix') + self._def_entity(QAPISchemaEnumType(name, info, data, prefix)) + self._make_array_type(name) # TODO really needed? + + def _make_member(self, name, typ): + optional = False + if name.startswith('*'): + name = name[1:] + optional = True + if isinstance(typ, list): + assert len(typ) == 1 + typ = self._make_array_type(typ[0]) + return QAPISchemaObjectTypeMember(name, typ, optional) + + def _make_members(self, data): + return [self._make_member(key, value) + for (key, value) in data.iteritems()] + + def _def_struct_type(self, expr, info): + name = expr['struct'] + base = expr.get('base') + data = expr['data'] + self._def_entity(QAPISchemaObjectType(name, info, base, + self._make_members(data), + None)) + self._make_array_type(name) # TODO really needed? + + def _make_variant(self, case, typ): + return QAPISchemaObjectTypeVariant(case, typ) + + def _make_simple_variant(self, case, typ): + if isinstance(typ, list): + assert len(typ) == 1 + typ = self._make_array_type(typ[0]) + typ = self._make_implicit_object_type(typ, 'wrapper', + [self._make_member('data', typ)]) + return QAPISchemaObjectTypeVariant(case, typ) + + def _make_tag_enum(self, type_name, variants): + return self._make_implicit_enum_type(type_name, + [v.name for v in variants]) + + def _def_union_type(self, expr, info): + name = expr['union'] + data = expr['data'] + base = expr.get('base') + tag_name = expr.get('discriminator') + tag_enum = None + if tag_name: + variants = [self._make_variant(key, value) + for (key, value) in data.iteritems()] + else: + variants = [self._make_simple_variant(key, value) + for (key, value) in data.iteritems()] + tag_enum = self._make_tag_enum(name, variants) + self._def_entity( + QAPISchemaObjectType(name, info, base, + self._make_members(OrderedDict()), + QAPISchemaObjectTypeVariants(tag_name, + tag_enum, + variants))) + self._make_array_type(name) # TODO really needed? + + def _def_alternate_type(self, expr, info): + name = expr['alternate'] + data = expr['data'] + variants = [self._make_variant(key, value) + for (key, value) in data.iteritems()] + tag_enum = self._make_tag_enum(name, variants) + self._def_entity( + QAPISchemaAlternateType(name, info, + QAPISchemaObjectTypeVariants(None, + tag_enum, + variants))) + self._make_array_type(name) # TODO really needed? + + def _def_command(self, expr, info): + name = expr['command'] + data = expr.get('data') + rets = expr.get('returns') + gen = expr.get('gen', True) + success_response = expr.get('success-response', True) + if isinstance(data, OrderedDict): + data = self._make_implicit_object_type(name, 'arg', + self._make_members(data)) + if isinstance(rets, list): + assert len(rets) == 1 + rets = self._make_array_type(rets[0]) + self._def_entity(QAPISchemaCommand(name, info, data, rets, gen, + success_response)) + + def _def_event(self, expr, info): + name = expr['event'] + data = expr.get('data') + if isinstance(data, OrderedDict): + data = self._make_implicit_object_type(name, 'arg', + self._make_members(data)) + self._def_entity(QAPISchemaEvent(name, info, data)) + + def _def_exprs(self): + for expr_elem in self.exprs: + expr = expr_elem['expr'] + info = expr_elem['info'] + if 'enum' in expr: + self._def_enum_type(expr, info) + elif 'struct' in expr: + self._def_struct_type(expr, info) + elif 'union' in expr: + self._def_union_type(expr, info) + elif 'alternate' in expr: + self._def_alternate_type(expr, info) + elif 'command' in expr: + self._def_command(expr, info) + elif 'event' in expr: + self._def_event(expr, info) + else: + assert False + + def check(self): + for ent in self._entity_dict.values(): + ent.check(self) + + def visit(self, visitor): + ignore = visitor.visit_begin(self) + for name in sorted(self._entity_dict.keys()): + if not ignore or not isinstance(self._entity_dict[name], ignore): + self._entity_dict[name].visit(visitor) + visitor.visit_end() -def parse_schema(fname): - try: - schema = QAPISchema(open(fname, "r")) - return check_exprs(schema.exprs) - except (QAPISchemaError, QAPIExprError), e: - print >>sys.stderr, e - exit(1) # # Code generation helpers # -def parse_args(typeinfo): - if isinstance(typeinfo, str): - struct = find_struct(typeinfo) - assert struct != None - typeinfo = struct['data'] - - for member in typeinfo: - argname = member - argentry = typeinfo[member] - optional = False - if member.startswith('*'): - argname = member[1:] - optional = True - # Todo: allow argentry to be OrderedDict, for providing the - # value of an optional argument. - yield (argname, argentry, optional) - def camel_case(name): new_name = '' first = True @@ -864,70 +1361,9 @@ def c_name(name, protect=True): return "q_" + name return name.translate(c_name_trans) -# Map type @name to the C typedef name for the list form. -# -# ['Name'] -> 'NameList', ['x-Foo'] -> 'x_FooList', ['int'] -> 'intList' -def c_list_type(name): - return type_name(name) + 'List' - -# Map type @value to the C typedef form. -# -# Used for converting 'type' from a 'member':'type' qapi definition -# into the alphanumeric portion of the type for a generated C parameter, -# as well as generated C function names. See c_type() for the rest of -# the conversion such as adding '*' on pointer types. -# 'int' -> 'int', '[x-Foo]' -> 'x_FooList', '__a.b_c' -> '__a_b_c' -def type_name(value): - if type(value) == list: - return c_list_type(value[0]) - if value in builtin_types.keys(): - return value - return c_name(value) - eatspace = '\033EATSPACE.' pointer_suffix = ' *' + eatspace -# Map type @name to its C type expression. -# If @is_param, const-qualify the string type. -# -# This function is used for computing the full C type of 'member':'name'. -# A special suffix is added in c_type() for pointer types, and it's -# stripped in mcgen(). So please notice this when you check the return -# value of c_type() outside mcgen(). -def c_type(value, is_param=False): - if value == 'str': - if is_param: - return 'const char' + pointer_suffix - return 'char' + pointer_suffix - - elif value == 'int': - return 'int64_t' - elif (value == 'int8' or value == 'int16' or value == 'int32' or - value == 'int64' or value == 'uint8' or value == 'uint16' or - value == 'uint32' or value == 'uint64'): - return value + '_t' - elif value == 'size': - return 'uint64_t' - elif value == 'bool': - return 'bool' - elif value == 'number': - return 'double' - elif type(value) == list: - return c_list_type(value[0]) + pointer_suffix - elif is_enum(value): - return c_name(value) - elif value == None: - return 'void' - elif value in events: - return camel_case(value) + 'Event' + pointer_suffix - else: - # complex type name - assert isinstance(value, str) and value != "" - return c_name(value) + pointer_suffix - -def is_c_ptr(value): - return c_type(value).endswith(pointer_suffix) - def genindent(count): ret = "" for i in range(count): @@ -982,6 +1418,74 @@ def guardend(name): ''', name=guardname(name)) +def gen_enum_lookup(name, values, prefix=None): + ret = mcgen(''' + +const char *const %(c_name)s_lookup[] = { +''', + c_name=c_name(name)) + for value in values: + index = c_enum_const(name, value, prefix) + ret += mcgen(''' + [%(index)s] = "%(value)s", +''', + index=index, value=value) + + max_index = c_enum_const(name, 'MAX', prefix) + ret += mcgen(''' + [%(max_index)s] = NULL, +}; +''', + max_index=max_index) + return ret + +def gen_enum(name, values, prefix=None): + # append automatically generated _MAX value + enum_values = values + ['MAX'] + + ret = mcgen(''' + +typedef enum %(c_name)s { +''', + c_name=c_name(name)) + + i = 0 + for value in enum_values: + ret += mcgen(''' + %(c_enum)s = %(i)d, +''', + c_enum=c_enum_const(name, value, prefix), + i=i) + i += 1 + + ret += mcgen(''' +} %(c_name)s; +''', + c_name=c_name(name)) + + ret += mcgen(''' + +extern const char *const %(c_name)s_lookup[]; +''', + c_name=c_name(name)) + return ret + +def gen_params(arg_type, extra): + if not arg_type: + return extra + assert not arg_type.variants + ret = '' + sep = '' + for memb in arg_type.members: + ret += sep + sep = ', ' + if memb.optional: + ret += 'bool has_%s, ' % c_name(memb.name) + ret += '%s %s' % (memb.type.c_type(is_param=True), c_name(memb.name)) + if extra: + ret += sep + extra + return ret + # # Common command line parsing # diff --git a/tests/.gitignore b/tests/.gitignore index 2c5e2c31e4..a607bddce4 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -35,6 +35,7 @@ test-qmp-commands.h test-qmp-event test-qmp-input-strict test-qmp-input-visitor +test-qmp-introspect.[ch] test-qmp-marshal.c test-qmp-output-visitor test-rcu-list diff --git a/tests/Makefile b/tests/Makefile index 7c6025a2c5..4063639a59 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -234,11 +234,12 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ bad-type-dict.json double-data.json unknown-expr-key.json \ redefined-type.json redefined-command.json redefined-builtin.json \ redefined-event.json command-int.json bad-data.json event-max.json \ - type-bypass.json type-bypass-no-gen.json type-bypass-bad-gen.json \ + type-bypass-bad-gen.json \ args-invalid.json \ args-array-empty.json args-array-unknown.json args-int.json \ args-unknown.json args-member-unknown.json args-member-array.json \ args-member-array-bad.json args-alternate.json args-union.json \ + args-any.json \ returns-array-bad.json returns-int.json returns-dict.json \ returns-unknown.json returns-alternate.json returns-whitelist.json \ missing-colon.json missing-comma-list.json missing-comma-object.json \ @@ -255,7 +256,7 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ flat-union-invalid-branch-key.json flat-union-reverse-define.json \ flat-union-string-discriminator.json union-base-no-discriminator.json \ flat-union-bad-discriminator.json flat-union-bad-base.json \ - flat-union-base-star.json \ + flat-union-base-any.json \ flat-union-array-branch.json flat-union-int-branch.json \ flat-union-base-union.json flat-union-branch-clash.json \ alternate-nested.json alternate-unknown.json alternate-clash.json \ @@ -268,7 +269,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ struct-base-clash.json struct-base-clash-deep.json ) GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \ - tests/test-qmp-commands.h tests/test-qapi-event.h + tests/test-qmp-commands.h tests/test-qapi-event.h \ + tests/test-qmp-introspect.h test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \ tests/check-qlist.o tests/check-qfloat.o tests/check-qjson.o \ @@ -288,7 +290,7 @@ QEMU_CFLAGS += -I$(SRC_PATH)/tests test-util-obj-y = libqemuutil.a libqemustub.a test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y) test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \ - tests/test-qapi-event.o \ + tests/test-qapi-event.o tests/test-qmp-introspect.o \ $(test-qom-obj-y) test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y) test-block-obj-y = $(block-obj-y) $(test-crypto-obj-y) @@ -345,6 +347,11 @@ $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-eve $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \ $(gen-out-type) -o tests -p "test-" $<, \ " GEN $@") +tests/test-qmp-introspect.c tests/test-qmp-introspect.h :\ +$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py) + $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \ + $(gen-out-type) -o tests -p "test-" $<, \ + " GEN $@") tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o $(test-qapi-obj-y) tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o $(test-qapi-obj-y) diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out index 99848eefbb..65af7278f4 100644 --- a/tests/qapi-schema/alternate-good.out +++ b/tests/qapi-schema/alternate-good.out @@ -1,6 +1,10 @@ -[OrderedDict([('struct', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]), - OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]), - OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])] -[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']}, - {'enum_name': 'AltKind', 'enum_values': None}] -[OrderedDict([('struct', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])] +object :empty +alternate Alt + case value: int + case string: Enum + case struct: Data +enum AltKind ['value', 'string', 'struct'] +object Data + member number: int optional=True + member name: str optional=True +enum Enum ['hello', 'world'] diff --git a/tests/qapi-schema/args-any.err b/tests/qapi-schema/args-any.err new file mode 100644 index 0000000000..bf9b5e0730 --- /dev/null +++ b/tests/qapi-schema/args-any.err @@ -0,0 +1 @@ +tests/qapi-schema/args-any.json:2: 'data' for command 'oops' cannot use built-in type 'any' diff --git a/tests/qapi-schema/flat-union-base-star.exit b/tests/qapi-schema/args-any.exit similarity index 100% rename from tests/qapi-schema/flat-union-base-star.exit rename to tests/qapi-schema/args-any.exit diff --git a/tests/qapi-schema/args-any.json b/tests/qapi-schema/args-any.json new file mode 100644 index 0000000000..58fe5e470e --- /dev/null +++ b/tests/qapi-schema/args-any.json @@ -0,0 +1,2 @@ +# we do not allow an 'any' argument +{ 'command': 'oops', 'data': 'any' } diff --git a/tests/qapi-schema/flat-union-base-star.out b/tests/qapi-schema/args-any.out similarity index 100% rename from tests/qapi-schema/flat-union-base-star.out rename to tests/qapi-schema/args-any.out diff --git a/tests/qapi-schema/args-member-array.out b/tests/qapi-schema/args-member-array.out index c39fa25484..b3b92dfc8e 100644 --- a/tests/qapi-schema/args-member-array.out +++ b/tests/qapi-schema/args-member-array.out @@ -1,5 +1,9 @@ -[OrderedDict([('enum', 'abc'), ('data', ['a', 'b', 'c'])]), - OrderedDict([('struct', 'def'), ('data', OrderedDict([('array', ['abc'])]))]), - OrderedDict([('command', 'okay'), ('data', OrderedDict([('member1', ['int']), ('member2', ['def'])]))])] -[{'enum_name': 'abc', 'enum_values': ['a', 'b', 'c']}] -[OrderedDict([('struct', 'def'), ('data', OrderedDict([('array', ['abc'])]))])] +object :empty +object :obj-okay-arg + member member1: intList optional=False + member member2: defList optional=False +enum abc ['a', 'b', 'c'] +object def + member array: abcList optional=False +command okay :obj-okay-arg -> None + gen=True success_response=True diff --git a/tests/qapi-schema/comments.out b/tests/qapi-schema/comments.out index 4ce3dcf12f..9e2c656fa0 100644 --- a/tests/qapi-schema/comments.out +++ b/tests/qapi-schema/comments.out @@ -1,3 +1,2 @@ -[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])] -[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}] -[] +object :empty +enum Status ['good', 'bad', 'ugly'] diff --git a/tests/qapi-schema/empty.out b/tests/qapi-schema/empty.out index b7f89a45c6..272b1616f4 100644 --- a/tests/qapi-schema/empty.out +++ b/tests/qapi-schema/empty.out @@ -1,3 +1 @@ -[] -[] -[] +object :empty diff --git a/tests/qapi-schema/enum-empty.out b/tests/qapi-schema/enum-empty.out index 3b75c1613c..a449d455fb 100644 --- a/tests/qapi-schema/enum-empty.out +++ b/tests/qapi-schema/enum-empty.out @@ -1,3 +1,2 @@ -[OrderedDict([('enum', 'MyEnum'), ('data', [])])] -[{'enum_name': 'MyEnum', 'enum_values': []}] -[] +object :empty +enum MyEnum [] diff --git a/tests/qapi-schema/event-case.out b/tests/qapi-schema/event-case.out index 3764bc781d..cdfd264f9c 100644 --- a/tests/qapi-schema/event-case.out +++ b/tests/qapi-schema/event-case.out @@ -1,3 +1,2 @@ -[OrderedDict([('event', 'oops')])] -[] -[] +object :empty +event oops None diff --git a/tests/qapi-schema/flat-union-base-any.err b/tests/qapi-schema/flat-union-base-any.err new file mode 100644 index 0000000000..ad4d629e75 --- /dev/null +++ b/tests/qapi-schema/flat-union-base-any.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-base-any.json:8: Base 'any' is not a valid struct diff --git a/tests/qapi-schema/type-bypass-no-gen.exit b/tests/qapi-schema/flat-union-base-any.exit similarity index 100% rename from tests/qapi-schema/type-bypass-no-gen.exit rename to tests/qapi-schema/flat-union-base-any.exit diff --git a/tests/qapi-schema/flat-union-base-star.json b/tests/qapi-schema/flat-union-base-any.json similarity index 95% rename from tests/qapi-schema/flat-union-base-star.json rename to tests/qapi-schema/flat-union-base-any.json index 5099439a9d..fe66b713ef 100644 --- a/tests/qapi-schema/flat-union-base-star.json +++ b/tests/qapi-schema/flat-union-base-any.json @@ -6,7 +6,7 @@ { 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', - 'base': '**', + 'base': 'any', 'discriminator': 'enum1', 'data': { 'value1': 'TestTypeA', 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/type-bypass-no-gen.out b/tests/qapi-schema/flat-union-base-any.out similarity index 100% rename from tests/qapi-schema/type-bypass-no-gen.out rename to tests/qapi-schema/flat-union-base-any.out diff --git a/tests/qapi-schema/flat-union-base-star.err b/tests/qapi-schema/flat-union-base-star.err deleted file mode 100644 index b7748f08bf..0000000000 --- a/tests/qapi-schema/flat-union-base-star.err +++ /dev/null @@ -1 +0,0 @@ -tests/qapi-schema/flat-union-base-star.json:8: Base '**' is not a valid struct diff --git a/tests/qapi-schema/flat-union-reverse-define.out b/tests/qapi-schema/flat-union-reverse-define.out index 1ed7b8a519..a5a9134e7e 100644 --- a/tests/qapi-schema/flat-union-reverse-define.out +++ b/tests/qapi-schema/flat-union-reverse-define.out @@ -1,9 +1,13 @@ -[OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'TestTypeA'), ('value2', 'TestTypeB')]))]), - OrderedDict([('struct', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), - OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), - OrderedDict([('struct', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('struct', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] -[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}] -[OrderedDict([('struct', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), - OrderedDict([('struct', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('struct', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] +object :empty +object TestBase + member enum1: TestEnum optional=False +enum TestEnum ['value1', 'value2'] +object TestTypeA + member string: str optional=False +object TestTypeB + member integer: int optional=False +object TestUnion + base TestBase + tag enum1 + case value1: TestTypeA + case value2: TestTypeB diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out index 402843081b..f4542b12d8 100644 --- a/tests/qapi-schema/ident-with-escape.out +++ b/tests/qapi-schema/ident-with-escape.out @@ -1,3 +1,5 @@ -[OrderedDict([('command', 'fooA'), ('data', OrderedDict([('bar1', 'str')]))])] -[] -[] +object :empty +object :obj-fooA-arg + member bar1: str optional=False +command fooA :obj-fooA-arg -> None + gen=True success_response=True diff --git a/tests/qapi-schema/include-relpath.out b/tests/qapi-schema/include-relpath.out index 4ce3dcf12f..9e2c656fa0 100644 --- a/tests/qapi-schema/include-relpath.out +++ b/tests/qapi-schema/include-relpath.out @@ -1,3 +1,2 @@ -[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])] -[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}] -[] +object :empty +enum Status ['good', 'bad', 'ugly'] diff --git a/tests/qapi-schema/include-repetition.out b/tests/qapi-schema/include-repetition.out index 4ce3dcf12f..9e2c656fa0 100644 --- a/tests/qapi-schema/include-repetition.out +++ b/tests/qapi-schema/include-repetition.out @@ -1,3 +1,2 @@ -[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])] -[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}] -[] +object :empty +enum Status ['good', 'bad', 'ugly'] diff --git a/tests/qapi-schema/include-simple.out b/tests/qapi-schema/include-simple.out index 4ce3dcf12f..9e2c656fa0 100644 --- a/tests/qapi-schema/include-simple.out +++ b/tests/qapi-schema/include-simple.out @@ -1,3 +1,2 @@ -[OrderedDict([('enum', 'Status'), ('data', ['good', 'bad', 'ugly'])])] -[{'enum_name': 'Status', 'enum_values': ['good', 'bad', 'ugly']}] -[] +object :empty +enum Status ['good', 'bad', 'ugly'] diff --git a/tests/qapi-schema/indented-expr.out b/tests/qapi-schema/indented-expr.out index b5ce9151bc..226d300798 100644 --- a/tests/qapi-schema/indented-expr.out +++ b/tests/qapi-schema/indented-expr.out @@ -1,3 +1,5 @@ -[OrderedDict([('command', 'eins')]), OrderedDict([('command', 'zwei')])] -[] -[] +object :empty +command eins None -> None + gen=True success_response=True +command zwei None -> None + gen=True success_response=True diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 677190c1ef..6897a6ea39 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -44,8 +44,6 @@ 'data': { 'value1' : 'UserDefA', 'value2' : 'UserDefB', 'value3' : 'UserDefB' } } -# FIXME generated struct UserDefFlatUnion has members for direct base -# UserDefUnionBase, but lacks members for indirect base UserDefZero { 'struct': 'UserDefUnionBase', 'base': 'UserDefZero', @@ -62,7 +60,6 @@ { 'alternate': 'UserDefAlternate', 'data': { 'uda': 'UserDefA', 's': 'str', 'i': 'int' } } -# FIXME only a declaration of visit_type_UserDefAlternateKind() generated { 'struct': 'UserDefC', 'data': { 'string1': 'str', 'string2': 'str' } } @@ -81,7 +78,8 @@ 'number': ['number'], 'boolean': ['bool'], 'string': ['str'], - 'sizes': ['size'] } } + 'sizes': ['size'], + 'any': ['any'] } } # testing commands { 'command': 'user_def_cmd', 'data': {} } @@ -91,6 +89,8 @@ 'returns': 'UserDefTwo' } { 'command': 'user_def_cmd3', 'data': {'a': 'int', '*b': 'int' }, 'returns': 'int' } +# note: command name 'guest-sync' chosen to avoid "cannot use built-in" error +{ 'command': 'guest-sync', 'data': { 'arg': 'any' }, 'returns': 'any' } # For testing integer range flattening in opts-visitor. The following schema # corresponds to the option format: @@ -134,9 +134,6 @@ { 'alternate': '__org.qemu_x-Alt', 'data': { '__org.qemu_x-branch': 'str', 'b': '__org.qemu_x-Base' } } { 'event': '__ORG.QEMU_X-EVENT', 'data': '__org.qemu_x-Struct' } -# FIXME generated qapi_event_send___org_qemu_x_event() has only a -# parameter for data's member __org_qemu_x_member2, none for its base -# __org.qemu_x-Base's member __org_qemu_x_member1 { 'command': '__org.qemu_x-command', 'data': { 'a': ['__org.qemu_x-Enum'], 'b': ['__org.qemu_x-Struct'], 'c': '__org.qemu_x-Union2', 'd': '__org.qemu_x-Alt' }, diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 32f17c5278..1f6e858def 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -1,57 +1,169 @@ -[OrderedDict([('enum', 'EnumOne'), ('data', ['value1', 'value2', 'value3'])]), - OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), - OrderedDict([('enum', 'QEnumTwo'), ('prefix', 'QENUM_TWO'), ('data', ['value1', 'value2'])]), - OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('struct', 'UserDefTwoDictDict'), ('data', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]), - OrderedDict([('struct', 'UserDefTwoDict'), ('data', OrderedDict([('string1', 'str'), ('dict2', 'UserDefTwoDictDict'), ('*dict3', 'UserDefTwoDictDict')]))]), - OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', 'UserDefTwoDict')]))]), - OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), - OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('intb', 'int')]))]), - OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]), - OrderedDict([('struct', 'UserDefUnionBase'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), - OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]), - OrderedDict([('alternate', 'UserDefAlternate'), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), - OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), - OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('integer', ['int']), ('s8', ['int8']), ('s16', ['int16']), ('s32', ['int32']), ('s64', ['int64']), ('u8', ['uint8']), ('u16', ['uint16']), ('u32', ['uint32']), ('u64', ['uint64']), ('number', ['number']), ('boolean', ['bool']), ('string', ['str']), ('sizes', ['size'])]))]), - OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]), - OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]), - OrderedDict([('command', 'user_def_cmd2'), ('data', OrderedDict([('ud1a', 'UserDefOne'), ('*ud1b', 'UserDefOne')])), ('returns', 'UserDefTwo')]), - OrderedDict([('command', 'user_def_cmd3'), ('data', OrderedDict([('a', 'int'), ('*b', 'int')])), ('returns', 'int')]), - OrderedDict([('struct', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]), - OrderedDict([('struct', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))]), - OrderedDict([('event', 'EVENT_A')]), - OrderedDict([('event', 'EVENT_B'), ('data', OrderedDict())]), - OrderedDict([('event', 'EVENT_C'), ('data', OrderedDict([('*a', 'int'), ('*b', 'UserDefOne'), ('c', 'str')]))]), - OrderedDict([('event', 'EVENT_D'), ('data', OrderedDict([('a', 'EventStructOne'), ('b', 'str'), ('*c', 'str'), ('*enum3', 'EnumOne')]))]), - OrderedDict([('enum', '__org.qemu_x-Enum'), ('data', ['__org.qemu_x-value'])]), - OrderedDict([('struct', '__org.qemu_x-Base'), ('data', OrderedDict([('__org.qemu_x-member1', '__org.qemu_x-Enum')]))]), - OrderedDict([('struct', '__org.qemu_x-Struct'), ('base', '__org.qemu_x-Base'), ('data', OrderedDict([('__org.qemu_x-member2', 'str')]))]), - OrderedDict([('union', '__org.qemu_x-Union1'), ('data', OrderedDict([('__org.qemu_x-branch', 'str')]))]), - OrderedDict([('struct', '__org.qemu_x-Struct2'), ('data', OrderedDict([('array', ['__org.qemu_x-Union1'])]))]), - OrderedDict([('union', '__org.qemu_x-Union2'), ('base', '__org.qemu_x-Base'), ('discriminator', '__org.qemu_x-member1'), ('data', OrderedDict([('__org.qemu_x-value', '__org.qemu_x-Struct2')]))]), - OrderedDict([('alternate', '__org.qemu_x-Alt'), ('data', OrderedDict([('__org.qemu_x-branch', 'str'), ('b', '__org.qemu_x-Base')]))]), - OrderedDict([('event', '__ORG.QEMU_X-EVENT'), ('data', '__org.qemu_x-Struct')]), - OrderedDict([('command', '__org.qemu_x-command'), ('data', OrderedDict([('a', ['__org.qemu_x-Enum']), ('b', ['__org.qemu_x-Struct']), ('c', '__org.qemu_x-Union2'), ('d', '__org.qemu_x-Alt')])), ('returns', '__org.qemu_x-Union1')])] -[{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']}, - {'enum_name': 'QEnumTwo', 'enum_values': ['value1', 'value2']}, - {'enum_name': '__org.qemu_x-Enum', 'enum_values': ['__org.qemu_x-value']}, - {'enum_name': 'UserDefAlternateKind', 'enum_values': None}, - {'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None}, - {'enum_name': '__org.qemu_x-Union1Kind', 'enum_values': None}, - {'enum_name': '__org.qemu_x-AltKind', 'enum_values': None}] -[OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('struct', 'UserDefTwoDictDict'), ('data', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]), - OrderedDict([('struct', 'UserDefTwoDict'), ('data', OrderedDict([('string1', 'str'), ('dict2', 'UserDefTwoDictDict'), ('*dict3', 'UserDefTwoDictDict')]))]), - OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', 'UserDefTwoDict')]))]), - OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), - OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('intb', 'int')]))]), - OrderedDict([('struct', 'UserDefUnionBase'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), - OrderedDict([('struct', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]), - OrderedDict([('struct', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))]), - OrderedDict([('struct', '__org.qemu_x-Base'), ('data', OrderedDict([('__org.qemu_x-member1', '__org.qemu_x-Enum')]))]), - OrderedDict([('struct', '__org.qemu_x-Struct'), ('base', '__org.qemu_x-Base'), ('data', OrderedDict([('__org.qemu_x-member2', 'str')]))]), - OrderedDict([('struct', '__org.qemu_x-Struct2'), ('data', OrderedDict([('array', ['__org.qemu_x-Union1'])]))])] +object :empty +object :obj-EVENT_C-arg + member a: int optional=True + member b: UserDefOne optional=True + member c: str optional=False +object :obj-EVENT_D-arg + member a: EventStructOne optional=False + member b: str optional=False + member c: str optional=True + member enum3: EnumOne optional=True +object :obj-__org.qemu_x-command-arg + member a: __org.qemu_x-EnumList optional=False + member b: __org.qemu_x-StructList optional=False + member c: __org.qemu_x-Union2 optional=False + member d: __org.qemu_x-Alt optional=False +object :obj-anyList-wrapper + member data: anyList optional=False +object :obj-boolList-wrapper + member data: boolList optional=False +object :obj-guest-sync-arg + member arg: any optional=False +object :obj-int16List-wrapper + member data: int16List optional=False +object :obj-int32List-wrapper + member data: int32List optional=False +object :obj-int64List-wrapper + member data: int64List optional=False +object :obj-int8List-wrapper + member data: int8List optional=False +object :obj-intList-wrapper + member data: intList optional=False +object :obj-numberList-wrapper + member data: numberList optional=False +object :obj-sizeList-wrapper + member data: sizeList optional=False +object :obj-str-wrapper + member data: str optional=False +object :obj-strList-wrapper + member data: strList optional=False +object :obj-uint16List-wrapper + member data: uint16List optional=False +object :obj-uint32List-wrapper + member data: uint32List optional=False +object :obj-uint64List-wrapper + member data: uint64List optional=False +object :obj-uint8List-wrapper + member data: uint8List optional=False +object :obj-user_def_cmd1-arg + member ud1a: UserDefOne optional=False +object :obj-user_def_cmd2-arg + member ud1a: UserDefOne optional=False + member ud1b: UserDefOne optional=True +object :obj-user_def_cmd3-arg + member a: int optional=False + member b: int optional=True +event EVENT_A None +event EVENT_B None +event EVENT_C :obj-EVENT_C-arg +event EVENT_D :obj-EVENT_D-arg +enum EnumOne ['value1', 'value2', 'value3'] +object EventStructOne + member struct1: UserDefOne optional=False + member string: str optional=False + member enum2: EnumOne optional=True +object NestedEnumsOne + member enum1: EnumOne optional=False + member enum2: EnumOne optional=True + member enum3: EnumOne optional=False + member enum4: EnumOne optional=True +enum QEnumTwo ['value1', 'value2'] + prefix QENUM_TWO +object UserDefA + member boolean: bool optional=False +alternate UserDefAlternate + case uda: UserDefA + case s: str + case i: int +enum UserDefAlternateKind ['uda', 's', 'i'] +object UserDefB + member intb: int optional=False +object UserDefC + member string1: str optional=False + member string2: str optional=False +object UserDefFlatUnion + base UserDefUnionBase + tag enum1 + case value1: UserDefA + case value2: UserDefB + case value3: UserDefB +object UserDefFlatUnion2 + base UserDefUnionBase + tag enum1 + case value1: UserDefC + case value2: UserDefB + case value3: UserDefA +object UserDefNativeListUnion + case integer: :obj-intList-wrapper + case s8: :obj-int8List-wrapper + case s16: :obj-int16List-wrapper + case s32: :obj-int32List-wrapper + case s64: :obj-int64List-wrapper + case u8: :obj-uint8List-wrapper + case u16: :obj-uint16List-wrapper + case u32: :obj-uint32List-wrapper + case u64: :obj-uint64List-wrapper + case number: :obj-numberList-wrapper + case boolean: :obj-boolList-wrapper + case string: :obj-strList-wrapper + case sizes: :obj-sizeList-wrapper + case any: :obj-anyList-wrapper +enum UserDefNativeListUnionKind ['integer', 's8', 's16', 's32', 's64', 'u8', 'u16', 'u32', 'u64', 'number', 'boolean', 'string', 'sizes', 'any'] +object UserDefOne + base UserDefZero + member string: str optional=False + member enum1: EnumOne optional=True +object UserDefOptions + member i64: intList optional=True + member u64: uint64List optional=True + member u16: uint16List optional=True + member i64x: int optional=True + member u64x: uint64 optional=True +object UserDefTwo + member string0: str optional=False + member dict1: UserDefTwoDict optional=False +object UserDefTwoDict + member string1: str optional=False + member dict2: UserDefTwoDictDict optional=False + member dict3: UserDefTwoDictDict optional=True +object UserDefTwoDictDict + member userdef: UserDefOne optional=False + member string: str optional=False +object UserDefUnionBase + base UserDefZero + member string: str optional=False + member enum1: EnumOne optional=False +object UserDefZero + member integer: int optional=False +event __ORG.QEMU_X-EVENT __org.qemu_x-Struct +alternate __org.qemu_x-Alt + case __org.qemu_x-branch: str + case b: __org.qemu_x-Base +enum __org.qemu_x-AltKind ['__org.qemu_x-branch', 'b'] +object __org.qemu_x-Base + member __org.qemu_x-member1: __org.qemu_x-Enum optional=False +enum __org.qemu_x-Enum ['__org.qemu_x-value'] +object __org.qemu_x-Struct + base __org.qemu_x-Base + member __org.qemu_x-member2: str optional=False +object __org.qemu_x-Struct2 + member array: __org.qemu_x-Union1List optional=False +object __org.qemu_x-Union1 + case __org.qemu_x-branch: :obj-str-wrapper +enum __org.qemu_x-Union1Kind ['__org.qemu_x-branch'] +object __org.qemu_x-Union2 + base __org.qemu_x-Base + tag __org.qemu_x-member1 + case __org.qemu_x-value: __org.qemu_x-Struct2 +command __org.qemu_x-command :obj-__org.qemu_x-command-arg -> __org.qemu_x-Union1 + gen=True success_response=True +command guest-sync :obj-guest-sync-arg -> any + gen=True success_response=True +command user_def_cmd None -> None + gen=True success_response=True +command user_def_cmd1 :obj-user_def_cmd1-arg -> None + gen=True success_response=True +command user_def_cmd2 :obj-user_def_cmd2-arg -> UserDefTwo + gen=True success_response=True +command user_def_cmd3 :obj-user_def_cmd3-arg -> int + gen=True success_response=True diff --git a/tests/qapi-schema/returns-int.out b/tests/qapi-schema/returns-int.out index 70b3ac5e6f..a2da259be4 100644 --- a/tests/qapi-schema/returns-int.out +++ b/tests/qapi-schema/returns-int.out @@ -1,3 +1,3 @@ -[OrderedDict([('command', 'guest-get-time'), ('returns', 'int')])] -[] -[] +object :empty +command guest-get-time None -> int + gen=True success_response=True diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 634ef2d00a..649677e017 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -15,11 +15,42 @@ from pprint import pprint import os import sys -try: - exprs = parse_schema(sys.argv[1]) -except SystemExit: - raise -pprint(exprs) -pprint(enum_types) -pprint(struct_types) +class QAPISchemaTestVisitor(QAPISchemaVisitor): + def visit_enum_type(self, name, info, values, prefix): + print 'enum %s %s' % (name, values) + if prefix: + print ' prefix %s' % prefix + + def visit_object_type(self, name, info, base, members, variants): + print 'object %s' % name + if base: + print ' base %s' % base.name + for m in members: + print ' member %s: %s optional=%s' % \ + (m.name, m.type.name, m.optional) + self._print_variants(variants) + + def visit_alternate_type(self, name, info, variants): + print 'alternate %s' % name + self._print_variants(variants) + + def visit_command(self, name, info, arg_type, ret_type, + gen, success_response): + print 'command %s %s -> %s' % \ + (name, arg_type and arg_type.name, ret_type and ret_type.name) + print ' gen=%s success_response=%s' % (gen, success_response) + + def visit_event(self, name, info, arg_type): + print 'event %s %s' % (name, arg_type and arg_type.name) + + @staticmethod + def _print_variants(variants): + if variants: + if variants.tag_name: + print ' tag %s' % variants.tag_name + for v in variants.variants: + print ' case %s: %s' % (v.name, v.type.name) + +schema = QAPISchema(sys.argv[1]) +schema.visit(QAPISchemaTestVisitor()) diff --git a/tests/qapi-schema/type-bypass-no-gen.err b/tests/qapi-schema/type-bypass-no-gen.err deleted file mode 100644 index 20cef0a8a7..0000000000 --- a/tests/qapi-schema/type-bypass-no-gen.err +++ /dev/null @@ -1 +0,0 @@ -tests/qapi-schema/type-bypass-no-gen.json:2: Member 'arg' of 'data' for command 'unsafe' uses '**' but did not request 'gen':false diff --git a/tests/qapi-schema/type-bypass-no-gen.json b/tests/qapi-schema/type-bypass-no-gen.json deleted file mode 100644 index 4feae3719c..0000000000 --- a/tests/qapi-schema/type-bypass-no-gen.json +++ /dev/null @@ -1,2 +0,0 @@ -# type bypass only works with 'gen':false -{ 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**' } diff --git a/tests/qapi-schema/type-bypass.err b/tests/qapi-schema/type-bypass.err deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/qapi-schema/type-bypass.exit b/tests/qapi-schema/type-bypass.exit deleted file mode 100644 index 573541ac97..0000000000 --- a/tests/qapi-schema/type-bypass.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/tests/qapi-schema/type-bypass.json b/tests/qapi-schema/type-bypass.json deleted file mode 100644 index 48b2137833..0000000000 --- a/tests/qapi-schema/type-bypass.json +++ /dev/null @@ -1,2 +0,0 @@ -# Use of 'gen':false allows bypassing type system -{ 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**', 'gen': false } diff --git a/tests/qapi-schema/type-bypass.out b/tests/qapi-schema/type-bypass.out deleted file mode 100644 index eaf20f8344..0000000000 --- a/tests/qapi-schema/type-bypass.out +++ /dev/null @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'unsafe'), ('data', OrderedDict([('arg', '**')])), ('returns', '**'), ('gen', False)])] -[] -[] diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c index 9918f23062..8d5249e7e4 100644 --- a/tests/test-qmp-commands.c +++ b/tests/test-qmp-commands.c @@ -51,6 +51,11 @@ int64_t qmp_user_def_cmd3(int64_t a, bool has_b, int64_t b, Error **errp) return a + (has_b ? b : 0); } +QObject *qmp_guest_sync(QObject *arg, Error **errp) +{ + return arg; +} + __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a, __org_qemu_x_StructList *b, __org_qemu_x_Union2 *c, diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c index 68f855bdf3..53a769388c 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -19,6 +19,9 @@ #include "test-qapi-types.h" #include "test-qapi-visit.h" #include "qapi/qmp/types.h" +#include "test-qmp-introspect.h" +#include "qmp-introspect.h" +#include "qapi-visit.h" typedef struct TestInputVisitorData { QObject *obj; @@ -62,6 +65,30 @@ Visitor *validate_test_init(TestInputVisitorData *data, return v; } +/* similar to validate_test_init(), but does not expect a string + * literal/format json_string argument and so can be used for + * programatically generated strings (and we can't pass in programatically + * generated strings via %s format parameters since qobject_from_jsonv() + * will wrap those in double-quotes and treat the entire object as a + * string) + */ +static Visitor *validate_test_init_raw(TestInputVisitorData *data, + const char *json_string) +{ + Visitor *v; + + data->obj = qobject_from_json(json_string); + g_assert(data->obj != NULL); + + data->qiv = qmp_input_visitor_new_strict(data->obj); + g_assert(data->qiv != NULL); + + v = qmp_input_get_visitor(data->qiv); + g_assert(v != NULL); + + return v; +} + typedef struct TestStruct { int64_t integer; @@ -167,9 +194,9 @@ static void test_validate_union_flat(TestInputVisitorData *data, v = validate_test_init(data, "{ 'enum1': 'value1', " + "'integer': 41, " "'string': 'str', " "'boolean': true }"); - /* TODO when generator bug is fixed, add 'integer': 41 */ visit_type_UserDefFlatUnion(v, &tmp, NULL, &err); g_assert(!err); @@ -272,7 +299,7 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data, Visitor *v; /* test situation where discriminator field ('enum1' here) is missing */ - v = validate_test_init(data, "{ 'string': 'c', 'string1': 'd', 'string2': 'e' }"); + v = validate_test_init(data, "{ 'integer': 42, 'string': 'c', 'string1': 'd', 'string2': 'e' }"); visit_type_UserDefFlatUnion2(v, &tmp, NULL, &err); g_assert(err); @@ -293,6 +320,32 @@ static void test_validate_fail_alternate(TestInputVisitorData *data, qapi_free_UserDefAlternate(tmp); } +static void do_test_validate_qmp_introspect(TestInputVisitorData *data, + const char *schema_json) +{ + SchemaInfoList *schema = NULL; + Error *err = NULL; + Visitor *v; + + v = validate_test_init_raw(data, schema_json); + + visit_type_SchemaInfoList(v, &schema, NULL, &err); + if (err) { + fprintf(stderr, "%s", error_get_pretty(err)); + } + g_assert(!err); + g_assert(schema); + + qapi_free_SchemaInfoList(schema); +} + +static void test_validate_qmp_introspect(TestInputVisitorData *data, + const void *unused) +{ + do_test_validate_qmp_introspect(data, test_qmp_schema_json); + do_test_validate_qmp_introspect(data, qmp_schema_json); +} + static void validate_test_add(const char *testpath, TestInputVisitorData *data, void (*test_func)(TestInputVisitorData *data, const void *user_data)) @@ -333,6 +386,8 @@ int main(int argc, char **argv) &testdata, test_validate_fail_alternate); validate_test_add("/visitor/input-strict/fail/union-native-list", &testdata, test_validate_fail_union_native_list); + validate_test_add("/visitor/input-strict/pass/qmp-introspect", + &testdata, test_validate_qmp_introspect); g_test_run(); diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index a5cfefae8b..61715b3725 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -298,6 +298,49 @@ static void test_visitor_in_list(TestInputVisitorData *data, qapi_free_UserDefOneList(head); } +static void test_visitor_in_any(TestInputVisitorData *data, + const void *unused) +{ + QObject *res = NULL; + Error *err = NULL; + Visitor *v; + QInt *qint; + QBool *qbool; + QString *qstring; + QDict *qdict; + QObject *qobj; + + v = visitor_input_test_init(data, "-42"); + visit_type_any(v, &res, NULL, &err); + g_assert(!err); + qint = qobject_to_qint(res); + g_assert(qint); + g_assert_cmpint(qint_get_int(qint), ==, -42); + qobject_decref(res); + + v = visitor_input_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }"); + visit_type_any(v, &res, NULL, &err); + g_assert(!err); + qdict = qobject_to_qdict(res); + g_assert(qdict && qdict_size(qdict) == 3); + qobj = qdict_get(qdict, "integer"); + g_assert(qobj); + qint = qobject_to_qint(qobj); + g_assert(qint); + g_assert_cmpint(qint_get_int(qint), ==, -42); + qobj = qdict_get(qdict, "boolean"); + g_assert(qobj); + qbool = qobject_to_qbool(qobj); + g_assert(qbool); + g_assert(qbool_get_bool(qbool) == true); + qobj = qdict_get(qdict, "string"); + g_assert(qobj); + qstring = qobject_to_qstring(qobj); + g_assert(qstring); + g_assert_cmpstr(qstring_get_str(qstring), ==, "foo"); + qobject_decref(res); +} + static void test_visitor_in_union_flat(TestInputVisitorData *data, const void *unused) { @@ -307,15 +350,15 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data, v = visitor_input_test_init(data, "{ 'enum1': 'value1', " + "'integer': 41, " "'string': 'str', " "'boolean': true }"); - /* TODO when generator bug is fixed, add 'integer': 41 */ visit_type_UserDefFlatUnion(v, &tmp, NULL, &err); g_assert(err == NULL); g_assert_cmpint(tmp->enum1, ==, ENUM_ONE_VALUE1); g_assert_cmpstr(tmp->string, ==, "str"); - /* TODO g_assert_cmpint(tmp->integer, ==, 41); */ + g_assert_cmpint(tmp->integer, ==, 41); g_assert_cmpint(tmp->value1->boolean, ==, true); qapi_free_UserDefFlatUnion(tmp); } @@ -669,6 +712,8 @@ int main(int argc, char **argv) &in_visitor_data, test_visitor_in_struct_nested); input_visitor_test_add("/visitor/input/list", &in_visitor_data, test_visitor_in_list); + input_visitor_test_add("/visitor/input/any", + &in_visitor_data, test_visitor_in_any); input_visitor_test_add("/visitor/input/union-flat", &in_visitor_data, test_visitor_in_union_flat); input_visitor_test_add("/visitor/input/alternate", diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c index 338ada0253..c84002e2f2 100644 --- a/tests/test-qmp-output-visitor.c +++ b/tests/test-qmp-output-visitor.c @@ -428,6 +428,57 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data, qapi_free_UserDefTwoList(head); } +static void test_visitor_out_any(TestOutputVisitorData *data, + const void *unused) +{ + QObject *qobj; + Error *err = NULL; + QInt *qint; + QBool *qbool; + QString *qstring; + QDict *qdict; + QObject *obj; + + qobj = QOBJECT(qint_from_int(-42)); + visit_type_any(data->ov, &qobj, NULL, &err); + g_assert(!err); + obj = qmp_output_get_qobject(data->qov); + g_assert(obj != NULL); + g_assert(qobject_type(obj) == QTYPE_QINT); + g_assert_cmpint(qint_get_int(qobject_to_qint(obj)), ==, -42); + qobject_decref(obj); + qobject_decref(qobj); + + qdict = qdict_new(); + qdict_put(qdict, "integer", qint_from_int(-42)); + qdict_put(qdict, "boolean", qbool_from_bool(true)); + qdict_put(qdict, "string", qstring_from_str("foo")); + qobj = QOBJECT(qdict); + visit_type_any(data->ov, &qobj, NULL, &err); + g_assert(!err); + obj = qmp_output_get_qobject(data->qov); + g_assert(obj != NULL); + qdict = qobject_to_qdict(obj); + g_assert(qdict); + qobj = qdict_get(qdict, "integer"); + g_assert(qobj); + qint = qobject_to_qint(qobj); + g_assert(qint); + g_assert_cmpint(qint_get_int(qint), ==, -42); + qobj = qdict_get(qdict, "boolean"); + g_assert(qobj); + qbool = qobject_to_qbool(qobj); + g_assert(qbool); + g_assert(qbool_get_bool(qbool) == true); + qobj = qdict_get(qdict, "string"); + g_assert(qobj); + qstring = qobject_to_qstring(qobj); + g_assert(qstring); + g_assert_cmpstr(qstring_get_str(qstring), ==, "foo"); + qobject_decref(obj); + qobject_decref(qobj); +} + static void test_visitor_out_union_flat(TestOutputVisitorData *data, const void *unused) { @@ -485,7 +536,8 @@ static void test_visitor_out_empty(TestOutputVisitorData *data, QObject *arg; arg = qmp_output_get_qobject(data->qov); - g_assert(!arg); + g_assert(qobject_type(arg) == QTYPE_QNULL); + qobject_decref(arg); } static void init_native_list(UserDefNativeListUnion *cvalue) @@ -832,6 +884,8 @@ int main(int argc, char **argv) &out_visitor_data, test_visitor_out_struct_errors); output_visitor_test_add("/visitor/output/list", &out_visitor_data, test_visitor_out_list); + output_visitor_test_add("/visitor/output/any", + &out_visitor_data, test_visitor_out_any); output_visitor_test_add("/visitor/output/list-qapi-free", &out_visitor_data, test_visitor_out_list_qapi_free); output_visitor_test_add("/visitor/output/union-flat",