diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2020-03-19 10:18:07 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2020-03-19 10:18:07 +0000 |
commit | f57587c7d47b35b2d9b31def3a74d81bdb5475d7 (patch) | |
tree | 39e00516810dde66ebbcefca5610b60a5c8f6219 | |
parent | 0a4833b3b4bce181834d4c736ceba78434dfd471 (diff) | |
parent | 08712fcb851034228b61f75bd922863a984a4f60 (diff) |
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2020-03-17' into staging
QAPI patches for 2020-03-17
# gpg: Signature made Tue 17 Mar 2020 20:50:54 GMT
# gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg: issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full]
# gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full]
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653
* remotes/armbru/tags/pull-qapi-2020-03-17: (30 commits)
net: Track netdevs in NetClientState rather than QemuOpt
net: Complete qapi-fication of netdev_add
qmp: constify QmpCommand and list
qapi: Mark deprecated QMP parts with feature 'deprecated'
qapi: New special feature flag "deprecated"
qapi: Replace qmp_dispatch()'s TODO comment by an explanation
qapi: Simplify how qmp_dispatch() gets the request ID
qapi: Simplify how qmp_dispatch() deals with QCO_NO_SUCCESS_RESP
qapi: Inline do_qmp_dispatch() into qmp_dispatch()
qapi: Add feature flags to struct members
qapi/schema: Call QAPIDoc.connect_member() in just one place
qapi/schema: Rename QAPISchemaObjectType{Variant,Variants}
qapi/schema: Reorder classes so related ones are together
qapi/schema: Change _make_features() to a take feature list
qapi/introspect: Factor out _make_tree()
qapi/introspect: Rename *qlit* to reduce confusion
qapi: Consistently put @features parameter right after @ifcond
qapi: Add feature flags to remaining definitions
qapi/schema: Clean up around QAPISchemaEntity.connect_doc()
tests/test-qmp-event: Check event is actually emitted
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
42 files changed, 907 insertions, 752 deletions
diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt index 59d6973e1e..1967adfa92 100644 --- a/docs/devel/qapi-code-gen.txt +++ b/docs/devel/qapi-code-gen.txt @@ -172,7 +172,8 @@ Syntax: ENUM = { 'enum': STRING, 'data': [ ENUM-VALUE, ... ], '*prefix': STRING, - '*if': COND } + '*if': COND, + '*features': FEATURES } ENUM-VALUE = STRING | { 'name': STRING, '*if': COND } @@ -207,6 +208,9 @@ the job satisfactorily. The optional 'if' member specifies a conditional. See "Configuring the schema" below for more on this. +The optional 'features' member specifies features. See "Features" +below for more on this. + === Type references and array types === @@ -230,7 +234,9 @@ Syntax: '*features': FEATURES } MEMBERS = { MEMBER, ... } MEMBER = STRING : TYPE-REF - | STRING : { 'type': TYPE-REF, '*if': COND } + | STRING : { 'type': TYPE-REF, + '*if': COND, + '*features': FEATURES } Member 'struct' names the struct type. @@ -279,12 +285,14 @@ below for more on this. Syntax: UNION = { 'union': STRING, 'data': BRANCHES, - '*if': COND } + '*if': COND, + '*features': FEATURES } | { 'union': STRING, 'data': BRANCHES, 'base': ( MEMBERS | STRING ), 'discriminator': STRING, - '*if': COND } + '*if': COND, + '*features': FEATURES } BRANCHES = { BRANCH, ... } BRANCH = STRING : TYPE-REF | STRING : { 'type': TYPE-REF, '*if': COND } @@ -391,15 +399,19 @@ is identical on the wire to: The optional 'if' member specifies a conditional. See "Configuring the schema" below for more on this. +The optional 'features' member specifies features. See "Features" +below for more on this. + === Alternate types === Syntax: ALTERNATE = { 'alternate': STRING, 'data': ALTERNATIVES, - '*if': COND } + '*if': COND, + '*features': FEATURES } ALTERNATIVES = { ALTERNATIVE, ... } - ALTERNATIVE = STRING : TYPE-REF + ALTERNATIVE = STRING : STRING | STRING : { 'type': STRING, '*if': COND } Member 'alternate' names the alternate type. @@ -441,6 +453,9 @@ following example objects: The optional 'if' member specifies a conditional. See "Configuring the schema" below for more on this. +The optional 'features' member specifies features. See "Features" +below for more on this. + === Commands === @@ -584,6 +599,9 @@ started with --preconfig. The optional 'if' member specifies a conditional. See "Configuring the schema" below for more on this. +The optional 'features' member specifies features. See "Features" +below for more on this. + === Events === @@ -595,7 +613,8 @@ Syntax: 'data': STRING, 'boxed': true, ) - '*if': COND } + '*if': COND, + '*features': FEATURES } Member 'event' names the event. This is the event name used in the Client JSON Protocol. @@ -628,6 +647,9 @@ complex type. See section "Code generated for events" for examples. The optional 'if' member specifies a conditional. See "Configuring the schema" below for more on this. +The optional 'features' member specifies features. See "Features" +below for more on this. + === Features === @@ -642,13 +664,8 @@ that previously resulted in an error). QMP clients may still need to know whether the extension is available. For this purpose, a list of features can be specified for a command or -struct type. This is exposed to the client as a list of strings, -where each string signals that this build of QEMU shows a certain -behaviour. - -Each member of the 'features' array defines a feature. It can either -be { 'name': STRING, '*if': COND }, or STRING, which is shorthand for -{ 'name': STRING }. +struct type. Each list member can either be { 'name': STRING, '*if': +COND }, or STRING, which is shorthand for { 'name': STRING }. The optional 'if' member specifies a conditional. See "Configuring the schema" below for more on this. @@ -659,6 +676,18 @@ Example: 'data': { 'number': 'int' }, 'features': [ 'allow-negative-numbers' ] } +The feature strings are exposed to clients in introspection, as +explained in section "Client JSON Protocol introspection". + +Intended use is to have each feature string signal that this build of +QEMU shows a certain behaviour. + + +==== Special features ==== + +Feature "deprecated" marks a command, event, or struct member as +deprecated. It is not supported elsewhere so far. + === Naming rules and reserved names === @@ -965,8 +994,9 @@ 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. +SchemaInfo objects have common members "name", "meta-type", +"features", 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. @@ -979,6 +1009,9 @@ not. Therefore, the SchemaInfo for types have auto-generated meaningless names. For readability, the examples in this section use meaningful type names instead. +Optional member "features" exposes the entity's feature strings as a +JSON array of strings. + To examine a type, start with a command or event using it, then follow references by name. @@ -988,9 +1021,9 @@ The SchemaInfo for a command has meta-type "command", and variant members "arg-type", "ret-type" and "allow-oob". 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". When "allow-oob" is set, it means the command supports -out-of-band execution. +passes in a success response conforms to the type named by "ret-type". +When "allow-oob" is true, it means the command supports out-of-band +execution. It defaults to false. If the command takes no arguments, "arg-type" names an object type without members. Likewise, if the command returns nothing, "ret-type" @@ -1047,6 +1080,16 @@ Example: the SchemaInfo for MyType from section Struct types { "name": "member2", "type": "int" }, { "name": "member3", "type": "str", "default": null } ] } +"features" exposes the command's feature strings as a JSON array of +strings. + +Example: the SchemaInfo for TestType from section Features: + + { "name": "TestType", "meta-type": "object", + "members": [ + { "name": "number", "type": "int" } ], + "features": ["allow-negative-numbers"] } + "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 diff --git a/docs/system/deprecated.rst b/docs/system/deprecated.rst index 412b1b166d..c633fe2bef 100644 --- a/docs/system/deprecated.rst +++ b/docs/system/deprecated.rst @@ -180,27 +180,67 @@ QEMU Machine Protocol (QMP) commands Use ``blockdev-change-medium`` or ``change-vnc-password`` instead. +``blockdev-open-tray``, ``blockdev-close-tray`` argument ``device`` (since 2.8.0) +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Use argument ``id`` instead. + +``eject`` argument ``device`` (since 2.8.0) +''''''''''''''''''''''''''''''''''''''''''' + +Use argument ``id`` instead. + +``blockdev-change-medium`` argument ``device`` (since 2.8.0) +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Use argument ``id`` instead. + +``block_set_io_throttle`` argument ``device`` (since 2.8.0) +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Use argument ``id`` instead. + ``migrate_set_downtime`` and ``migrate_set_speed`` (since 2.8.0) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Use ``migrate-set-parameters`` instead. +``query-named-block-nodes`` result ``encryption_key_missing`` (since 2.10.0) +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Always false. + +``query-block`` result ``inserted.encryption_key_missing`` (since 2.10.0) +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Always false. + +``blockdev-add`` empty string argument ``backing`` (since 2.10.0) +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Use argument value ``null`` instead. + ``migrate-set-cache-size`` and ``query-migrate-cache-size`` (since 2.11.0) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Use ``migrate-set-parameters`` and ``query-migrate-parameters`` instead. +``block-commit`` arguments ``base`` and ``top`` (since 3.1.0) +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Use arguments ``base-node`` and ``top-node`` instead. + ``object-add`` option ``props`` (since 5.0) ''''''''''''''''''''''''''''''''''''''''''' Specify the properties for the object as top-level arguments instead. -``query-block`` result field ``dirty-bitmaps[i].status`` (since 4.0) -'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +``query-named-block-nodes`` and ``query-block`` result dirty-bitmaps[i].status (since 4.0) +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' The ``status`` field of the ``BlockDirtyInfo`` structure, returned by -the query-block command is deprecated. Two new boolean fields, -``recording`` and ``busy`` effectively replace it. +these commands is deprecated. Two new boolean fields, ``recording`` and +``busy`` effectively replace it. ``query-block`` result field ``dirty-bitmaps`` (Since 4.2) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''' diff --git a/include/net/net.h b/include/net/net.h index e175ba9677..094e966af9 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -98,6 +98,7 @@ struct NetClientState { unsigned rxfilter_notify_enabled:1; int vring_enable; int vnet_hdr_len; + bool is_netdev; QTAILQ_HEAD(, NetFilterState) filters; }; @@ -203,7 +204,6 @@ void net_cleanup(void); void hmp_host_net_add(Monitor *mon, const QDict *qdict); void hmp_host_net_remove(Monitor *mon, const QDict *qdict); void netdev_add(QemuOpts *opts, Error **errp); -void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp); int net_hub_id_for_client(NetClientState *nc, int *id); NetClientState *net_hub_port_find(int hub_id); diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h index 9aa426a398..5a9cf82472 100644 --- a/include/qapi/qmp/dispatch.h +++ b/include/qapi/qmp/dispatch.h @@ -39,7 +39,8 @@ typedef QTAILQ_HEAD(QmpCommandList, QmpCommand) QmpCommandList; void qmp_register_command(QmpCommandList *cmds, const char *name, QmpCommandFunc *fn, QmpCommandOptions options); -QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name); +const QmpCommand *qmp_find_command(const QmpCommandList *cmds, + const char *name); void qmp_disable_command(QmpCommandList *cmds, const char *name); void qmp_enable_command(QmpCommandList *cmds, const char *name); @@ -47,13 +48,13 @@ bool qmp_command_is_enabled(const QmpCommand *cmd); const char *qmp_command_name(const QmpCommand *cmd); bool qmp_has_success_response(const QmpCommand *cmd); QDict *qmp_error_response(Error *err); -QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request, +QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request, bool allow_oob); bool qmp_is_oob(const QDict *dict); -typedef void (*qmp_cmd_callback_fn)(QmpCommand *cmd, void *opaque); +typedef void (*qmp_cmd_callback_fn)(const QmpCommand *cmd, void *opaque); -void qmp_for_each_command(QmpCommandList *cmds, qmp_cmd_callback_fn fn, +void qmp_for_each_command(const QmpCommandList *cmds, qmp_cmd_callback_fn fn, void *opaque); #endif diff --git a/monitor/misc.c b/monitor/misc.c index c3bc34c099..6c45fa490f 100644 --- a/monitor/misc.c +++ b/monitor/misc.c @@ -247,8 +247,6 @@ static void monitor_init_qmp_commands(void) qmp_query_qmp_schema, QCO_ALLOW_PRECONFIG); qmp_register_command(&qmp_commands, "device_add", qmp_device_add, QCO_NO_OPTIONS); - qmp_register_command(&qmp_commands, "netdev_add", qmp_netdev_add, - QCO_NO_OPTIONS); qmp_register_command(&qmp_commands, "object-add", qmp_object_add, QCO_NO_OPTIONS); @@ -2037,13 +2035,11 @@ void netdev_del_completion(ReadLineState *rs, int nb_args, const char *str) count = qemu_find_net_clients_except(NULL, ncs, NET_CLIENT_DRIVER_NIC, MAX_QUEUE_NUM); for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) { - QemuOpts *opts; const char *name = ncs[i]->name; if (strncmp(str, name, len)) { continue; } - opts = qemu_opts_find(qemu_find_opts_err("netdev", NULL), name); - if (opts) { + if (ncs[i]->is_netdev) { readline_add_completion(rs, name); } } diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h index 3e6baba88f..8f60ccc70a 100644 --- a/monitor/monitor-internal.h +++ b/monitor/monitor-internal.h @@ -133,7 +133,7 @@ typedef struct { * qmp_capabilities succeeds, we go into command mode, and * @command becomes &qmp_commands. */ - QmpCommandList *commands; + const QmpCommandList *commands; bool capab_offered[QMP_CAPABILITY__MAX]; /* capabilities offered */ bool capab[QMP_CAPABILITY__MAX]; /* offered and accepted */ /* diff --git a/monitor/qmp-cmds-control.c b/monitor/qmp-cmds-control.c index 5cd9bb817c..8f04cfa6e6 100644 --- a/monitor/qmp-cmds-control.c +++ b/monitor/qmp-cmds-control.c @@ -101,7 +101,7 @@ VersionInfo *qmp_query_version(Error **errp) return info; } -static void query_commands_cb(QmpCommand *cmd, void *opaque) +static void query_commands_cb(const QmpCommand *cmd, void *opaque) { CommandInfoList *info, **list = opaque; @@ -1060,6 +1060,15 @@ static int net_client_init1(const void *object, bool is_netdev, Error **errp) } return -1; } + + if (is_netdev) { + NetClientState *nc; + + nc = qemu_find_netdev(netdev->id); + assert(nc); + nc->is_netdev = true; + } + return 0; } @@ -1170,36 +1179,14 @@ void netdev_add(QemuOpts *opts, Error **errp) net_client_init(opts, true, errp); } -void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp) +void qmp_netdev_add(Netdev *netdev, Error **errp) { - Error *local_err = NULL; - QemuOptsList *opts_list; - QemuOpts *opts; - - opts_list = qemu_find_opts_err("netdev", &local_err); - if (local_err) { - goto out; - } - - opts = qemu_opts_from_qdict(opts_list, qdict, &local_err); - if (local_err) { - goto out; - } - - netdev_add(opts, &local_err); - if (local_err) { - qemu_opts_del(opts); - goto out; - } - -out: - error_propagate(errp, local_err); + net_client_init1(netdev, true, errp); } void qmp_netdev_del(const char *id, Error **errp) { NetClientState *nc; - QemuOpts *opts; nc = qemu_find_netdev(id); if (!nc) { @@ -1208,14 +1195,12 @@ void qmp_netdev_del(const char *id, Error **errp) return; } - opts = qemu_opts_find(qemu_find_opts_err("netdev", NULL), id); - if (!opts) { + if (!nc->is_netdev) { error_setg(errp, "Device '%s' is not a netdev", id); return; } qemu_del_net_client(nc); - qemu_opts_del(opts); } static void netfilter_print_info(Monitor *mon, NetFilterState *nf) diff --git a/qapi/block-core.json b/qapi/block-core.json index 91586fb1fb..943df1926a 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -297,7 +297,7 @@ # # @encrypted: true if the backing device is encrypted # -# @encryption_key_missing: Deprecated; always false +# @encryption_key_missing: always false # # @detect_zeroes: detect and optimize zero writes (Since 2.1) # @@ -363,13 +363,19 @@ # @dirty-bitmaps: dirty bitmaps information (only present if node # has one or more dirty bitmaps) (Since 4.2) # +# Features: +# @deprecated: Member @encryption_key_missing is deprecated. It is +# always false. +# # Since: 0.14.0 # ## { 'struct': 'BlockDeviceInfo', 'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str', '*backing_file': 'str', 'backing_file_depth': 'int', - 'encrypted': 'bool', 'encryption_key_missing': 'bool', + 'encrypted': 'bool', + 'encryption_key_missing': { 'type': 'bool', + 'features': [ 'deprecated' ] }, 'detect_zeroes': 'BlockdevDetectZeroesOptions', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int', @@ -475,7 +481,7 @@ # # @granularity: granularity of the dirty bitmap in bytes (since 1.4) # -# @status: Deprecated in favor of @recording and @locked. (since 2.4) +# @status: current status of the dirty bitmap (since 2.4) # # @recording: true if the bitmap is recording new writes from the guest. # Replaces `active` and `disabled` statuses. (since 4.0) @@ -492,11 +498,17 @@ # @busy to be false. This bitmap cannot be used. To remove # it, use @block-dirty-bitmap-remove. (Since 4.0) # +# Features: +# @deprecated: Member @status is deprecated. Use @recording and +# @locked instead. +# # Since: 1.3 ## { 'struct': 'BlockDirtyInfo', 'data': {'*name': 'str', 'count': 'int', 'granularity': 'uint32', - 'recording': 'bool', 'busy': 'bool', 'status': 'DirtyBitmapStatus', + 'recording': 'bool', 'busy': 'bool', + 'status': { 'type': 'DirtyBitmapStatus', + 'features': [ 'deprecated' ] }, 'persistent': 'bool', '*inconsistent': 'bool' } } ## @@ -587,7 +599,6 @@ # # @dirty-bitmaps: dirty bitmaps information (only present if the # driver has one or more dirty bitmaps) (Since 2.0) -# Deprecated in 4.2; see BlockDeviceInfo instead. # # @io-status: @BlockDeviceIoStatus. Only present if the device # supports it and the VM is configured to stop on errors @@ -597,13 +608,18 @@ # @inserted: @BlockDeviceInfo describing the device if media is # present # +# Features: +# @deprecated: Member @dirty-bitmaps is deprecated. Use @inserted +# member @dirty-bitmaps instead. +# # Since: 0.14.0 ## { 'struct': 'BlockInfo', 'data': {'device': 'str', '*qdev': 'str', 'type': 'str', 'removable': 'bool', 'locked': 'bool', '*inserted': 'BlockDeviceInfo', '*tray_open': 'bool', '*io-status': 'BlockDeviceIoStatus', - '*dirty-bitmaps': ['BlockDirtyInfo'] } } + '*dirty-bitmaps': { 'type': ['BlockDirtyInfo'], + 'features': [ 'deprecated' ] } } } ## # @BlockMeasureInfo: @@ -1551,7 +1567,7 @@ # @base: Same as @base-node, except that it is a file name rather than a node # name. This must be the exact filename string that was used to open the # node; other strings, even if addressing the same file, are not -# accepted (deprecated, use @base-node instead) +# accepted # # @top-node: The node name of the backing image within the image chain # which contains the topmost data to be committed down. If @@ -1560,7 +1576,7 @@ # @top: Same as @top-node, except that it is a file name rather than a node # name. This must be the exact filename string that was used to open the # node; other strings, even if addressing the same file, are not -# accepted (deprecated, use @base-node instead) +# accepted # # @backing-file: The backing file string to write into the overlay # image of 'top'. If 'top' is the active layer, @@ -1614,6 +1630,10 @@ # list without user intervention. # Defaults to true. (Since 3.1) # +# Features: +# @deprecated: Members @base and @top are deprecated. Use @base-node +# and @top-node instead. +# # Returns: - Nothing on success # - If @device does not exist, DeviceNotFound # - Any other error returns a GenericError. @@ -1630,7 +1650,9 @@ ## { 'command': 'block-commit', 'data': { '*job-id': 'str', 'device': 'str', '*base-node': 'str', - '*base': 'str', '*top-node': 'str', '*top': 'str', + '*base': { 'type': 'str', 'features': [ 'deprecated' ] }, + '*top-node': 'str', + '*top': { 'type': 'str', 'features': [ 'deprecated' ] }, '*backing-file': 'str', '*speed': 'int', '*on-error': 'BlockdevOnError', '*filter-node-name': 'str', @@ -2296,7 +2318,7 @@ # # A set of parameters describing block throttling. # -# @device: Block device name (deprecated, use @id instead) +# @device: Block device name # # @id: The name or QOM path of the guest device (since: 2.8) # @@ -2364,10 +2386,14 @@ # # @group: throttle group name (Since 2.4) # +# Features: +# @deprecated: Member @device is deprecated. Use @id instead. +# # Since: 1.1 ## { 'struct': 'BlockIOThrottle', - 'data': { '*device': 'str', '*id': 'str', 'bps': 'int', 'bps_rd': 'int', + 'data': { '*device': { 'type': 'str', 'features': [ 'deprecated' ] }, + '*id': 'str', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int', '*bps_max': 'int', '*bps_rd_max': 'int', '*bps_wr_max': 'int', '*iops_max': 'int', diff --git a/qapi/block.json b/qapi/block.json index 97bf52b7c7..2ddbfa8306 100644 --- a/qapi/block.json +++ b/qapi/block.json @@ -90,15 +90,18 @@ ## # @eject: # -# Ejects a device from a removable drive. +# Ejects the medium from a removable drive. # -# @device: Block device name (deprecated, use @id instead) +# @device: Block device name # # @id: The name or QOM path of the guest device (since: 2.8) # # @force: If true, eject regardless of whether the drive is locked. # If not specified, the default value is false. # +# Features: +# @deprecated: Member @device is deprecated. Use @id instead. +# # Returns: - Nothing on success # - If @device is not a valid block device, DeviceNotFound # Notes: Ejecting a device with no media results in success @@ -111,7 +114,7 @@ # <- { "return": {} } ## { 'command': 'eject', - 'data': { '*device': 'str', + 'data': { '*device': { 'type': 'str', 'features': [ 'deprecated' ] }, '*id': 'str', '*force': 'bool' } } @@ -134,7 +137,7 @@ # to it # - if the guest device does not have an actual tray # -# @device: Block device name (deprecated, use @id instead) +# @device: Block device name # # @id: The name or QOM path of the guest device (since: 2.8) # @@ -143,6 +146,9 @@ # immediately); if true, the tray will be opened regardless of whether # it is locked # +# Features: +# @deprecated: Member @device is deprecated. Use @id instead. +# # Since: 2.5 # # Example: @@ -161,7 +167,7 @@ # ## { 'command': 'blockdev-open-tray', - 'data': { '*device': 'str', + 'data': { '*device': { 'type': 'str', 'features': [ 'deprecated' ] }, '*id': 'str', '*force': 'bool' } } @@ -174,10 +180,13 @@ # # If the tray was already closed before, this will be a no-op. # -# @device: Block device name (deprecated, use @id instead) +# @device: Block device name # # @id: The name or QOM path of the guest device (since: 2.8) # +# Features: +# @deprecated: Member @device is deprecated. Use @id instead. +# # Since: 2.5 # # Example: @@ -196,7 +205,7 @@ # ## { 'command': 'blockdev-close-tray', - 'data': { '*device': 'str', + 'data': { '*device': { 'type': 'str', 'features': [ 'deprecated' ] }, '*id': 'str' } } ## @@ -303,7 +312,7 @@ # combines blockdev-open-tray, blockdev-remove-medium, blockdev-insert-medium # and blockdev-close-tray). # -# @device: Block device name (deprecated, use @id instead) +# @device: Block device name # # @id: The name or QOM path of the guest device # (since: 2.8) @@ -316,6 +325,9 @@ # @read-only-mode: change the read-only mode of the device; defaults # to 'retain' # +# Features: +# @deprecated: Member @device is deprecated. Use @id instead. +# # Since: 2.5 # # Examples: @@ -350,7 +362,7 @@ # ## { 'command': 'blockdev-change-medium', - 'data': { '*device': 'str', + 'data': { '*device': { 'type': 'str', 'features': [ 'deprecated' ] }, '*id': 'str', 'filename': 'str', '*format': 'str', diff --git a/qapi/char.json b/qapi/char.json index 6907b2bfdb..daceb20f84 100644 --- a/qapi/char.json +++ b/qapi/char.json @@ -258,6 +258,7 @@ # @server: create server socket (default: true) # @wait: wait for incoming connection on server # sockets (default: false). +# Silently ignored with server: false. This use is deprecated. # @nodelay: set TCP_NODELAY socket option (default: false) # @telnet: enable telnet protocol on server # sockets (default: false) diff --git a/qapi/control.json b/qapi/control.json index 85b12fe0fb..6b816bb61f 100644 --- a/qapi/control.json +++ b/qapi/control.json @@ -174,13 +174,15 @@ # # Return information on QMP events. # +# Features: +# @deprecated: This command is deprecated, because its output doesn't +# reflect compile-time configuration. Use 'query-qmp-schema' +# instead. +# # Returns: A list of @EventInfo. # # Since: 1.2.0 # -# Note: This command is deprecated, because its output doesn't reflect -# compile-time configuration. Use query-qmp-schema instead. -# # Example: # # -> { "execute": "query-events" } @@ -198,7 +200,8 @@ # Note: This example has been shortened as the real response is too long. # ## -{ 'command': 'query-events', 'returns': ['EventInfo'] } +{ 'command': 'query-events', 'returns': ['EventInfo'], + 'features': [ 'deprecated' ] } ## # @quit: diff --git a/qapi/introspect.json b/qapi/introspect.json index 8756e7920e..b1aabd4cfd 100644 --- a/qapi/introspect.json +++ b/qapi/introspect.json @@ -89,12 +89,18 @@ # # @meta-type: the entity's meta type, inherited from @base. # +# @features: names of features associated with the entity, in no +# particular order. +# (since 4.1 for object types, 4.2 for commands, 5.0 for +# the rest) +# # Additional members depend on the value of @meta-type. # # Since: 2.5 ## { 'union': 'SchemaInfo', - 'base': { 'name': 'str', 'meta-type': 'SchemaMetaType' }, + 'base': { 'name': 'str', 'meta-type': 'SchemaMetaType', + '*features': [ 'str' ] }, 'discriminator': 'meta-type', 'data': { 'builtin': 'SchemaInfoBuiltin', @@ -174,9 +180,6 @@ # and may even differ from the order of the values of the # enum type of the @tag. # -# @features: names of features associated with the type, in no particular -# order. (since: 4.1) -# # Values of this type are JSON object on the wire. # # Since: 2.5 @@ -184,8 +187,7 @@ { 'struct': 'SchemaInfoObject', 'data': { 'members': [ 'SchemaInfoObjectMember' ], '*tag': 'str', - '*variants': [ 'SchemaInfoObjectVariant' ], - '*features': [ 'str' ] } } + '*variants': [ 'SchemaInfoObjectVariant' ] } } ## # @SchemaInfoObjectMember: @@ -204,11 +206,15 @@ # Future extension: if present and non-null, the parameter # is optional, and defaults to this value. # +# @features: names of features associated with the member, in no +# particular order. (since 5.0) +# # Since: 2.5 ## { 'struct': 'SchemaInfoObjectMember', - 'data': { 'name': 'str', 'type': 'str', '*default': 'any' } } + 'data': { 'name': 'str', 'type': 'str', '*default': 'any', # @default's type must be null or match @type + '*features': [ 'str' ] } } ## # @SchemaInfoObjectVariant: @@ -266,17 +272,13 @@ # @allow-oob: whether the command allows out-of-band execution, # defaults to false (Since: 2.12) # -# @features: names of features associated with the command, in no particular -# order. (since 4.2) -# # TODO: @success-response (currently irrelevant, because it's QGA, not QMP) # # Since: 2.5 ## { 'struct': 'SchemaInfoCommand', 'data': { 'arg-type': 'str', 'ret-type': 'str', - '*allow-oob': 'bool', - '*features': [ 'str' ] } } + '*allow-oob': 'bool' } } ## # @SchemaInfoEvent: diff --git a/qapi/machine.json b/qapi/machine.json index 6c11e3cf3a..0da3f3adc4 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -184,8 +184,11 @@ # This command causes vCPU threads to exit to userspace, which causes # a small interruption to guest CPU execution. This will have a negative # impact on realtime guests and other latency sensitive guest workloads. -# It is recommended to use @query-cpus-fast instead of this command to -# avoid the vCPU interruption. +# +# Features: +# @deprecated: This command is deprecated, because it interferes with +# the guest. Use 'query-cpus-fast' instead to avoid the vCPU +# interruption. # # Returns: a list of @CpuInfo for each virtual CPU # @@ -216,12 +219,9 @@ # ] # } # -# Notes: This interface is deprecated (since 2.12.0), and it is strongly -# recommended that you avoid using it. Use @query-cpus-fast to -# obtain information about virtual CPUs. -# ## -{ 'command': 'query-cpus', 'returns': ['CpuInfo'] } +{ 'command': 'query-cpus', 'returns': ['CpuInfo'], + 'features': [ 'deprecated' ] } ## # @CpuInfoFast: @@ -237,12 +237,14 @@ # @props: properties describing to which node/socket/core/thread # virtual CPU belongs to, provided if supported by board # -# @arch: base architecture of the cpu; deprecated since 3.0.0 in favor -# of @target +# @arch: base architecture of the cpu # # @target: the QEMU system emulation target, which determines which # additional fields will be listed (since 3.0) # +# Features: +# @deprecated: Member @arch is deprecated. Use @target instead. +# # Since: 2.12 # ## @@ -251,7 +253,8 @@ 'qom-path' : 'str', 'thread-id' : 'int', '*props' : 'CpuInstanceProperties', - 'arch' : 'CpuInfoArch', + 'arch' : { 'type': 'CpuInfoArch', + 'features': [ 'deprecated' ] }, 'target' : 'SysEmuTarget' }, 'discriminator' : 'target', 'data' : { 's390x' : 'CpuInfoS390' } } @@ -307,21 +310,22 @@ # # @id: ID of CPU to be created, valid values [0..max_cpus) # +# Features: +# @deprecated: This command is deprecated. Use `device_add` instead. +# See the `query-hotpluggable-cpus` command for details. +# # Returns: Nothing on success # # Since: 1.5 # -# Note: This command is deprecated. The `device_add` command should be -# used instead. See the `query-hotpluggable-cpus` command for -# details. -# # Example: # # -> { "execute": "cpu-add", "arguments": { "id": 2 } } # <- { "return": {} } # ## -{ 'command': 'cpu-add', 'data': {'id': 'int'} } +{ 'command': 'cpu-add', 'data': {'id': 'int'}, + 'features': [ 'deprecated' ] } ## # @MachineInfo: diff --git a/qapi/migration.json b/qapi/migration.json index 0d1c0712ca..eca2981d0a 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -1210,9 +1210,11 @@ # # @value: maximum downtime in seconds # -# Returns: nothing on success +# Features: +# @deprecated: This command is deprecated. Use +# 'migrate-set-parameters' instead. # -# Notes: This command is deprecated in favor of 'migrate-set-parameters' +# Returns: nothing on success # # Since: 0.14.0 # @@ -1222,7 +1224,8 @@ # <- { "return": {} } # ## -{ 'command': 'migrate_set_downtime', 'data': {'value': 'number'} } +{ 'command': 'migrate_set_downtime', 'data': {'value': 'number'}, + 'features': [ 'deprecated' ] } ## # @migrate_set_speed: @@ -1231,9 +1234,11 @@ # # @value: maximum speed in bytes per second. # -# Returns: nothing on success +# Features: +# @deprecated: This command is deprecated. Use +# 'migrate-set-parameters' instead. # -# Notes: This command is deprecated in favor of 'migrate-set-parameters' +# Returns: nothing on success # # Since: 0.14.0 # @@ -1243,7 +1248,8 @@ # <- { "return": {} } # ## -{ 'command': 'migrate_set_speed', 'data': {'value': 'int'} } +{ 'command': 'migrate_set_speed', 'data': {'value': 'int'}, + 'features': [ 'deprecated' ] } ## # @migrate-set-cache-size: @@ -1252,13 +1258,15 @@ # # @value: cache size in bytes # +# Features: +# @deprecated: This command is deprecated. Use +# 'migrate-set-parameters' instead. +# # The size will be rounded down to the nearest power of 2. # The cache size can be modified before and during ongoing migration # # Returns: nothing on success # -# Notes: This command is deprecated in favor of 'migrate-set-parameters' -# # Since: 1.2 # # Example: @@ -1268,16 +1276,19 @@ # <- { "return": {} } # ## -{ 'command': 'migrate-set-cache-size', 'data': {'value': 'int'} } +{ 'command': 'migrate-set-cache-size', 'data': {'value': 'int'}, + 'features': [ 'deprecated' ] } ## # @query-migrate-cache-size: # # Query migration XBZRLE cache size # -# Returns: XBZRLE cache size in bytes +# Features: +# @deprecated: This command is deprecated. Use +# 'query-migrate-parameters' instead. # -# Notes: This command is deprecated in favor of 'query-migrate-parameters' +# Returns: XBZRLE cache size in bytes # # Since: 1.2 # @@ -1287,7 +1298,8 @@ # <- { "return": 67108864 } # ## -{ 'command': 'query-migrate-cache-size', 'returns': 'int' } +{ 'command': 'query-migrate-cache-size', 'returns': 'int', + 'features': [ 'deprecated' ] } ## # @migrate: diff --git a/qapi/misc.json b/qapi/misc.json index c18fe681fb..99b90ac80b 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -872,14 +872,14 @@ # If @device is 'vnc' and @target is 'password', this is the new VNC # password to set. See change-vnc-password for additional notes. # +# Features: +# @deprecated: This command is deprecated. For changing block +# devices, use 'blockdev-change-medium' instead; for changing VNC +# parameters, use 'change-vnc-password' instead. +# # Returns: - Nothing on success. # - If @device is not a valid block device, DeviceNotFound # -# Notes: This interface is deprecated, and it is strongly recommended that you -# avoid using it. For changing block devices, use -# blockdev-change-medium; for changing VNC parameters, use -# change-vnc-password. -# # Since: 0.14.0 # # Example: @@ -900,7 +900,8 @@ # ## { 'command': 'change', - 'data': {'device': 'str', 'target': 'str', '*arg': 'str'} } + 'data': {'device': 'str', 'target': 'str', '*arg': 'str'}, + 'features': [ 'deprecated' ] } ## # @xen-set-global-dirty-log: diff --git a/qapi/net.json b/qapi/net.json index 1cb9a7d782..cebb1b52e3 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -39,18 +39,8 @@ # # Add a network backend. # -# @type: the type of network backend. Possible values are listed in -# NetClientDriver (excluding 'none' and 'nic') -# -# @id: the name of the new network backend -# # Additional arguments depend on the type. # -# 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 # # Returns: Nothing on success @@ -64,9 +54,7 @@ # <- { "return": {} } # ## -{ 'command': 'netdev_add', - 'data': {'type': 'str', 'id': 'str'}, - 'gen': false } # so we can get the additional arguments +{ 'command': 'netdev_add', 'data': 'Netdev', 'boxed': true } ## # @netdev_del: diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index bc264b3c9b..c30c7ff9e1 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -19,20 +19,13 @@ #include "sysemu/runstate.h" #include "qapi/qmp/qbool.h" -static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, +static QDict *qmp_dispatch_check_obj(QDict *dict, bool allow_oob, Error **errp) { const char *exec_key = NULL; const QDictEntry *ent; const char *arg_name; const QObject *arg_obj; - QDict *dict; - - dict = qobject_to(QDict, request); - if (!dict) { - error_setg(errp, "QMP input must be a JSON object"); - return NULL; - } for (ent = qdict_first(dict); ent; ent = qdict_next(dict, ent)) { @@ -75,19 +68,50 @@ static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, return dict; } -static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, - bool allow_oob, Error **errp) +QDict *qmp_error_response(Error *err) +{ + QDict *rsp; + + rsp = qdict_from_jsonf_nofail("{ 'error': { 'class': %s, 'desc': %s } }", + QapiErrorClass_str(error_get_class(err)), + error_get_pretty(err)); + error_free(err); + return rsp; +} + +/* + * Does @qdict look like a command to be run out-of-band? + */ +bool qmp_is_oob(const QDict *dict) +{ + return qdict_haskey(dict, "exec-oob") + && !qdict_haskey(dict, "execute"); +} + +QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request, + bool allow_oob) { - Error *local_err = NULL; + Error *err = NULL; bool oob; const char *command; - QDict *args, *dict; - QmpCommand *cmd; + QDict *args; + const QmpCommand *cmd; + QDict *dict; + QObject *id; QObject *ret = NULL; + QDict *rsp = NULL; - dict = qmp_dispatch_check_obj(request, allow_oob, errp); + dict = qobject_to(QDict, request); if (!dict) { - return NULL; + id = NULL; + error_setg(&err, "QMP input must be a JSON object"); + goto out; + } + + id = qdict_get(dict, "id"); + + if (!qmp_dispatch_check_obj(dict, allow_oob, &err)) { + goto out; } command = qdict_get_try_str(dict, "execute"); @@ -99,27 +123,27 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, } cmd = qmp_find_command(cmds, command); if (cmd == NULL) { - error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + error_set(&err, ERROR_CLASS_COMMAND_NOT_FOUND, "The command %s has not been found", command); - return NULL; + goto out; } if (!cmd->enabled) { - error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + error_set(&err, ERROR_CLASS_COMMAND_NOT_FOUND, "The command %s has been disabled for this instance", command); - return NULL; + goto out; } if (oob && !(cmd->options & QCO_ALLOW_OOB)) { - error_setg(errp, "The command %s does not support OOB", + error_setg(&err, "The command %s does not support OOB", command); - return NULL; + goto out; } if (runstate_check(RUN_STATE_PRECONFIG) && !(cmd->options & QCO_ALLOW_PRECONFIG)) { - error_setg(errp, "The command '%s' isn't permitted in '%s' state", + error_setg(&err, "The command '%s' isn't permitted in '%s' state", cmd->name, RunState_str(RUN_STATE_PRECONFIG)); - return NULL; + goto out; } if (!qdict_haskey(dict, "arguments")) { @@ -128,62 +152,36 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, args = qdict_get_qdict(dict, "arguments"); qobject_ref(args); } + cmd->fn(args, &ret, &err); + qobject_unref(args); + if (err) { + goto out; + } - cmd->fn(args, &ret, &local_err); - if (local_err) { - error_propagate(errp, local_err); - } else if (cmd->options & QCO_NO_SUCCESS_RESP) { + if (cmd->options & QCO_NO_SUCCESS_RESP) { g_assert(!ret); + return NULL; } else if (!ret) { - /* TODO turn into assertion */ + /* + * When the command's schema has no 'returns', cmd->fn() + * leaves @ret null. The QMP spec calls for an empty object + * then; supply it. + */ ret = QOBJECT(qdict_new()); } - qobject_unref(args); - - return ret; -} - -QDict *qmp_error_response(Error *err) -{ - QDict *rsp; - - rsp = qdict_from_jsonf_nofail("{ 'error': { 'class': %s, 'desc': %s } }", - QapiErrorClass_str(error_get_class(err)), - error_get_pretty(err)); - error_free(err); - return rsp; -} - -/* - * Does @qdict look like a command to be run out-of-band? - */ -bool qmp_is_oob(const QDict *dict) -{ - return qdict_haskey(dict, "exec-oob") - && !qdict_haskey(dict, "execute"); -} - -QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request, - bool allow_oob) -{ - Error *err = NULL; - QDict *dict = qobject_to(QDict, request); - QObject *ret, *id = dict ? qdict_get(dict, "id") : NULL; - QDict *rsp; + rsp = qdict_new(); + qdict_put_obj(rsp, "return", ret); - ret = do_qmp_dispatch(cmds, request, allow_oob, &err); +out: if (err) { + assert(!rsp); rsp = qmp_error_response(err); - } else if (ret) { - rsp = qdict_new(); - qdict_put_obj(rsp, "return", ret); - } else { - /* Can only happen for commands with QCO_NO_SUCCESS_RESP */ - rsp = NULL; } - if (rsp && id) { + assert(rsp); + + if (id) { qdict_put_obj(rsp, "id", qobject_ref(id)); } diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c index ca00f74795..d0f9a1d3e3 100644 --- a/qapi/qmp-registry.c +++ b/qapi/qmp-registry.c @@ -27,7 +27,7 @@ void qmp_register_command(QmpCommandList *cmds, const char *name, QTAILQ_INSERT_TAIL(cmds, cmd, node); } -QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name) +const QmpCommand *qmp_find_command(const QmpCommandList *cmds, const char *name) { QmpCommand *cmd; @@ -77,10 +77,10 @@ bool qmp_has_success_response(const QmpCommand *cmd) return !(cmd->options & QCO_NO_SUCCESS_RESP); } -void qmp_for_each_command(QmpCommandList *cmds, qmp_cmd_callback_fn fn, +void qmp_for_each_command(const QmpCommandList *cmds, qmp_cmd_callback_fn fn, void *opaque) { - QmpCommand *cmd; + const QmpCommand *cmd; QTAILQ_FOREACH(cmd, cmds, node) { fn(cmd, opaque); diff --git a/qga/commands.c b/qga/commands.c index 43c323cead..f8852beb9c 100644 --- a/qga/commands.c +++ b/qga/commands.c @@ -54,7 +54,7 @@ void qmp_guest_ping(Error **errp) slog("guest-ping called"); } -static void qmp_command_info(QmpCommand *cmd, void *opaque) +static void qmp_command_info(const QmpCommand *cmd, void *opaque) { GuestAgentInfo *info = opaque; GuestAgentCommandInfo *cmd_info; diff --git a/qga/main.c b/qga/main.c index e5c39c189a..8ee2736f8e 100644 --- a/qga/main.c +++ b/qga/main.c @@ -359,7 +359,7 @@ static gint ga_strcmp(gconstpointer str1, gconstpointer str2) } /* disable commands that aren't safe for fsfreeze */ -static void ga_disable_non_whitelisted(QmpCommand *cmd, void *opaque) +static void ga_disable_non_whitelisted(const QmpCommand *cmd, void *opaque) { bool whitelisted = false; int i = 0; @@ -378,7 +378,7 @@ static void ga_disable_non_whitelisted(QmpCommand *cmd, void *opaque) } /* [re-]enable all commands, except those explicitly blacklisted by user */ -static void ga_enable_non_blacklisted(QmpCommand *cmd, void *opaque) +static void ga_enable_non_blacklisted(const QmpCommand *cmd, void *opaque) { GList *blacklist = opaque; const char *name = qmp_command_name(cmd); @@ -918,7 +918,7 @@ int64_t ga_get_fd_handle(GAState *s, Error **errp) return handle; } -static void ga_print_cmd(QmpCommand *cmd, void *opaque) +static void ga_print_cmd(const QmpCommand *cmd, void *opaque) { printf("%s\n", qmp_command_name(cmd)); } diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py index 0e13e82989..bc30876c88 100644 --- a/scripts/qapi/commands.py +++ b/scripts/qapi/commands.py @@ -283,9 +283,9 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); prefix=self._prefix)) self._genc.add(gen_registry(self._regy.get_content(), self._prefix)) - def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, - success_response, boxed, allow_oob, allow_preconfig, - features): + def visit_command(self, name, info, ifcond, features, + arg_type, ret_type, gen, success_response, boxed, + allow_oob, allow_preconfig): if not gen: return # FIXME: If T is a user-defined type, the user is responsible diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py index 1787a53d91..92f584edcf 100644 --- a/scripts/qapi/doc.py +++ b/scripts/qapi/doc.py @@ -243,34 +243,34 @@ class QAPISchemaGenDocVisitor(QAPISchemaVisitor): def write(self, output_dir): self._gen.write(output_dir) - def visit_enum_type(self, name, info, ifcond, members, prefix): + def visit_enum_type(self, name, info, ifcond, features, members, prefix): doc = self.cur_doc self._gen.add(texi_type('Enum', doc, ifcond, texi_members(doc, 'Values', member_func=texi_enum_value))) - def visit_object_type(self, name, info, ifcond, base, members, variants, - features): + def visit_object_type(self, name, info, ifcond, features, + base, members, variants): doc = self.cur_doc if base and base.is_implicit(): base = None self._gen.add(texi_type('Object', doc, ifcond, texi_members(doc, 'Members', base, variants))) - def visit_alternate_type(self, name, info, ifcond, variants): + def visit_alternate_type(self, name, info, ifcond, features, variants): doc = self.cur_doc self._gen.add(texi_type('Alternate', doc, ifcond, texi_members(doc, 'Members'))) - def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, - success_response, boxed, allow_oob, allow_preconfig, - features): + def visit_command(self, name, info, ifcond, features, + arg_type, ret_type, gen, success_response, boxed, + allow_oob, allow_preconfig): doc = self.cur_doc self._gen.add(texi_msg('Command', doc, ifcond, texi_arguments(doc, arg_type if boxed else None))) - def visit_event(self, name, info, ifcond, arg_type, boxed): + def visit_event(self, name, info, ifcond, features, arg_type, boxed): doc = self.cur_doc self._gen.add(texi_msg('Event', doc, ifcond, texi_arguments(doc, diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py index a98b9f5099..b544af5a1c 100644 --- a/scripts/qapi/events.py +++ b/scripts/qapi/events.py @@ -189,7 +189,7 @@ void %(event_emit)s(%(event_enum)s event, QDict *qdict); event_emit=self._event_emit_name, event_enum=self._event_enum_name)) - def visit_event(self, name, info, ifcond, arg_type, boxed): + def visit_event(self, name, info, ifcond, features, arg_type, boxed): with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_event_send_decl(name, arg_type, boxed)) self._genc.add(gen_event_send(name, arg_type, boxed, diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py index fecf466fa7..2942520399 100644 --- a/scripts/qapi/expr.py +++ b/scripts/qapi/expr.py @@ -167,8 +167,9 @@ def check_type(value, info, source, allow_optional=True, permit_upper=permit_upper) if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'): raise QAPISemError(info, "%s uses reserved name" % key_source) - check_keys(arg, info, key_source, ['type'], ['if']) + check_keys(arg, info, key_source, ['type'], ['if', 'features']) check_if(arg, info, key_source) + check_features(arg.get('features'), info) check_type(arg['type'], info, key_source, allow_array=True) @@ -219,7 +220,6 @@ def check_struct(expr, info): check_type(members, info, "'data'", allow_dict=name) check_type(expr.get('base'), info, "'base'") - check_features(expr.get('features'), info) def check_union(expr, info): @@ -267,7 +267,6 @@ def check_command(expr, info): raise QAPISemError(info, "'boxed': true requires 'data'") check_type(args, info, "'data'", allow_dict=not boxed) check_type(rets, info, "'returns'", allow_array=True) - check_features(expr.get('features'), info) def check_event(expr, info): @@ -319,18 +318,18 @@ def check_exprs(exprs): if meta == 'enum': check_keys(expr, info, meta, - ['enum', 'data'], ['if', 'prefix']) + ['enum', 'data'], ['if', 'features', 'prefix']) check_enum(expr, info) elif meta == 'union': check_keys(expr, info, meta, ['union', 'data'], - ['base', 'discriminator', 'if']) + ['base', 'discriminator', 'if', 'features']) normalize_members(expr.get('base')) normalize_members(expr['data']) check_union(expr, info) elif meta == 'alternate': check_keys(expr, info, meta, - ['alternate', 'data'], ['if']) + ['alternate', 'data'], ['if', 'features']) normalize_members(expr['data']) check_alternate(expr, info) elif meta == 'struct': @@ -348,13 +347,14 @@ def check_exprs(exprs): check_command(expr, info) elif meta == 'event': check_keys(expr, info, meta, - ['event'], ['data', 'boxed', 'if']) + ['event'], ['data', 'boxed', 'if', 'features']) normalize_members(expr.get('data')) check_event(expr, info) else: assert False, 'unexpected meta type' check_if(expr, info, meta) + check_features(expr.get('features'), info) check_flags(expr, info) return exprs diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index b5537eddc0..23652be810 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -16,7 +16,19 @@ from qapi.schema import (QAPISchemaArrayType, QAPISchemaBuiltinType, QAPISchemaType) -def to_qlit(obj, level=0, suppress_first_indent=False): +def _make_tree(obj, ifcond, features, extra=None): + if extra is None: + extra = {} + if ifcond: + extra['if'] = ifcond + if features: + obj['features'] = [(f.name, {'if': f.ifcond}) for f in features] + if extra: + return (obj, extra) + return obj + + +def _tree_to_qlit(obj, level=0, suppress_first_indent=False): def indent(level): return level * 4 * ' ' @@ -30,7 +42,7 @@ def to_qlit(obj, level=0, suppress_first_indent=False): ret += indent(level) + '/* %s */\n' % comment if ifcond: ret += gen_if(ifcond) - ret += to_qlit(ifobj, level) + ret += _tree_to_qlit(ifobj, level) if ifcond: ret += '\n' + gen_endif(ifcond) return ret @@ -43,7 +55,7 @@ def to_qlit(obj, level=0, suppress_first_indent=False): elif isinstance(obj, str): ret += 'QLIT_QSTR(' + to_c_string(obj) + ')' elif isinstance(obj, list): - elts = [to_qlit(elt, level + 1).strip('\n') + elts = [_tree_to_qlit(elt, level + 1).strip('\n') for elt in obj] elts.append(indent(level + 1) + "{}") ret += 'QLIT_QLIST(((QLitObject[]) {\n' @@ -53,7 +65,8 @@ def to_qlit(obj, level=0, suppress_first_indent=False): elts = [] for key, value in sorted(obj.items()): elts.append(indent(level + 1) + '{ %s, %s }' % - (to_c_string(key), to_qlit(value, level + 1, True))) + (to_c_string(key), + _tree_to_qlit(value, level + 1, True))) elts.append(indent(level + 1) + '{}') ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' ret += ',\n'.join(elts) + '\n' @@ -79,7 +92,7 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): ' * QAPI/QMP schema introspection', __doc__) self._unmask = unmask self._schema = None - self._qlits = [] + self._trees = [] self._used_types = [] self._name_map = {} self._genc.add(mcgen(''' @@ -108,9 +121,9 @@ extern const QLitObject %(c_name)s; const QLitObject %(c_name)s = %(c_string)s; ''', c_name=c_name(name), - c_string=to_qlit(self._qlits))) + c_string=_tree_to_qlit(self._trees))) self._schema = None - self._qlits = [] + self._trees = [] self._used_types = [] self._name_map = {} @@ -144,89 +157,78 @@ const QLitObject %(c_name)s = %(c_string)s; return '[' + self._use_type(typ.element_type) + ']' return self._name(typ.name) - def _gen_qlit(self, name, mtype, obj, ifcond): - extra = {} + def _gen_tree(self, name, mtype, obj, ifcond, features): + extra = None if mtype not in ('command', 'event', 'builtin', 'array'): if not self._unmask: # Output a comment to make it easy to map masked names # back to the source when reading the generated output. - extra['comment'] = '"%s" = %s' % (self._name(name), name) + extra = {'comment': '"%s" = %s' % (self._name(name), name)} name = self._name(name) obj['name'] = name obj['meta-type'] = mtype - if ifcond: - extra['if'] = ifcond - if extra: - self._qlits.append((obj, extra)) - else: - self._qlits.append(obj) + self._trees.append(_make_tree(obj, ifcond, features, extra)) def _gen_member(self, member): - ret = {'name': member.name, 'type': self._use_type(member.type)} + obj = {'name': member.name, 'type': self._use_type(member.type)} if member.optional: - ret['default'] = None - if member.ifcond: - ret = (ret, {'if': member.ifcond}) - return ret + obj['default'] = None + return _make_tree(obj, member.ifcond, member.features) 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)}, - {'if': variant.ifcond}) + obj = {'case': variant.name, 'type': self._use_type(variant.type)} + return _make_tree(obj, variant.ifcond, None) def visit_builtin_type(self, name, info, json_type): - self._gen_qlit(name, 'builtin', {'json-type': json_type}, []) + self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) - def visit_enum_type(self, name, info, ifcond, members, prefix): - self._gen_qlit(name, 'enum', - {'values': - [(m.name, {'if': m.ifcond}) for m in members]}, - ifcond) + def visit_enum_type(self, name, info, ifcond, features, members, prefix): + self._gen_tree(name, 'enum', + {'values': [_make_tree(m.name, m.ifcond, None) + for m in members]}, + ifcond, features) def visit_array_type(self, name, info, ifcond, element_type): element = self._use_type(element_type) - self._gen_qlit('[' + element + ']', 'array', {'element-type': element}, - ifcond) + self._gen_tree('[' + element + ']', 'array', {'element-type': element}, + ifcond, None) - def visit_object_type_flat(self, name, info, ifcond, members, variants, - features): + def visit_object_type_flat(self, name, info, ifcond, features, + 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)) - if features: - obj['features'] = [(f.name, {'if': f.ifcond}) for f in features] - self._gen_qlit(name, 'object', obj, ifcond) + self._gen_tree(name, 'object', obj, ifcond, features) - def visit_alternate_type(self, name, info, ifcond, variants): - self._gen_qlit(name, 'alternate', + def visit_alternate_type(self, name, info, ifcond, features, variants): + self._gen_tree(name, 'alternate', {'members': [ - ({'type': self._use_type(m.type)}, {'if': m.ifcond}) - for m in variants.variants]}, ifcond) - - def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, - success_response, boxed, allow_oob, allow_preconfig, - features): + _make_tree({'type': self._use_type(m.type)}, + m.ifcond, None) + for m in variants.variants]}, + ifcond, features) + + def visit_command(self, name, info, ifcond, features, + arg_type, ret_type, gen, success_response, boxed, + allow_oob, allow_preconfig): arg_type = arg_type or self._schema.the_empty_object_type ret_type = ret_type or self._schema.the_empty_object_type obj = {'arg-type': self._use_type(arg_type), 'ret-type': self._use_type(ret_type)} if allow_oob: obj['allow-oob'] = allow_oob + self._gen_tree(name, 'command', obj, ifcond, features) - if features: - obj['features'] = [(f.name, {'if': f.ifcond}) for f in features] - - self._gen_qlit(name, 'command', obj, ifcond) - - def visit_event(self, name, info, ifcond, arg_type, boxed): + def visit_event(self, name, info, ifcond, features, arg_type, boxed): arg_type = arg_type or self._schema.the_empty_object_type - self._gen_qlit(name, 'event', {'arg-type': self._use_type(arg_type)}, - ifcond) + self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, + ifcond, features) def gen_introspect(schema, output_dir, prefix, opt_unmask): diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index d759308b4e..78309a00f0 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -53,13 +53,13 @@ class QAPISchemaEntity: seen = {} for f in self.features: f.check_clash(self.info, seen) - if self.doc: - self.doc.connect_feature(f) - self._checked = True def connect_doc(self, doc=None): - pass + doc = doc or self.doc + if doc: + for f in self.features: + doc.connect_feature(f) def check_doc(self): if self.doc: @@ -109,29 +109,29 @@ class QAPISchemaVisitor: def visit_builtin_type(self, name, info, json_type): pass - def visit_enum_type(self, name, info, ifcond, members, prefix): + def visit_enum_type(self, name, info, ifcond, features, members, prefix): pass def visit_array_type(self, name, info, ifcond, element_type): pass - def visit_object_type(self, name, info, ifcond, base, members, variants, - features): + def visit_object_type(self, name, info, ifcond, features, + base, members, variants): pass - def visit_object_type_flat(self, name, info, ifcond, members, variants, - features): + def visit_object_type_flat(self, name, info, ifcond, features, + members, variants): pass - def visit_alternate_type(self, name, info, ifcond, variants): + def visit_alternate_type(self, name, info, ifcond, features, variants): pass - def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, - success_response, boxed, allow_oob, allow_preconfig, - features): + def visit_command(self, name, info, ifcond, features, + arg_type, ret_type, gen, success_response, boxed, + allow_oob, allow_preconfig): pass - def visit_event(self, name, info, ifcond, arg_type, boxed): + def visit_event(self, name, info, ifcond, features, arg_type, boxed): pass @@ -193,6 +193,12 @@ class QAPISchemaType(QAPISchemaEntity): return None return self.name + def check(self, schema): + QAPISchemaEntity.check(self, schema) + if 'deprecated' in [f.name for f in self.features]: + raise QAPISemError( + self.info, "feature 'deprecated' is not supported for types") + def describe(self): assert self.meta return "%s type '%s'" % (self.meta, self.name) @@ -234,8 +240,8 @@ class QAPISchemaBuiltinType(QAPISchemaType): class QAPISchemaEnumType(QAPISchemaType): meta = 'enum' - def __init__(self, name, info, doc, ifcond, members, prefix): - super().__init__(name, info, doc, ifcond) + def __init__(self, name, info, doc, ifcond, features, members, prefix): + super().__init__(name, info, doc, ifcond, features) for m in members: assert isinstance(m, QAPISchemaEnumMember) m.set_defined_in(name) @@ -250,10 +256,10 @@ class QAPISchemaEnumType(QAPISchemaType): m.check_clash(self.info, seen) def connect_doc(self, doc=None): + super().connect_doc(doc) doc = doc or self.doc - if doc: - for m in self.members: - doc.connect_member(m) + for m in self.members: + m.connect_doc(doc) def is_implicit(self): # See QAPISchema._make_implicit_enum_type() and ._def_predefineds() @@ -270,15 +276,16 @@ class QAPISchemaEnumType(QAPISchemaType): def visit(self, visitor): super().visit(visitor) - visitor.visit_enum_type(self.name, self.info, self.ifcond, - self.members, self.prefix) + visitor.visit_enum_type( + self.name, self.info, self.ifcond, self.features, + self.members, self.prefix) class QAPISchemaArrayType(QAPISchemaType): meta = 'array' def __init__(self, name, info, element_type): - super().__init__(name, info, None, None) + super().__init__(name, info, None) assert isinstance(element_type, str) self._element_type_name = element_type self.element_type = None @@ -324,8 +331,8 @@ class QAPISchemaArrayType(QAPISchemaType): class QAPISchemaObjectType(QAPISchemaType): - def __init__(self, name, info, doc, ifcond, - base, local_members, variants, features): + def __init__(self, name, info, doc, ifcond, features, + base, local_members, variants): # struct has local_members, optional base, and no variants # flat union has base, variants, and no local_members # simple union has local_members, variants, and no base @@ -336,7 +343,7 @@ class QAPISchemaObjectType(QAPISchemaType): assert isinstance(m, QAPISchemaObjectTypeMember) m.set_defined_in(name) if variants is not None: - assert isinstance(variants, QAPISchemaObjectTypeVariants) + assert isinstance(variants, QAPISchemaVariants) variants.set_defined_in(name) self._base_name = base self.base = None @@ -392,12 +399,12 @@ class QAPISchemaObjectType(QAPISchemaType): m.check_clash(info, seen) def connect_doc(self, doc=None): + super().connect_doc(doc) doc = doc or self.doc - if doc: - if self.base and self.base.is_implicit(): - self.base.connect_doc(doc) - for m in self.local_members: - doc.connect_member(m) + if self.base and self.base.is_implicit(): + self.base.connect_doc(doc) + for m in self.local_members: + m.connect_doc(doc) @property def ifcond(self): @@ -433,93 +440,82 @@ class QAPISchemaObjectType(QAPISchemaType): def visit(self, visitor): super().visit(visitor) - visitor.visit_object_type(self.name, self.info, self.ifcond, - self.base, self.local_members, self.variants, - self.features) - visitor.visit_object_type_flat(self.name, self.info, self.ifcond, - self.members, self.variants, - self.features) - - -class QAPISchemaMember: - """ Represents object members, enum members and features """ - role = 'member' - - def __init__(self, name, info, ifcond=None): - assert isinstance(name, str) - self.name = name - self.info = info - self.ifcond = ifcond or [] - self.defined_in = None - - def set_defined_in(self, name): - assert not self.defined_in - self.defined_in = name - - def check_clash(self, info, seen): - cname = c_name(self.name) - if cname in seen: - raise QAPISemError( - info, - "%s collides with %s" - % (self.describe(info), seen[cname].describe(info))) - seen[cname] = self + visitor.visit_object_type( + self.name, self.info, self.ifcond, self.features, + self.base, self.local_members, self.variants) + visitor.visit_object_type_flat( + self.name, self.info, self.ifcond, self.features, + self.members, self.variants) - def describe(self, info): - role = self.role - defined_in = self.defined_in - assert defined_in - - if defined_in.startswith('q_obj_'): - # See QAPISchema._make_implicit_object_type() - reverse the - # mapping there to create a nice human-readable description - defined_in = defined_in[6:] - if defined_in.endswith('-arg'): - # Implicit type created for a command's dict 'data' - assert role == 'member' - role = 'parameter' - elif defined_in.endswith('-base'): - # Implicit type created for a flat union's dict 'base' - role = 'base ' + role - else: - # Implicit type created for a simple union's branch - assert defined_in.endswith('-wrapper') - # Unreachable and not implemented - assert False - elif defined_in.endswith('Kind'): - # See QAPISchema._make_implicit_enum_type() - # Implicit enum created for simple union's branches - assert role == 'value' - role = 'branch' - elif defined_in != info.defn_name: - return "%s '%s' of type '%s'" % (role, self.name, defined_in) - return "%s '%s'" % (role, self.name) +class QAPISchemaAlternateType(QAPISchemaType): + meta = 'alternate' -class QAPISchemaEnumMember(QAPISchemaMember): - role = 'value' + def __init__(self, name, info, doc, ifcond, features, variants): + super().__init__(name, info, doc, ifcond, features) + assert isinstance(variants, QAPISchemaVariants) + assert variants.tag_member + variants.set_defined_in(name) + variants.tag_member.set_defined_in(self.name) + self.variants = variants + def check(self, schema): + super().check(schema) + self.variants.tag_member.check(schema) + # Not calling self.variants.check_clash(), because there's nothing + # to clash with + self.variants.check(schema, {}) + # Alternate branch names have no relation to the tag enum values; + # so we have to check for potential name collisions ourselves. + seen = {} + types_seen = {} + for v in self.variants.variants: + v.check_clash(self.info, seen) + qtype = v.type.alternate_qtype() + if not qtype: + raise QAPISemError( + self.info, + "%s cannot use %s" + % (v.describe(self.info), v.type.describe())) + conflicting = set([qtype]) + if qtype == 'QTYPE_QSTRING': + if isinstance(v.type, QAPISchemaEnumType): + for m in v.type.members: + if m.name in ['on', 'off']: + conflicting.add('QTYPE_QBOOL') + if re.match(r'[-+0-9.]', m.name): + # lazy, could be tightened + conflicting.add('QTYPE_QNUM') + else: + conflicting.add('QTYPE_QNUM') + conflicting.add('QTYPE_QBOOL') + for qt in conflicting: + if qt in types_seen: + raise QAPISemError( + self.info, + "%s can't be distinguished from '%s'" + % (v.describe(self.info), types_seen[qt])) + types_seen[qt] = v.name -class QAPISchemaFeature(QAPISchemaMember): - role = 'feature' + def connect_doc(self, doc=None): + super().connect_doc(doc) + doc = doc or self.doc + for v in self.variants.variants: + v.connect_doc(doc) + def c_type(self): + return c_name(self.name) + pointer_suffix -class QAPISchemaObjectTypeMember(QAPISchemaMember): - def __init__(self, name, info, typ, optional, ifcond=None): - super().__init__(name, info, ifcond) - assert isinstance(typ, str) - assert isinstance(optional, bool) - self._type_name = typ - self.type = None - self.optional = optional + def json_type(self): + return 'value' - def check(self, schema): - assert self.defined_in - self.type = schema.resolve_type(self._type_name, self.info, - self.describe) + def visit(self, visitor): + super().visit(visitor) + visitor.visit_alternate_type( + self.name, self.info, self.ifcond, self.features, self.variants) -class QAPISchemaObjectTypeVariants: +class QAPISchemaVariants: def __init__(self, tag_name, info, tag_member, variants): # Flat unions pass tag_name but not tag_member. # Simple unions and alternates pass tag_member but not tag_name. @@ -529,7 +525,7 @@ class QAPISchemaObjectTypeVariants: assert (isinstance(tag_name, str) or isinstance(tag_member, QAPISchemaObjectTypeMember)) for v in variants: - assert isinstance(v, QAPISchemaObjectTypeVariant) + assert isinstance(v, QAPISchemaVariant) self._tag_name = tag_name self.info = info self.tag_member = tag_member @@ -579,8 +575,8 @@ class QAPISchemaObjectTypeVariants: cases = {v.name for v in self.variants} for m in self.tag_member.type.members: if m.name not in cases: - v = QAPISchemaObjectTypeVariant(m.name, self.info, - 'q_empty', m.ifcond) + v = QAPISchemaVariant(m.name, self.info, + 'q_empty', m.ifcond) v.set_defined_in(self.tag_member.defined_in) self.variants.append(v) if not self.variants: @@ -610,86 +606,114 @@ class QAPISchemaObjectTypeVariants: v.type.check_clash(info, dict(seen)) -class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): - role = 'branch' +class QAPISchemaMember: + """ Represents object members, enum members and features """ + role = 'member' - def __init__(self, name, info, typ, ifcond=None): - super().__init__(name, info, typ, False, ifcond) + def __init__(self, name, info, ifcond=None): + assert isinstance(name, str) + self.name = name + self.info = info + self.ifcond = ifcond or [] + self.defined_in = None + def set_defined_in(self, name): + assert not self.defined_in + self.defined_in = name -class QAPISchemaAlternateType(QAPISchemaType): - meta = 'alternate' + def check_clash(self, info, seen): + cname = c_name(self.name) + if cname in seen: + raise QAPISemError( + info, + "%s collides with %s" + % (self.describe(info), seen[cname].describe(info))) + seen[cname] = self + + def connect_doc(self, doc): + if doc: + doc.connect_member(self) + + def describe(self, info): + role = self.role + defined_in = self.defined_in + assert defined_in + + if defined_in.startswith('q_obj_'): + # See QAPISchema._make_implicit_object_type() - reverse the + # mapping there to create a nice human-readable description + defined_in = defined_in[6:] + if defined_in.endswith('-arg'): + # Implicit type created for a command's dict 'data' + assert role == 'member' + role = 'parameter' + elif defined_in.endswith('-base'): + # Implicit type created for a flat union's dict 'base' + role = 'base ' + role + else: + # Implicit type created for a simple union's branch + assert defined_in.endswith('-wrapper') + # Unreachable and not implemented + assert False + elif defined_in.endswith('Kind'): + # See QAPISchema._make_implicit_enum_type() + # Implicit enum created for simple union's branches + assert role == 'value' + role = 'branch' + elif defined_in != info.defn_name: + return "%s '%s' of type '%s'" % (role, self.name, defined_in) + return "%s '%s'" % (role, self.name) + + +class QAPISchemaEnumMember(QAPISchemaMember): + role = 'value' + + +class QAPISchemaFeature(QAPISchemaMember): + role = 'feature' - def __init__(self, name, info, doc, ifcond, variants): - super().__init__(name, info, doc, ifcond) - assert isinstance(variants, QAPISchemaObjectTypeVariants) - assert variants.tag_member - variants.set_defined_in(name) - variants.tag_member.set_defined_in(self.name) - self.variants = variants + +class QAPISchemaObjectTypeMember(QAPISchemaMember): + def __init__(self, name, info, typ, optional, ifcond=None, features=None): + super().__init__(name, info, ifcond) + assert isinstance(typ, str) + assert isinstance(optional, bool) + for f in features or []: + assert isinstance(f, QAPISchemaFeature) + f.set_defined_in(name) + self._type_name = typ + self.type = None + self.optional = optional + self.features = features or [] def check(self, schema): - super().check(schema) - self.variants.tag_member.check(schema) - # Not calling self.variants.check_clash(), because there's nothing - # to clash with - self.variants.check(schema, {}) - # Alternate branch names have no relation to the tag enum values; - # so we have to check for potential name collisions ourselves. + assert self.defined_in + self.type = schema.resolve_type(self._type_name, self.info, + self.describe) seen = {} - types_seen = {} - for v in self.variants.variants: - v.check_clash(self.info, seen) - qtype = v.type.alternate_qtype() - if not qtype: - raise QAPISemError( - self.info, - "%s cannot use %s" - % (v.describe(self.info), v.type.describe())) - conflicting = set([qtype]) - if qtype == 'QTYPE_QSTRING': - if isinstance(v.type, QAPISchemaEnumType): - for m in v.type.members: - if m.name in ['on', 'off']: - conflicting.add('QTYPE_QBOOL') - if re.match(r'[-+0-9.]', m.name): - # lazy, could be tightened - conflicting.add('QTYPE_QNUM') - else: - conflicting.add('QTYPE_QNUM') - conflicting.add('QTYPE_QBOOL') - for qt in conflicting: - if qt in types_seen: - raise QAPISemError( - self.info, - "%s can't be distinguished from '%s'" - % (v.describe(self.info), types_seen[qt])) - types_seen[qt] = v.name + for f in self.features: + f.check_clash(self.info, seen) - def connect_doc(self, doc=None): - doc = doc or self.doc + def connect_doc(self, doc): + super().connect_doc(doc) if doc: - for v in self.variants.variants: - doc.connect_member(v) + for f in self.features: + doc.connect_feature(f) - def c_type(self): - return c_name(self.name) + pointer_suffix - def json_type(self): - return 'value' +class QAPISchemaVariant(QAPISchemaObjectTypeMember): + role = 'branch' - def visit(self, visitor): - super().visit(visitor) - visitor.visit_alternate_type(self.name, self.info, self.ifcond, - self.variants) + def __init__(self, name, info, typ, ifcond=None): + super().__init__(name, info, typ, False, ifcond) class QAPISchemaCommand(QAPISchemaEntity): meta = 'command' - def __init__(self, name, info, doc, ifcond, arg_type, ret_type, - gen, success_response, boxed, allow_oob, allow_preconfig, - features): + def __init__(self, name, info, doc, ifcond, features, + arg_type, ret_type, + gen, success_response, boxed, allow_oob, allow_preconfig): super().__init__(name, info, doc, ifcond, features) assert not arg_type or isinstance(arg_type, str) assert not ret_type or isinstance(ret_type, str) @@ -733,6 +757,7 @@ class QAPISchemaCommand(QAPISchemaEntity): % self.ret_type.describe()) def connect_doc(self, doc=None): + super().connect_doc(doc) doc = doc or self.doc if doc: if self.arg_type and self.arg_type.is_implicit(): @@ -740,19 +765,17 @@ class QAPISchemaCommand(QAPISchemaEntity): def visit(self, visitor): super().visit(visitor) - visitor.visit_command(self.name, self.info, self.ifcond, - self.arg_type, self.ret_type, - self.gen, self.success_response, - self.boxed, self.allow_oob, - self.allow_preconfig, - self.features) + visitor.visit_command( + self.name, self.info, self.ifcond, self.features, + self.arg_type, self.ret_type, self.gen, self.success_response, + self.boxed, self.allow_oob, self.allow_preconfig) class QAPISchemaEvent(QAPISchemaEntity): meta = 'event' - def __init__(self, name, info, doc, ifcond, arg_type, boxed): - super().__init__(name, info, doc, ifcond) + def __init__(self, name, info, doc, ifcond, features, arg_type, boxed): + super().__init__(name, info, doc, ifcond, features) assert not arg_type or isinstance(arg_type, str) self._arg_type_name = arg_type self.arg_type = None @@ -775,6 +798,7 @@ class QAPISchemaEvent(QAPISchemaEntity): % self.arg_type.describe()) def connect_doc(self, doc=None): + super().connect_doc(doc) doc = doc or self.doc if doc: if self.arg_type and self.arg_type.is_implicit(): @@ -782,8 +806,9 @@ class QAPISchemaEvent(QAPISchemaEntity): def visit(self, visitor): super().visit(visitor) - visitor.visit_event(self.name, self.info, self.ifcond, - self.arg_type, self.boxed) + visitor.visit_event( + self.name, self.info, self.ifcond, self.features, + self.arg_type, self.boxed) class QAPISchema: @@ -888,7 +913,7 @@ class QAPISchema: ('null', 'null', 'QNull' + pointer_suffix)]: self._def_builtin_type(*t) self.the_empty_object_type = QAPISchemaObjectType( - 'q_empty', None, None, None, None, [], None, []) + 'q_empty', None, None, None, None, None, [], None) self._def_entity(self.the_empty_object_type) qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', @@ -896,10 +921,12 @@ class QAPISchema: qtype_values = self._make_enum_members( [{'name': n} for n in qtypes], None) - self._def_entity(QAPISchemaEnumType('QType', None, None, None, + self._def_entity(QAPISchemaEnumType('QType', None, None, None, None, qtype_values, 'QTYPE')) def _make_features(self, features, info): + if features is None: + return [] return [QAPISchemaFeature(f['name'], info, f.get('if')) for f in features] @@ -911,7 +938,8 @@ class QAPISchema: # See also QAPISchemaObjectTypeMember.describe() name = name + 'Kind' # reserved by check_defn_name_str() self._def_entity(QAPISchemaEnumType( - name, info, None, ifcond, self._make_enum_members(values, info), + name, info, None, ifcond, None, + self._make_enum_members(values, info), None)) return name @@ -939,8 +967,8 @@ class QAPISchema: # TODO kill simple unions or implement the disjunction assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access else: - self._def_entity(QAPISchemaObjectType(name, info, None, ifcond, - None, members, None, [])) + self._def_entity(QAPISchemaObjectType( + name, info, None, ifcond, None, None, members, None)) return name def _def_enum_type(self, expr, info, doc): @@ -948,11 +976,12 @@ class QAPISchema: data = expr['data'] prefix = expr.get('prefix') ifcond = expr.get('if') + features = self._make_features(expr.get('features'), info) self._def_entity(QAPISchemaEnumType( - name, info, doc, ifcond, + name, info, doc, ifcond, features, self._make_enum_members(data, info), prefix)) - def _make_member(self, name, typ, ifcond, info): + def _make_member(self, name, typ, ifcond, features, info): optional = False if name.startswith('*'): name = name[1:] @@ -960,10 +989,12 @@ class QAPISchema: if isinstance(typ, list): assert len(typ) == 1 typ = self._make_array_type(typ[0], info) - return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond) + return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond, + self._make_features(features, info)) def _make_members(self, data, info): - return [self._make_member(key, value['type'], value.get('if'), info) + return [self._make_member(key, value['type'], value.get('if'), + value.get('features'), info) for (key, value) in data.items()] def _def_struct_type(self, expr, info, doc): @@ -971,15 +1002,14 @@ class QAPISchema: base = expr.get('base') data = expr['data'] ifcond = expr.get('if') - features = expr.get('features', []) + features = self._make_features(expr.get('features'), info) self._def_entity(QAPISchemaObjectType( - name, info, doc, ifcond, base, + name, info, doc, ifcond, features, base, self._make_members(data, info), - None, - self._make_features(features, info))) + None)) def _make_variant(self, case, typ, ifcond, info): - return QAPISchemaObjectTypeVariant(case, info, typ, ifcond) + return QAPISchemaVariant(case, info, typ, ifcond) def _make_simple_variant(self, case, typ, ifcond, info): if isinstance(typ, list): @@ -987,14 +1017,15 @@ class QAPISchema: typ = self._make_array_type(typ[0], info) typ = self._make_implicit_object_type( typ, info, self.lookup_type(typ), - 'wrapper', [self._make_member('data', typ, None, info)]) - return QAPISchemaObjectTypeVariant(case, info, typ, ifcond) + 'wrapper', [self._make_member('data', typ, None, None, info)]) + return QAPISchemaVariant(case, info, typ, ifcond) def _def_union_type(self, expr, info, doc): name = expr['union'] data = expr['data'] base = expr.get('base') ifcond = expr.get('if') + features = self._make_features(expr.get('features'), info) tag_name = expr.get('discriminator') tag_member = None if isinstance(base, dict): @@ -1015,22 +1046,23 @@ class QAPISchema: tag_member = QAPISchemaObjectTypeMember('type', info, typ, False) members = [tag_member] self._def_entity( - QAPISchemaObjectType(name, info, doc, ifcond, base, members, - QAPISchemaObjectTypeVariants( - tag_name, info, tag_member, variants), - [])) + QAPISchemaObjectType(name, info, doc, ifcond, features, + base, members, + QAPISchemaVariants( + tag_name, info, tag_member, variants))) def _def_alternate_type(self, expr, info, doc): name = expr['alternate'] data = expr['data'] ifcond = expr.get('if') + features = self._make_features(expr.get('features'), info) variants = [self._make_variant(key, value['type'], value.get('if'), info) for (key, value) in data.items()] tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False) self._def_entity( - QAPISchemaAlternateType(name, info, doc, ifcond, - QAPISchemaObjectTypeVariants( + QAPISchemaAlternateType(name, info, doc, ifcond, features, + QAPISchemaVariants( None, info, tag_member, variants))) def _def_command(self, expr, info, doc): @@ -1043,27 +1075,31 @@ class QAPISchema: allow_oob = expr.get('allow-oob', False) allow_preconfig = expr.get('allow-preconfig', False) ifcond = expr.get('if') - features = expr.get('features', []) + features = self._make_features(expr.get('features'), info) if isinstance(data, OrderedDict): data = self._make_implicit_object_type( - name, info, ifcond, 'arg', self._make_members(data, info)) + name, info, ifcond, + 'arg', self._make_members(data, info)) if isinstance(rets, list): assert len(rets) == 1 rets = self._make_array_type(rets[0], info) - self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets, + self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, features, + data, rets, gen, success_response, - boxed, allow_oob, allow_preconfig, - self._make_features(features, info))) + boxed, allow_oob, allow_preconfig)) def _def_event(self, expr, info, doc): name = expr['event'] data = expr.get('data') boxed = expr.get('boxed', False) ifcond = expr.get('if') + features = self._make_features(expr.get('features'), info) if isinstance(data, OrderedDict): data = self._make_implicit_object_type( - name, info, ifcond, 'arg', self._make_members(data, info)) - self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed)) + name, info, ifcond, + 'arg', self._make_members(data, info)) + self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, features, + data, boxed)) def _def_exprs(self, exprs): for expr_elem in exprs: diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index 3c83b6e4be..3ad33af4ee 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -278,7 +278,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): self._genh.add(gen_type_cleanup_decl(name)) self._genc.add(gen_type_cleanup(name)) - def visit_enum_type(self, name, info, ifcond, members, prefix): + def visit_enum_type(self, name, info, ifcond, features, members, prefix): with ifcontext(ifcond, self._genh, self._genc): self._genh.preamble_add(gen_enum(name, members, prefix)) self._genc.add(gen_enum_lookup(name, members, prefix)) @@ -289,8 +289,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): self._genh.add(gen_array(name, element_type)) self._gen_type_cleanup(name) - def visit_object_type(self, name, info, ifcond, base, members, variants, - features): + def visit_object_type(self, name, info, ifcond, features, + base, members, variants): # Nothing to do for the special empty builtin if name == 'q_empty': return @@ -306,7 +306,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): # implicit types won't be directly allocated/freed self._gen_type_cleanup(name) - def visit_alternate_type(self, name, info, ifcond, variants): + def visit_alternate_type(self, name, info, ifcond, features, variants): with ifcontext(ifcond, self._genh): self._genh.preamble_add(gen_fwd_object_or_array(name)) self._genh.add(gen_object(name, ifcond, None, diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index 421e5bd8cd..23d9194aa4 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -316,7 +316,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): ''', types=types)) - def visit_enum_type(self, name, info, ifcond, members, prefix): + def visit_enum_type(self, name, info, ifcond, features, members, prefix): with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name, scalar=True)) self._genc.add(gen_visit_enum(name)) @@ -326,8 +326,8 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_list(name, element_type)) - def visit_object_type(self, name, info, ifcond, base, members, variants, - features): + def visit_object_type(self, name, info, ifcond, features, + base, members, variants): # Nothing to do for the special empty builtin if name == 'q_empty': return @@ -342,7 +342,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_object(name, base, members, variants)) - def visit_alternate_type(self, name, info, ifcond, variants): + def visit_alternate_type(self, name, info, ifcond, features, variants): with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_alternate(name, variants)) diff --git a/tests/Makefile.include b/tests/Makefile.include index 67e8fcddda..d1340301b2 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -242,6 +242,7 @@ qapi-schema += event-case.json qapi-schema += event-member-invalid-dict.json qapi-schema += event-nest-struct.json qapi-schema += features-bad-type.json +qapi-schema += features-deprecated-type.json qapi-schema += features-duplicate-name.json qapi-schema += features-if-invalid.json qapi-schema += features-missing-name.json diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err index 31ebe56bbf..970a08ab26 100644 --- a/tests/qapi-schema/alternate-base.err +++ b/tests/qapi-schema/alternate-base.err @@ -1,3 +1,3 @@ alternate-base.json: In alternate 'Alt': alternate-base.json:4: alternate has unknown key 'base' -Valid keys are 'alternate', 'data', 'if'. +Valid keys are 'alternate', 'data', 'features', 'if'. diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json index d992e713d9..ddd89d1233 100644 --- a/tests/qapi-schema/doc-good.json +++ b/tests/qapi-schema/doc-good.json @@ -53,10 +53,14 @@ # @Enum: # @one: The _one_ {and only} # +# Features: +# @enum-feat: Also _one_ {and only} +# # @two is undocumented ## { 'enum': 'Enum', 'data': [ { 'name': 'one', 'if': 'defined(IFONE)' }, 'two' ], + 'features': [ 'enum-feat' ], 'if': 'defined(IFCOND)' } ## @@ -74,10 +78,13 @@ # # Features: # @variant1-feat: a feature +# @member-feat: a member feature ## { 'struct': 'Variant1', 'features': [ 'variant1-feat' ], - 'data': { 'var1': { 'type': 'str', 'if': 'defined(IFSTR)' } } } + 'data': { 'var1': { 'type': 'str', + 'features': [ 'member-feat' ], + 'if': 'defined(IFSTR)' } } } ## # @Variant2: @@ -86,24 +93,34 @@ ## # @Object: +# Features: +# @union-feat1: a feature ## { 'union': 'Object', + 'features': [ 'union-feat1' ], 'base': 'Base', 'discriminator': 'base1', 'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } } ## # @SugaredUnion: +# Features: +# @union-feat2: a feature ## { 'union': 'SugaredUnion', + 'features': [ 'union-feat2' ], 'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } } ## # @Alternate: # @i: an integer # @b is undocumented +# +# Features: +# @alt-feat: a feature ## { 'alternate': 'Alternate', + 'features': [ 'alt-feat' ], 'data': { 'i': 'int', 'b': 'bool' } } ## @@ -160,6 +177,9 @@ ## # @EVT-BOXED: +# Features: +# @feat3: a feature ## { 'event': 'EVT-BOXED', 'boxed': true, + 'features': [ 'feat3' ], 'data': 'Object' } diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index 4c9406a464..6757dd26a2 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -15,11 +15,13 @@ enum Enum if ['defined(IFONE)'] member two if ['defined(IFCOND)'] + feature enum-feat object Base member base1: Enum optional=False object Variant1 member var1: str optional=False if ['defined(IFSTR)'] + feature member-feat feature variant1-feat object Variant2 object Object @@ -28,6 +30,7 @@ object Object case one: Variant1 case two: Variant2 if ['IFTWO'] + feature union-feat1 object q_obj_Variant1-wrapper member data: Variant1 optional=False object q_obj_Variant2-wrapper @@ -42,10 +45,12 @@ object SugaredUnion case one: q_obj_Variant1-wrapper case two: q_obj_Variant2-wrapper if ['IFTWO'] + feature union-feat2 alternate Alternate tag type case i: int case b: bool + feature alt-feat object q_obj_cmd-arg member arg1: int optional=False member arg2: str optional=True @@ -60,6 +65,7 @@ command cmd-boxed Object -> None feature cmd-feat2 event EVT-BOXED Object boxed=True + feature feat3 doc freeform body= = Section @@ -112,6 +118,8 @@ doc symbol=Enum The _one_ {and only} arg=two + feature=enum-feat +Also _one_ {and only} section=None @two is undocumented doc symbol=Base @@ -128,17 +136,23 @@ Another paragraph (but no @var: line) feature=variant1-feat a feature + feature=member-feat +a member feature doc symbol=Variant2 body= doc symbol=Object body= + feature=union-feat1 +a feature doc symbol=SugaredUnion body= arg=type + feature=union-feat2 +a feature doc symbol=Alternate body= @@ -147,6 +161,8 @@ an integer @b is undocumented arg=b + feature=alt-feat +a feature doc freeform body= == Another subsection @@ -197,3 +213,5 @@ another feature doc symbol=EVT-BOXED body= + feature=feat3 +a feature diff --git a/tests/qapi-schema/doc-good.texi b/tests/qapi-schema/doc-good.texi index d4b15dabf0..7f28fb7a0f 100644 --- a/tests/qapi-schema/doc-good.texi +++ b/tests/qapi-schema/doc-good.texi @@ -88,6 +88,12 @@ The @emph{one} @{and only@} @item @code{two} Not documented @end table + +@b{Features:} +@table @asis +@item @code{enum-feat} +Also @emph{one} @{and only@} +@end table @code{two} is undocumented @b{If:} @code{defined(IFCOND)} @@ -126,6 +132,8 @@ Not documented @table @asis @item @code{variant1-feat} a feature +@item @code{member-feat} +a member feature @end table @end deftp @@ -151,6 +159,12 @@ a feature @item The members of @code{Variant2} when @code{base1} is @t{"two"} (@b{If:} @code{IFTWO}) @end table +@b{Features:} +@table @asis +@item @code{union-feat1} +a feature +@end table + @end deftp @@ -167,6 +181,12 @@ One of @t{"one"}, @t{"two"} @item @code{data: Variant2} when @code{type} is @t{"two"} (@b{If:} @code{IFTWO}) @end table +@b{Features:} +@table @asis +@item @code{union-feat2} +a feature +@end table + @end deftp @@ -184,6 +204,12 @@ an integer Not documented @end table +@b{Features:} +@table @asis +@item @code{alt-feat} +a feature +@end table + @end deftp @@ -283,5 +309,11 @@ another feature @b{Arguments:} the members of @code{Object} +@b{Features:} +@table @asis +@item @code{feat3} +a feature +@end table + @end deftypefn diff --git a/tests/qapi-schema/features-deprecated-type.err b/tests/qapi-schema/features-deprecated-type.err new file mode 100644 index 0000000000..af4ffe20aa --- /dev/null +++ b/tests/qapi-schema/features-deprecated-type.err @@ -0,0 +1,2 @@ +features-deprecated-type.json: In struct 'S': +features-deprecated-type.json:2: feature 'deprecated' is not supported for types diff --git a/tests/qapi-schema/features-deprecated-type.json b/tests/qapi-schema/features-deprecated-type.json new file mode 100644 index 0000000000..4b5bf5b86e --- /dev/null +++ b/tests/qapi-schema/features-deprecated-type.json @@ -0,0 +1,3 @@ +# Feature 'deprecated' is not supported for types +{ 'struct': 'S', 'data': {}, + 'features': [ 'deprecated' ] } diff --git a/tests/qapi-schema/features-deprecated-type.out b/tests/qapi-schema/features-deprecated-type.out new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/qapi-schema/features-deprecated-type.out diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 9abf175fe0..6b1f05afa7 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -252,13 +252,13 @@ 'bar': { 'type': ['TestIfEnum'], 'if': 'defined(TEST_IF_EVT_BAR)' } }, 'if': 'defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)' } -# test 'features' for structs +# test 'features' { 'struct': 'FeatureStruct0', 'data': { 'foo': 'int' }, 'features': [] } { 'struct': 'FeatureStruct1', - 'data': { 'foo': 'int' }, + 'data': { 'foo': { 'type': 'int', 'features': [ 'deprecated' ] } }, 'features': [ 'feature1' ] } { 'struct': 'FeatureStruct2', 'data': { 'foo': 'int' }, @@ -281,7 +281,22 @@ 'data': { 'foo': 'int' }, 'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'] } ] } -{ 'command': 'test-features', + +{ 'enum': 'FeatureEnum1', + 'data': [ 'eins', 'zwei', 'drei' ], + 'features': [ 'feature1' ] } + +{ 'union': 'FeatureUnion1', + 'base': { 'tag': 'FeatureEnum1' }, + 'discriminator': 'tag', + 'data': { 'eins': 'FeatureStruct1' }, + 'features': [ 'feature1' ] } + +{ 'alternate': 'FeatureAlternate1', + 'data': { 'eins': 'FeatureStruct1' }, + 'features': [ 'feature1' ] } + +{ 'command': 'test-features0', 'data': { 'fs0': 'FeatureStruct0', 'fs1': 'FeatureStruct1', 'fs2': 'FeatureStruct2', @@ -289,14 +304,11 @@ 'fs4': 'FeatureStruct4', 'cfs1': 'CondFeatureStruct1', 'cfs2': 'CondFeatureStruct2', - 'cfs3': 'CondFeatureStruct3' } } - -# test 'features' for command - -{ 'command': 'test-command-features0', + 'cfs3': 'CondFeatureStruct3' }, 'features': [] } + { 'command': 'test-command-features1', - 'features': [ 'feature1' ] } + 'features': [ 'deprecated' ] } { 'command': 'test-command-features3', 'features': [ 'feature1', 'feature2' ] } @@ -308,3 +320,6 @@ { 'command': 'test-command-cond-features3', 'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'] } ] } + +{ 'event': 'TEST-EVENT-FEATURES1', + 'features': [ 'deprecated' ] } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 9bd3c4a490..891b4101e0 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -359,6 +359,7 @@ object FeatureStruct0 member foo: int optional=False object FeatureStruct1 member foo: int optional=False + feature deprecated feature feature1 object FeatureStruct2 member foo: int optional=False @@ -387,7 +388,25 @@ object CondFeatureStruct3 member foo: int optional=False feature feature1 if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'] -object q_obj_test-features-arg +enum FeatureEnum1 + member eins + member zwei + member drei + feature feature1 +object q_obj_FeatureUnion1-base + member tag: FeatureEnum1 optional=False +object FeatureUnion1 + base q_obj_FeatureUnion1-base + tag tag + case eins: FeatureStruct1 + case zwei: q_empty + case drei: q_empty + feature feature1 +alternate FeatureAlternate1 + tag type + case eins: FeatureStruct1 + feature feature1 +object q_obj_test-features0-arg member fs0: FeatureStruct0 optional=False member fs1: FeatureStruct1 optional=False member fs2: FeatureStruct2 optional=False @@ -396,13 +415,11 @@ object q_obj_test-features-arg member cfs1: CondFeatureStruct1 optional=False member cfs2: CondFeatureStruct2 optional=False member cfs3: CondFeatureStruct3 optional=False -command test-features q_obj_test-features-arg -> None - gen=True success_response=True boxed=False oob=False preconfig=False -command test-command-features0 None -> None +command test-features0 q_obj_test-features0-arg -> None gen=True success_response=True boxed=False oob=False preconfig=False command test-command-features1 None -> None gen=True success_response=True boxed=False oob=False preconfig=False - feature feature1 + feature deprecated command test-command-features3 None -> None gen=True success_response=True boxed=False oob=False preconfig=False feature feature1 @@ -421,6 +438,9 @@ command test-command-cond-features3 None -> None gen=True success_response=True boxed=False oob=False preconfig=False feature feature1 if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'] +event TEST-EVENT-FEATURES1 None + boxed=False + feature deprecated module include/sub-module.json include sub-sub-module.json object SecondArrayRef diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index bee18ee344..f396b471eb 100755 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -30,7 +30,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): def visit_include(self, name, info): print('include %s' % name) - def visit_enum_type(self, name, info, ifcond, members, prefix): + def visit_enum_type(self, name, info, ifcond, features, members, prefix): print('enum %s' % name) if prefix: print(' prefix %s' % prefix) @@ -38,6 +38,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): print(' member %s' % m.name) self._print_if(m.ifcond, indent=8) self._print_if(ifcond) + self._print_features(features) def visit_array_type(self, name, info, ifcond, element_type): if not info: @@ -45,8 +46,8 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): print('array %s %s' % (name, element_type.name)) self._print_if(ifcond) - def visit_object_type(self, name, info, ifcond, base, members, variants, - features): + def visit_object_type(self, name, info, ifcond, features, + base, members, variants): print('object %s' % name) if base: print(' base %s' % base.name) @@ -54,18 +55,20 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): print(' member %s: %s optional=%s' % (m.name, m.type.name, m.optional)) self._print_if(m.ifcond, 8) + self._print_features(m.features, indent=8) self._print_variants(variants) self._print_if(ifcond) self._print_features(features) - def visit_alternate_type(self, name, info, ifcond, variants): + def visit_alternate_type(self, name, info, ifcond, features, variants): print('alternate %s' % name) self._print_variants(variants) self._print_if(ifcond) + self._print_features(features) - def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, - success_response, boxed, allow_oob, allow_preconfig, - features): + def visit_command(self, name, info, ifcond, features, + arg_type, ret_type, gen, success_response, boxed, + allow_oob, allow_preconfig): print('command %s %s -> %s' % (name, arg_type and arg_type.name, ret_type and ret_type.name)) @@ -74,10 +77,11 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): self._print_if(ifcond) self._print_features(features) - def visit_event(self, name, info, ifcond, arg_type, boxed): + def visit_event(self, name, info, ifcond, features, arg_type, boxed): print('event %s %s' % (name, arg_type and arg_type.name)) print(' boxed=%s' % boxed) self._print_if(ifcond) + self._print_features(features) @staticmethod def _print_variants(variants): @@ -93,11 +97,11 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): print('%sif %s' % (' ' * indent, ifcond)) @classmethod - def _print_features(cls, features): + def _print_features(cls, features, indent=4): if features: for f in features: - print(' feature %s' % f.name) - cls._print_if(f.ifcond, 8) + print('%sfeature %s' % (' ' * indent, f.name)) + cls._print_if(f.ifcond, indent + 4) def test_frontend(fname): diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c index 79507d9e54..d12ff47e26 100644 --- a/tests/test-qmp-cmds.c +++ b/tests/test-qmp-cmds.c @@ -1,5 +1,6 @@ #include "qemu/osdep.h" #include "qapi/qmp/qdict.h" +#include "qapi/qmp/qjson.h" #include "qapi/qmp/qnum.h" #include "qapi/qmp/qstring.h" #include "qapi/error.h" @@ -44,7 +45,7 @@ void qmp_user_def_cmd1(UserDefOne * ud1, Error **errp) { } -void qmp_test_features(FeatureStruct0 *fs0, FeatureStruct1 *fs1, +void qmp_test_features0(FeatureStruct0 *fs0, FeatureStruct1 *fs1, FeatureStruct2 *fs2, FeatureStruct3 *fs3, FeatureStruct4 *fs4, CondFeatureStruct1 *cfs1, CondFeatureStruct2 *cfs2, CondFeatureStruct3 *cfs3, @@ -52,10 +53,6 @@ void qmp_test_features(FeatureStruct0 *fs0, FeatureStruct1 *fs1, { } -void qmp_test_command_features0(Error **errp) -{ -} - void qmp_test_command_features1(Error **errp) { } @@ -145,66 +142,87 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a, } -/* test commands with no input and no return value */ -static void test_dispatch_cmd(void) +static QObject *do_qmp_dispatch(bool allow_oob, const char *template, ...) { - QDict *req = qdict_new(); - QDict *resp; + va_list ap; + QDict *req, *resp; + QObject *ret; - qdict_put_str(req, "execute", "user_def_cmd"); + va_start(ap, template); + req = qdict_from_vjsonf_nofail(template, ap); + va_end(ap); - resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false); - assert(resp != NULL); - assert(!qdict_haskey(resp, "error")); + resp = qmp_dispatch(&qmp_commands, QOBJECT(req), allow_oob); + g_assert(resp); + ret = qdict_get(resp, "return"); + g_assert(ret); + g_assert(qdict_size(resp) == 1); + qobject_ref(ret); qobject_unref(resp); qobject_unref(req); + return ret; } -static void test_dispatch_cmd_oob(void) +static void do_qmp_dispatch_error(bool allow_oob, ErrorClass cls, + const char *template, ...) { - QDict *req = qdict_new(); - QDict *resp; - - qdict_put_str(req, "exec-oob", "test-flags-command"); - - resp = qmp_dispatch(&qmp_commands, QOBJECT(req), true); - assert(resp != NULL); - assert(!qdict_haskey(resp, "error")); + va_list ap; + QDict *req, *resp; + QDict *error; + + va_start(ap, template); + req = qdict_from_vjsonf_nofail(template, ap); + va_end(ap); + + resp = qmp_dispatch(&qmp_commands, QOBJECT(req), allow_oob); + g_assert(resp); + error = qdict_get_qdict(resp, "error"); + g_assert(error); + g_assert_cmpstr(qdict_get_try_str(error, "class"), + ==, QapiErrorClass_str(cls)); + g_assert(qdict_get_try_str(error, "desc")); + g_assert(qdict_size(error) == 2); + g_assert(qdict_size(resp) == 1); qobject_unref(resp); qobject_unref(req); } -/* test commands that return an error due to invalid parameters */ -static void test_dispatch_cmd_failure(void) +/* test commands with no input and no return value */ +static void test_dispatch_cmd(void) { - QDict *req = qdict_new(); - QDict *args = qdict_new(); - QDict *resp; - - qdict_put_str(req, "execute", "user_def_cmd2"); - - resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false); - assert(resp != NULL); - assert(qdict_haskey(resp, "error")); - - qobject_unref(resp); - qobject_unref(req); + QDict *ret; - /* check that with extra arguments it throws an error */ - req = qdict_new(); - qdict_put_int(args, "a", 66); - qdict_put(req, "arguments", args); + ret = qobject_to(QDict, + do_qmp_dispatch(false, + "{ 'execute': 'user_def_cmd' }")); + assert(ret && qdict_size(ret) == 0); + qobject_unref(ret); +} - qdict_put_str(req, "execute", "user_def_cmd"); +static void test_dispatch_cmd_oob(void) +{ + QDict *ret; - resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false); - assert(resp != NULL); - assert(qdict_haskey(resp, "error")); + ret = qobject_to(QDict, + do_qmp_dispatch(true, + "{ 'exec-oob': 'test-flags-command' }")); + assert(ret && qdict_size(ret) == 0); + qobject_unref(ret); +} - qobject_unref(resp); - qobject_unref(req); +/* test commands that return an error due to invalid parameters */ +static void test_dispatch_cmd_failure(void) +{ + /* missing arguments */ + do_qmp_dispatch_error(false, ERROR_CLASS_GENERIC_ERROR, + "{ 'execute': 'user_def_cmd2' }"); + + /* extra arguments */ + do_qmp_dispatch_error(false, ERROR_CLASS_GENERIC_ERROR, + "{ 'execute': 'user_def_cmd'," + " 'arguments': { 'a': 66 } }"); } static void test_dispatch_cmd_success_response(void) @@ -218,43 +236,18 @@ static void test_dispatch_cmd_success_response(void) qobject_unref(req); } -static QObject *test_qmp_dispatch(QDict *req) -{ - QDict *resp; - QObject *ret; - - resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false); - assert(resp && !qdict_haskey(resp, "error")); - ret = qdict_get(resp, "return"); - assert(ret); - qobject_ref(ret); - qobject_unref(resp); - return ret; -} - /* test commands that involve both input parameters and return values */ static void test_dispatch_cmd_io(void) { - QDict *req = qdict_new(); - QDict *args = qdict_new(); - QDict *args3 = qdict_new(); - QDict *ud1a = qdict_new(); - QDict *ud1b = qdict_new(); QDict *ret, *ret_dict, *ret_dict_dict, *ret_dict_dict_userdef; QDict *ret_dict_dict2, *ret_dict_dict2_userdef; QNum *ret3; int64_t val; - qdict_put_int(ud1a, "integer", 42); - qdict_put_str(ud1a, "string", "hello"); - qdict_put_int(ud1b, "integer", 422); - qdict_put_str(ud1b, "string", "hello2"); - qdict_put(args, "ud1a", ud1a); - qdict_put(args, "ud1b", ud1b); - qdict_put(req, "arguments", args); - qdict_put_str(req, "execute", "user_def_cmd2"); - - ret = qobject_to(QDict, test_qmp_dispatch(req)); + ret = qobject_to(QDict, do_qmp_dispatch(false, + "{ 'execute': 'user_def_cmd2', 'arguments': {" + " 'ud1a': { 'integer': 42, 'string': 'hello' }," + " 'ud1b': { 'integer': 422, 'string': 'hello2' } } }")); assert(!strcmp(qdict_get_str(ret, "string0"), "blah1")); ret_dict = qdict_get_qdict(ret, "dict1"); @@ -271,16 +264,11 @@ static void test_dispatch_cmd_io(void) assert(!strcmp(qdict_get_str(ret_dict_dict2, "string"), "blah4")); qobject_unref(ret); - qdict_put_int(args3, "a", 66); - qdict_put(req, "arguments", args3); - qdict_put_str(req, "execute", "guest-get-time"); - - ret3 = qobject_to(QNum, test_qmp_dispatch(req)); + ret3 = qobject_to(QNum, do_qmp_dispatch(false, + "{ 'execute': 'guest-get-time', 'arguments': { 'a': 66 } }")); g_assert(qnum_get_try_int(ret3, &val)); g_assert_cmpint(val, ==, 66); qobject_unref(ret3); - - qobject_unref(req); } /* test generated dealloc functions for generated types */ diff --git a/tests/test-qmp-event.c b/tests/test-qmp-event.c index eee7e08ab6..7dd0053190 100644 --- a/tests/test-qmp-event.c +++ b/tests/test-qmp-event.c @@ -17,6 +17,7 @@ #include "qapi/error.h" #include "qapi/qmp/qbool.h" #include "qapi/qmp/qdict.h" +#include "qapi/qmp/qjson.h" #include "qapi/qmp/qnum.h" #include "qapi/qmp/qstring.h" #include "qapi/qmp-event.h" @@ -25,75 +26,12 @@ typedef struct TestEventData { QDict *expect; + bool emitted; } TestEventData; -typedef struct QDictCmpData { - QDict *expect; - bool result; -} QDictCmpData; - TestEventData *test_event_data; static GMutex test_event_lock; -/* Only compares bool, int, string */ -static -void qdict_cmp_do_simple(const char *key, QObject *obj1, void *opaque) - -{ - QObject *obj2; - QDictCmpData d_new, *d = opaque; - int64_t val1, val2; - - if (!d->result) { - return; - } - - obj2 = qdict_get(d->expect, key); - if (!obj2) { - d->result = false; - return; - } - - if (qobject_type(obj1) != qobject_type(obj2)) { - d->result = false; - return; - } - - switch (qobject_type(obj1)) { - case QTYPE_QBOOL: - d->result = (qbool_get_bool(qobject_to(QBool, obj1)) == - qbool_get_bool(qobject_to(QBool, obj2))); - return; - case QTYPE_QNUM: - g_assert(qnum_get_try_int(qobject_to(QNum, obj1), &val1)); - g_assert(qnum_get_try_int(qobject_to(QNum, obj2), &val2)); - d->result = val1 == val2; - return; - case QTYPE_QSTRING: - d->result = g_strcmp0(qstring_get_str(qobject_to(QString, obj1)), - qstring_get_str(qobject_to(QString, obj2))) == 0; - return; - case QTYPE_QDICT: - d_new.expect = qobject_to(QDict, obj2); - d_new.result = true; - qdict_iter(qobject_to(QDict, obj1), qdict_cmp_do_simple, &d_new); - d->result = d_new.result; - return; - default: - abort(); - } -} - -static bool qdict_cmp_simple(QDict *a, QDict *b) -{ - QDictCmpData d; - - d.expect = b; - d.result = true; - qdict_iter(a, qdict_cmp_do_simple, &d); - return d.result; -} - void test_qapi_event_emit(test_QAPIEvent event, QDict *d) { QDict *t; @@ -114,8 +52,8 @@ void test_qapi_event_emit(test_QAPIEvent event, QDict *d) qdict_del(d, "timestamp"); - g_assert(qdict_cmp_simple(d, test_event_data->expect)); - + g_assert(qobject_is_equal(QOBJECT(d), QOBJECT(test_event_data->expect))); + test_event_data->emitted = true; } static void event_prepare(TestEventData *data, @@ -124,17 +62,13 @@ static void event_prepare(TestEventData *data, /* Global variable test_event_data was used to pass the expectation, so test cases can't be executed at same time. */ g_mutex_lock(&test_event_lock); - - data->expect = qdict_new(); test_event_data = data; } static void event_teardown(TestEventData *data, const void *unused) { - qobject_unref(data->expect); test_event_data = NULL; - g_mutex_unlock(&test_event_lock); } @@ -152,90 +86,58 @@ static void event_test_add(const char *testpath, static void test_event_a(TestEventData *data, const void *unused) { - QDict *d; - d = data->expect; - qdict_put_str(d, "event", "EVENT_A"); + data->expect = qdict_from_jsonf_nofail("{ 'event': 'EVENT_A' }"); qapi_event_send_event_a(); + g_assert(data->emitted); + qobject_unref(data->expect); } static void test_event_b(TestEventData *data, const void *unused) { - QDict *d; - d = data->expect; - qdict_put_str(d, "event", "EVENT_B"); + data->expect = qdict_from_jsonf_nofail("{ 'event': 'EVENT_B' }"); qapi_event_send_event_b(); + g_assert(data->emitted); + qobject_unref(data->expect); } static void test_event_c(TestEventData *data, const void *unused) { - QDict *d, *d_data, *d_b; - - UserDefOne b; - b.integer = 2; - b.string = g_strdup("test1"); - b.has_enum1 = false; - - d_b = qdict_new(); - qdict_put_int(d_b, "integer", 2); - qdict_put_str(d_b, "string", "test1"); - - d_data = qdict_new(); - qdict_put_int(d_data, "a", 1); - qdict_put(d_data, "b", d_b); - qdict_put_str(d_data, "c", "test2"); - - d = data->expect; - qdict_put_str(d, "event", "EVENT_C"); - qdict_put(d, "data", d_data); + UserDefOne b = { .integer = 2, .string = (char *)"test1" }; + data->expect = qdict_from_jsonf_nofail( + "{ 'event': 'EVENT_C', 'data': {" + " 'a': 1, 'b': { 'integer': 2, 'string': 'test1' }, 'c': 'test2' } }"); qapi_event_send_event_c(true, 1, true, &b, "test2"); - - g_free(b.string); + g_assert(data->emitted); + qobject_unref(data->expect); } /* Complex type */ static void test_event_d(TestEventData *data, const void *unused) { - UserDefOne struct1; - EventStructOne a; - QDict *d, *d_data, *d_a, *d_struct1; - - struct1.integer = 2; - struct1.string = g_strdup("test1"); - struct1.has_enum1 = true; - struct1.enum1 = ENUM_ONE_VALUE1; - - a.struct1 = &struct1; - a.string = g_strdup("test2"); - a.has_enum2 = true; - a.enum2 = ENUM_ONE_VALUE2; - - d_struct1 = qdict_new(); - qdict_put_int(d_struct1, "integer", 2); - qdict_put_str(d_struct1, "string", "test1"); - qdict_put_str(d_struct1, "enum1", "value1"); - - d_a = qdict_new(); - qdict_put(d_a, "struct1", d_struct1); - qdict_put_str(d_a, "string", "test2"); - qdict_put_str(d_a, "enum2", "value2"); - - d_data = qdict_new(); - qdict_put(d_data, "a", d_a); - qdict_put_str(d_data, "b", "test3"); - qdict_put_str(d_data, "enum3", "value3"); - - d = data->expect; - qdict_put_str(d, "event", "EVENT_D"); - qdict_put(d, "data", d_data); - + UserDefOne struct1 = { + .integer = 2, .string = (char *)"test1", + .has_enum1 = true, .enum1 = ENUM_ONE_VALUE1, + }; + EventStructOne a = { + .struct1 = &struct1, + .string = (char *)"test2", + .has_enum2 = true, + .enum2 = ENUM_ONE_VALUE2, + }; + + data->expect = qdict_from_jsonf_nofail( + "{ 'event': 'EVENT_D', 'data': {" + " 'a': {" + " 'struct1': { 'integer': 2, 'string': 'test1', 'enum1': 'value1' }," + " 'string': 'test2', 'enum2': 'value2' }," + " 'b': 'test3', 'enum3': 'value3' } }"); qapi_event_send_event_d(&a, "test3", false, NULL, true, ENUM_ONE_VALUE3); - - g_free(struct1.string); - g_free(a.string); + g_assert(data->emitted); + qobject_unref(data->expect); } int main(int argc, char **argv) |