diff mbox

[v3,3/3] qmp: full introspection support for QMP

Message ID 1388923351-10556-4-git-send-email-akong@redhat.com
State New
Headers show

Commit Message

Amos Kong Jan. 5, 2014, 12:02 p.m. UTC
This patch introduces a new monitor command to query QMP schema
information, the return data is a range of schema structs, which
contains the useful metadata to help management to check supported
features, QMP commands detail, etc.

It parses all json definition in qapi-schema.json, and generate a
dynamic struct tree, QMP infrastructure will convert the tree to
json string and return to QMP client.

I defined a 'DataObject' union in qapi-schema.json, it's used to
describe the dynamic data struct.

I also added a document about QMP full introspection support
(docs/qmp-full-introspection.txt), it helps to use the new interface
and understand the abstract method in describing the dynamic struct.

TODO:
Wenchao Xia is working to convert QMP events to qapi-schema.json,
then event can also be queried by this interface.

I will introduce another command 'query-qga-schema' to query QGA
schema information, it's easy to add this support based on this
patch.

Signed-off-by: Amos Kong <akong@redhat.com>
---
 docs/qmp-full-introspection.txt |  97 ++++++++++
 qapi-schema.json                | 150 ++++++++++++++++
 qmp-commands.hx                 |  43 ++++-
 qmp.c                           | 382 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 671 insertions(+), 1 deletion(-)
 create mode 100644 docs/qmp-full-introspection.txt

Comments

Fam Zheng Jan. 6, 2014, 9:37 a.m. UTC | #1
On 2014年01月05日 20:02, Amos Kong wrote:
> This patch introduces a new monitor command to query QMP schema
> information, the return data is a range of schema structs, which
> contains the useful metadata to help management to check supported
> features, QMP commands detail, etc.
>
> It parses all json definition in qapi-schema.json, and generate a
> dynamic struct tree, QMP infrastructure will convert the tree to
> json string and return to QMP client.
>
> I defined a 'DataObject' union in qapi-schema.json, it's used to
> describe the dynamic data struct.
>
> I also added a document about QMP full introspection support
> (docs/qmp-full-introspection.txt), it helps to use the new interface
> and understand the abstract method in describing the dynamic struct.
>
> TODO:
> Wenchao Xia is working to convert QMP events to qapi-schema.json,
> then event can also be queried by this interface.
>
> I will introduce another command 'query-qga-schema' to query QGA
> schema information, it's easy to add this support based on this
> patch.
>
> Signed-off-by: Amos Kong <akong@redhat.com>
> ---

I have a few comments on the current implementation below, there are a 
few things to improve. However I agree to what Eric suggested in reply 
to V2: it may be better to generate most of the response data in python 
code at compile time and simplify the logic in C. Because this 
implementation is slow and it is unnecessary runtime computation. It 
also duplicates much of existing qapi.py logic (data types and other 
semantics parsing).

>   docs/qmp-full-introspection.txt |  97 ++++++++++
>   qapi-schema.json                | 150 ++++++++++++++++
>   qmp-commands.hx                 |  43 ++++-
>   qmp.c                           | 382 ++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 671 insertions(+), 1 deletion(-)
>   create mode 100644 docs/qmp-full-introspection.txt
>
> diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
> new file mode 100644
> index 0000000..1617df7
> --- /dev/null
> +++ b/docs/qmp-full-introspection.txt
> @@ -0,0 +1,97 @@
> += Full introspection support for QMP =
> +
> +
> +== Purpose ==
> +
> +Add a new monitor command for management to  query QMP schema
> +information, it returns a range of schema structs, which contain the
> +useful metadata to help management to check supported features, QMP
> +commands detail, etc.
> +
> +== Usage ==
> +
> +Json schema:
> +  { 'type': 'NameInfo', 'data': {'*name': 'str'} }
> +  { 'command': 'query-name', 'returns': 'NameInfo' }
> +
> +Execute QMP command:
> +
> +  { "execute": "query-qmp-schema" }
> +
> +Returns:
> +
> +  { "return": [
> +      {
> +          "name": "query-name",
> +          "type": "command",
> +          "returns": {
> +              "name": "NameInfo",
> +              "type": "type",
> +              "data": [
> +                  {
> +                      "name": "name",
> +                      "optional": true,
> +                      "recursive": false,
> +                      "type": "str"
> +                  }
> +              ]
> +          }
> +      },
> +      ...
> +   }
> +
> +The whole schema information will be returned in one go, it contains
> +all the schema entries. It doesn't support to be filtered by type
> +or name. Currently it takes about 5 seconds to return about 1.5M string.
> +
> +== 'DataObject' union ==
> +
> +{ 'union': 'DataObject',
> +  'base': 'DataObjectBase',
> +  'discriminator': 'type',
> +  'data': {
> +    'anonymous-struct': 'DataObjectAnonymousStruct',
> +    'command': 'DataObjectCommand',
> +    'enumeration': 'DataObjectEnumeration',
> +    'reference-type': 'String',
> +    'type': 'DataObjectType',
> +    'unionobj': 'DataObjectUnion' } }
> +
> +Currently we have schema difinitions for type, command, enumeration,
> +union. Some arbitrary structs (dictionary, list or string) and native
> +types are also used in the body of definitions.
> +
> +Here we use "DataObject" union to abstract all above schema. We want
> +to provide more useful metadata, and used some enum/unions to indicate
> +the dynamic type. In the output, some simple data is processed too
> +unwieldy. In another side, some complex data is described clearly.
> +It's also caused by some limitation of QAPI infrastructure.
> +
> +So we define 'DataObject' to be an union, it always has an object name
> +except anonymous struct.
> +
> +'command', 'enumeration', 'type', 'unionobj' are common schema type,
> +'union' is a build-in type, so I used unionobj here.
> +
> +'reference-type' will be used to describe native types and unextended
> +types.
> +
> +'anonymous-struct' will be used to describe arbitrary structs
> +(dictionary, list or string).
> +
> +== Avoid dead loop in recursive extending ==
> +
> +We have four types (ImageInfo, BlockStats, PciDeviceInfo, ObjectData)
> +that uses themself in their own define data directly or indirectly,
> +we will not repeatedly extend them to avoid dead loop.
> +
> +We use a string to record the visit path, type index of each node
> +will be saved to the string, indexes are split by ':'.
> +
> +Push index to visit_path_str before extending, and pop index from
> +visit_path_str after extending.
> +
> +If the type was already extended in parent node, we don't extend it
> +again to avoid dead loop.
> +
> +'recursive' indicates if the type is extended or not.
> diff --git a/qapi-schema.json b/qapi-schema.json
> index c3c939c..db500ab 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -4235,3 +4235,153 @@
>   # Since: 1.7
>   ##
>   { 'command': 'blockdev-add', 'data': { 'options': 'BlockdevOptions' } }
> +
> +##
> +# @DataObjectBase
> +#
> +# Base of @DataObject
> +#
> +# @name: #optional @DataObject name
> +# @type: @DataObject type
> +#
> +# Since: 1.8
> +##
> +{ 'type': 'DataObjectBase',
> +  'data': { '*name': 'str', 'type': 'str' } }
> +
> +##
> +# @DataObjectMemberType
> +#
> +# Type of @DabaObjectMember
> +#
> +# @reference: reference string
> +# @anonymous: arbitrary struct
> +# @extend: the @DataObjectMember
> +#
> +# Since: 1.8
> +##
> +{ 'union': 'DataObjectMemberType',
> +  'discriminator': {},
> +  'data': { 'reference': 'str',
> +            'anonymous': 'DataObject',
> +            'extend': 'DataObject' } }
> +
> +##
> +# @DataObjectMember
> +#
> +# General member of @DataObject
> +#
> +# @type: type of @DataObjectMember
> +# @name: #optional name
> +# @optional: #optional key to indicate if the @DataObjectMember is optional
> +# @recursive: #optional key to indicate if it's defined recursively
> +#
> +# Since: 1.8
> +##
> +{ 'type': 'DataObjectMember',
> +  'data': { 'type': 'DataObjectMemberType', '*name': 'str',
> +            '*optional': 'bool', '*recursive': 'bool' } }
> +
> +##
> +# @DataObjectAnonymousStruct
> +#
> +# Arbitrary struct, it can be dictionary, list or string
> +#
> +# @data: content of arbitrary struct
> +#
> +# Since: 1.8
> +##
> +{ 'type': 'DataObjectAnonymousStruct',
> +  'data': { 'data': [ 'DataObjectMember' ] } }
> +
> +##
> +# @DataObjectCommand
> +#
> +# QMP Command schema
> +#
> +# @data: QMP command content
> +# @returns: returns of executing command
> +# @gen: a key to suppress code generation
> +#
> +# Since: 1.8
> +##
> +{ 'type': 'DataObjectCommand',
> +  'data': { '*data': [ 'DataObjectMember' ],
> +            '*returns': 'DataObject',
> +            '*gen': 'bool' } }
> +
> +##
> +# @DataObjectEnumeration
> +#
> +# Enumeration schema
> +#
> +# @data: enumeration content, it's a string list
> +#
> +# Since: 1.8
> +##
> +{ 'type': 'DataObjectEnumeration',
> +  'data': { 'data': [ 'str' ] } }
> +
> +##
> +# @DataObjectType
> +#
> +# Type schema
> +#
> +# @data: type content
> +#
> +# Since: 1.8
> +##
> +{ 'type': 'DataObjectType',
> +  'data': { 'data': [ 'DataObjectMember' ] } }
> +
> +##
> +# @DataObjectUnion
> +#
> +# Union schema
> +#
> +# @data: union content
> +# @base: union base
> +# @discriminator: union discriminator
> +#
> +# Since: 1.8
> +##
> +{ 'type': 'DataObjectUnion',
> +  'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
> +            '*discriminator': 'str' } }
> +
> +##
> +# @DataObject
> +#
> +# Dynamic data struct, it can be command, enumeration, type, union, arbitrary
> +# struct or native type.
> +#
> +# @anonymous-struct: arbitrary struct, it can be dictionary, list or string
> +# @command: QMP command schema
> +# @enumeration: enumeration schema
> +# @reference-type: native type or unextended type
> +# @type: type schema, it will be extended
> +# @unionobj: union schema
> +#
> +# Since: 1.8
> +##
> +{ 'union': 'DataObject',
> +  'base': 'DataObjectBase',
> +  'discriminator': 'type',
> +  'data': {
> +    'anonymous-struct': 'DataObjectAnonymousStruct',
> +    'command': 'DataObjectCommand',
> +    'enumeration': 'DataObjectEnumeration',
> +    'reference-type': 'String',
> +    'type': 'DataObjectType',
> +    'unionobj': 'DataObjectUnion' } }
> +
> +##
> +# @query-qmp-schema
> +#
> +# Query QMP schema information
> +#
> +# @returns: list of @DataObject
> +#
> +# Since: 1.8
> +##
> +{ 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index fba15cd..cf9c4aa 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -3237,7 +3237,48 @@ Example:
>               "broadcast-allowed": false
>           }
>         ]
> -   }
> +
> +EQMP
> +    {
> +        .name       = "query-qmp-schema",
> +        .args_type  = "",
> +        .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
> +    },
> +
> +
> +SQMP
> +query-qmp-schema
> +----------------
> +
> +query qmp schema information
> +
> +Return a json-object with the following information:
> +
> +- "name": qmp schema name (json-string)
> +- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union'
> +- "returns": return data of qmp command (json-object, optional)
> +
> +Example:
> +
> +-> { "execute": "query-qmp-schema" }
> +-> { "return": [
> +     {
> +         "name": "query-name",
> +         "type": "command",
> +         "returns": {
> +             "name": "NameInfo",
> +             "type": "type",
> +             "data": [
> +                 {
> +                     "name": "name",
> +                     "optional": true,
> +                     "recursive": false,
> +                     "type": "str"
> +                 }
> +             ]
> +         }
> +     }
> +  }
>
>   EQMP
>
> diff --git a/qmp.c b/qmp.c
> index 1d7a04d..e9bba06 100644
> --- a/qmp.c
> +++ b/qmp.c
> @@ -25,6 +25,8 @@
>   #include "sysemu/blockdev.h"
>   #include "qom/qom-qobject.h"
>   #include "hw/boards.h"
> +#include "qmp-schema.h"
> +#include "qapi/qmp/qjson.h"
>
>   NameInfo *qmp_query_name(Error **errp)
>   {
> @@ -486,6 +488,386 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
>       return arch_query_cpu_definitions(errp);
>   }
>
> +/*
> + * use a passed string to record the visit path, schema index of
> + * each extended node will be appended to the string, indexes are
> + * split by ':'
> + */
> +static char visit_path_str[1024];

Is it safe to use a global variable here? Is there any race condition 
(i.e. multiple monitor commands run parallel)?

IIUC this serves as a queue of number? Why not use array of int?

> +
> +/* push the type index into visit_path_str */
> +static void push_id(int id)
> +{
> +    char *end = strrchr(visit_path_str, ':');
> +    char type_idx[256];
> +    int num;
> +
> +    num = sprintf(type_idx, "%d:", id);
> +
> +    if (end) {
> +        /* avoid overflow */
> +        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
> +        sprintf(end + 1, "%d:", id);
> +    } else {
> +        sprintf(visit_path_str, "%d:", id);
> +    }
> +}
> +
> +/* pop the type index from visit_path_str */
> +static void pop_id(void)
> +{
> +    char *p = strrchr(visit_path_str, ':');
> +
> +    assert(p != NULL);
> +    *p = '\0';
> +    p = strrchr(visit_path_str, ':');
> +    if (p) {
> +        *(p + 1) = '\0';
> +    } else {
> +        visit_path_str[0] = '\0';
> +    }
> +}
> +
> +static const char *qstring_copy_str(QObject *data)

The string is not copied, it's misleading. And I think you could just 
use qstring_get_str in the caller or define a new qstring_ util function 
in qobject/qstring.c.

> +{
> +    QString *qstr;
> +
> +    if (!data) {
> +        return NULL;
> +    }
> +    qstr = qobject_to_qstring(data);
> +    if (qstr) {
> +        return qstring_get_str(qstr);
> +    } else {
> +        return NULL;
> +    }
> +}
> +
> +static QObject *get_definition(const char *str, bool update_path)
> +{
> +    QObject *data, *value;
> +    QDict *qdict;
> +    int i;
> +
> +    if (!strcmp(str, "str") || !strcmp(str, "int") ||
> +        !strcmp(str, "number") || !strcmp(str, "bool") ||
> +        !strcmp(str, "int8") || !strcmp(str, "int16") ||
> +        !strcmp(str, "int32") || !strcmp(str, "int64") ||
> +        !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
> +        !strcmp(str, "uint32") || !strcmp(str, "uint64") ||
> +        !strcmp(str, "visitor") || !strcmp(str, "**") ||
> +        !strcmp(str, "size")) {
> +        return NULL;
> +    }
> +
> +    for (i = 0; qmp_schema_table[i]; i++) {
> +        data = qobject_from_json(qmp_schema_table[i]);
> +        qdict = qobject_to_qdict(data);
> +        assert(qdict != NULL);
> +
> +        if (qdict_get(qdict, "enum")) {
> +            value = qdict_get(qdict, "enum");
> +        } else if (qdict_get(qdict, "type")) {
> +            value = qdict_get(qdict, "type");
> +        } else if (qdict_get(qdict, "union")) {
> +            value = qdict_get(qdict, "union");
> +        } else {
> +            continue;
> +        }
> +
> +        if (!strcmp(str, qstring_copy_str(value)) && update_path) {

Simply:

if (strcmp(str, qstring_copy_str(value)) {
     continue;
}

Then avoid another check for this.

> +            char *start, *end;
> +            char cur_idx[256];
> +            char type_idx[256];
> +
> +            start = visit_path_str;
> +            sprintf(type_idx, "%d", i);

Let's avoid sprintf completely.

> +            while (start) {
> +                end = strchr(start, ':');
> +                if (!end) {
> +                    break;
> +                }
> +                snprintf(cur_idx, end - start + 1, "%s", start);
> +                start = end + 1;
> +                /* if the type was already extended in parent node,
> +                 * we don't extend it again to avoid dead loop. */
> +                if (!strcmp(cur_idx, type_idx)) {
> +                    return NULL;
> +                }
> +            }

Using array of int will get you a simpler code here.

Furthermore, you could avoid linear lookup by using a bitmap beside 
visit_path_str to keep track of what is visited.

> +            /* push index to visit_path_str before extending */
> +            push_id(i);
> +        }
> +        if (!strcmp(str, qstring_copy_str(value))) {
> +
> +            return qobject_from_json(qmp_schema_table[i]);
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static DataObject *visit_qobj_dict(QObject *data);
> +static DataObject *visit_qobj_list(QObject *data);
> +
> +/* extend defined type to json object */
> +static DataObject *extend_type(const char *str)
> +{
> +    QObject *data;
> +    DataObject *obj;
> +
> +    data = get_definition(str, true);
> +
> +    if (data) {
> +        obj = visit_qobj_dict(data);
> +        pop_id();
> +    } else {
> +        obj = g_malloc0(sizeof(struct DataObject));
> +        obj->kind = DATA_OBJECT_KIND_REFERENCE_TYPE;
> +        obj->reference_type = g_malloc0(sizeof(String));
> +        obj->reference_type->str = g_strdup(str);
> +    }
> +
> +    return obj;
> +}
> +
> +static DataObjectMemberList *list_to_memberlist(QObject *data)
> +{
> +    DataObjectMemberList *mem_list, *entry, *last_entry;
> +    QList *qlist;
> +    const QListEntry *lent;
> +
> +        qlist = qobject_to_qlist(data);

Bad indentation starting from here.

> +
> +        mem_list = NULL;
> +        for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
> +            entry = g_malloc0(sizeof(DataObjectMemberList *));

Pointer size allocated instead of structure.

> +            entry->value = g_malloc0(sizeof(DataObjectMember));
> +            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
> +
> +            if (get_definition(qstring_copy_str(lent->value), false)) {
> +                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> +                entry->value->has_recursive = true;
> +                entry->value->recursive = true;
> +                entry->value->type->extend =
> +                    extend_type(qstring_copy_str(lent->value));
> +            } else {
> +                entry->value->type->kind =
> +                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
> +                entry->value->has_recursive = true;
> +                entry->value->recursive = false;
> +                entry->value->type->reference =
> +                    g_strdup(qstring_copy_str(lent->value));
> +            }
> +
> +            entry->next = NULL;
> +            if (!mem_list) {
> +                mem_list = entry;
> +            } else {
> +                last_entry->next = entry;
> +            }
> +            last_entry = entry;
> +        }
> +        return mem_list;
> +}
> +
> +static DataObjectMemberList *dict_to_memberlist(QObject *data)
> +{
> +    DataObjectMemberList *mem_list, *entry, *last_entry;
> +    QDict *qdict;
> +    const QDictEntry *dent;
> +
> +        qdict = qobject_to_qdict(data);
> +
> +        mem_list = NULL;
> +        for (dent = qdict_first(qdict); dent; dent = qdict_next(qdict, dent)) {
> +            entry = g_malloc0(sizeof(DataObjectMemberList *));

Same here.

> +            entry->value = g_malloc0(sizeof(DataObjectMember));
> +
> +            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
> +
> +            if (dent->value->type->code == QTYPE_QDICT) {
> +                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> +                entry->value->type->extend = visit_qobj_dict(dent->value);
> +            } else if (dent->value->type->code == QTYPE_QLIST) {
> +                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> +                entry->value->type->extend = visit_qobj_list(dent->value);
> +            } else if (get_definition(qstring_copy_str(dent->value), false)) {
> +                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> +                entry->value->has_recursive = true;
> +                entry->value->recursive = true;
> +                entry->value->type->extend =
> +                    extend_type(qstring_copy_str(dent->value));
> +            } else {
> +                entry->value->type->kind =
> +                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
> +                entry->value->has_recursive = true;
> +                entry->value->recursive = false;
> +                entry->value->type->reference =
> +                    g_strdup(qstring_copy_str(dent->value));
> +            }
> +            entry->value->has_optional = true;
> +            entry->value->has_name = true;
> +            if (dent->key[0] == '*') {
> +                entry->value->optional = true;
> +                entry->value->name = g_strdup(dent->key + 1);
> +            } else {
> +                entry->value->name = g_strdup(dent->key);
> +            }
> +
> +            entry->next = NULL;
> +            if (!mem_list) {
> +                mem_list = entry;
> +            } else {
> +                last_entry->next = entry;
> +            }
> +            last_entry = entry;
> +        }
> +        return mem_list;
> +}
> +
> +static DataObject *visit_qobj_list(QObject *data)
> +{
> +    DataObject *obj;
> +
> +    obj = g_malloc0(sizeof(struct DataObject));
> +    obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
> +    obj->anonymous_struct = g_malloc0(sizeof(struct
> +                                             DataObjectAnonymousStruct));
> +    obj->anonymous_struct->data = list_to_memberlist(data);
> +
> +    return obj;
> +}
> +
> +static strList *get_str_list(QObject *data)
> +{
> +    strList *str_list, *last_str_entry, *str_entry;
> +    QList *qlist;
> +    const QListEntry *lent;
> +
> +    qlist = qobject_to_qlist(data);
> +    str_list = NULL;
> +    for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
> +        str_entry = g_malloc0(sizeof(strList *));

And here.

> +        str_entry->value = g_strdup(qstring_copy_str(lent->value));
> +        str_entry->next = NULL;
> +        if (!str_list) {
> +            str_list = str_entry;
> +        } else {
> +            last_str_entry->next = str_entry;
> +        }
> +        last_str_entry = str_entry;
> +    }
> +
> +    return str_list;
> +}
> +
> +static DataObject *visit_qobj_dict(QObject *data)
> +{
> +    DataObject *obj;
> +    QObject *subdata;
> +    QDict *qdict;
> +
> +    qdict = qobject_to_qdict(data);
> +    assert(qdict != NULL);
> +    obj = g_malloc0(sizeof(*obj));
> +
> +    if (qdict_get(qdict, "command")) {
> +        obj->kind = DATA_OBJECT_KIND_COMMAND;
> +        obj->has_name = true;
> +        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "command")));
> +        obj->command = g_malloc0(sizeof(struct DataObjectCommand));
> +
> +        subdata = qdict_get(qdict, "data");
> +        if (subdata && subdata->type->code == QTYPE_QDICT) {
> +            obj->command->has_data = true;
> +            obj->command->data = dict_to_memberlist(subdata);
> +        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
> +            abort();
> +        } else if (subdata) {
> +            obj->command->has_data = true;
> +            obj->command->data =
> +                dict_to_memberlist(get_definition(qstring_copy_str(subdata),
> +                                                  true));
> +            pop_id();
> +        }
> +
> +        subdata = qdict_get(qdict, "returns");
> +        if (subdata && subdata->type->code == QTYPE_QDICT) {
> +            abort();
> +        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
> +            obj->command->has_returns = true;
> +            obj->command->returns = visit_qobj_list(subdata);
> +        } else if (subdata && subdata->type->code == QTYPE_QSTRING) {
> +            obj->command->has_returns = true;
> +            obj->command->returns = extend_type(qstring_copy_str(subdata));
> +        }
> +
> +        subdata = qdict_get(qdict, "gen");
> +        if (subdata && subdata->type->code == QTYPE_QSTRING) {
> +            obj->command->has_gen = true;
> +            if (!strcmp(qstring_copy_str(subdata), "no")) {
> +                obj->command->gen = false;
> +            } else {
> +                obj->command->gen = true;
> +            }
> +        }
> +    } else if (qdict_get(qdict, "union")) {
> +        obj->kind = DATA_OBJECT_KIND_UNIONOBJ;
> +        obj->has_name = true;
> +        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "union")));
> +        obj->unionobj = g_malloc0(sizeof(struct DataObjectUnion));
> +        subdata = qdict_get(qdict, "data");
> +        obj->unionobj->data = dict_to_memberlist(subdata);
> +    } else if (qdict_get(qdict, "type")) {
> +        obj->kind = DATA_OBJECT_KIND_TYPE;
> +        obj->has_name = true;
> +        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "type")));
> +        obj->type = g_malloc0(sizeof(struct DataObjectType));
> +        subdata = qdict_get(qdict, "data");
> +        obj->type->data = dict_to_memberlist(subdata);
> +    } else if (qdict_get(qdict, "enum")) {
> +        obj->kind = DATA_OBJECT_KIND_ENUMERATION;
> +        obj->has_name = true;
> +        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "enum")));
> +        obj->enumeration = g_malloc0(sizeof(struct DataObjectEnumeration));
> +        subdata = qdict_get(qdict, "data");
> +        obj->enumeration->data = get_str_list(subdata);
> +    } else {
> +        obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
> +        obj->anonymous_struct = g_malloc0(sizeof(struct
> +                                                 DataObjectAnonymousStruct));
> +        obj->anonymous_struct->data = dict_to_memberlist(data);
> +    }
> +
> +    return obj;
> +}
> +
> +DataObjectList *qmp_query_qmp_schema(Error **errp)
> +{
> +    DataObjectList *list, *last_entry, *entry;
> +    QObject *data;
> +    int i;
> +
> +    list = NULL;
> +    for (i = 0; qmp_schema_table[i]; i++) {
> +        data = qobject_from_json(qmp_schema_table[i]);
> +        assert(data != NULL);
> +
> +        entry = g_malloc0(sizeof(DataObjectList *));

And here.

> +        memset(visit_path_str, 0, sizeof(visit_path_str));
> +        entry->value = visit_qobj_dict(data);
> +        entry->next = NULL;
> +        if (!list) {

Why not use a pointer to pointer to avoid this if branch?

> +            list = entry;
> +        } else {
> +            last_entry->next = entry;
> +        }
> +        last_entry = entry;
> +    }
> +
> +    return list;
> +}
> +
>   void qmp_add_client(const char *protocol, const char *fdname,
>                       bool has_skipauth, bool skipauth, bool has_tls, bool tls,
>                       Error **errp)
>

Fam
Amos Kong Jan. 9, 2014, 9:49 a.m. UTC | #2
On Mon, Jan 06, 2014 at 05:37:19PM +0800, Fam Zheng wrote:
> On 2014年01月05日 20:02, Amos Kong wrote:
> >This patch introduces a new monitor command to query QMP schema
> >information, the return data is a range of schema structs, which
> >contains the useful metadata to help management to check supported
> >features, QMP commands detail, etc.
> >
> >It parses all json definition in qapi-schema.json, and generate a
> >dynamic struct tree, QMP infrastructure will convert the tree to
> >json string and return to QMP client.
> >
> >I defined a 'DataObject' union in qapi-schema.json, it's used to
> >describe the dynamic data struct.
> >
> >I also added a document about QMP full introspection support
> >(docs/qmp-full-introspection.txt), it helps to use the new interface
> >and understand the abstract method in describing the dynamic struct.
> >
> >TODO:
> >Wenchao Xia is working to convert QMP events to qapi-schema.json,
> >then event can also be queried by this interface.
> >
> >I will introduce another command 'query-qga-schema' to query QGA
> >schema information, it's easy to add this support based on this
> >patch.
> >
> >Signed-off-by: Amos Kong <akong@redhat.com>
> >---

Hi Fam,
 
I'm very happy to get your comments!

> I have a few comments on the current implementation below, there are
> a few things to improve. However I agree to what Eric suggested in
> reply to V2: it may be better to generate most of the response data
> in python code at compile time and simplify the logic in C. Because
> this implementation is slow and it is unnecessary runtime
> computation. It also duplicates much of existing qapi.py logic (data
> types and other semantics parsing).

The implemented code of QMP command will generate defined struct,
the memory is allocated in run time, it will be auto released by
QMP infrastructure.

We can implement the parse/extend work by Python and generate
a result in a head file, that can be directly & easyly used C code.
Maybe we can generate a nested & complex struct definition in the
head file, and use less g_malloc() to generate return struct.
I will try, thanks.
 
> >  docs/qmp-full-introspection.txt |  97 ++++++++++
> >  qapi-schema.json                | 150 ++++++++++++++++
> >  qmp-commands.hx                 |  43 ++++-
> >  qmp.c                           | 382 ++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 671 insertions(+), 1 deletion(-)
> >  create mode 100644 docs/qmp-full-introspection.txt
> >
> >diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
> >new file mode 100644
> >index 0000000..1617df7
> >--- /dev/null
> >+++ b/docs/qmp-full-introspection.txt
> >@@ -0,0 +1,97 @@
> >+= Full introspection support for QMP =
> >+
> >+
> >+== Purpose ==
> >+
> >+Add a new monitor command for management to  query QMP schema
> >+information, it returns a range of schema structs, which contain the
> >+useful metadata to help management to check supported features, QMP
> >+commands detail, etc.
> >+
> >+== Usage ==
> >+
> >+Json schema:
> >+  { 'type': 'NameInfo', 'data': {'*name': 'str'} }
> >+  { 'command': 'query-name', 'returns': 'NameInfo' }
> >+
> >+Execute QMP command:
> >+
> >+  { "execute": "query-qmp-schema" }
> >+
> >+Returns:
> >+
> >+  { "return": [
> >+      {
> >+          "name": "query-name",
> >+          "type": "command",
> >+          "returns": {
> >+              "name": "NameInfo",
> >+              "type": "type",
> >+              "data": [
> >+                  {
> >+                      "name": "name",
> >+                      "optional": true,
> >+                      "recursive": false,
> >+                      "type": "str"
> >+                  }
> >+              ]
> >+          }
> >+      },
> >+      ...
> >+   }
> >+
> >+The whole schema information will be returned in one go, it contains
> >+all the schema entries. It doesn't support to be filtered by type
> >+or name. Currently it takes about 5 seconds to return about 1.5M string.
> >+
> >+== 'DataObject' union ==
> >+
> >+{ 'union': 'DataObject',
> >+  'base': 'DataObjectBase',
> >+  'discriminator': 'type',
> >+  'data': {
> >+    'anonymous-struct': 'DataObjectAnonymousStruct',
> >+    'command': 'DataObjectCommand',
> >+    'enumeration': 'DataObjectEnumeration',
> >+    'reference-type': 'String',
> >+    'type': 'DataObjectType',
> >+    'unionobj': 'DataObjectUnion' } }
> >+
> >+Currently we have schema difinitions for type, command, enumeration,
> >+union. Some arbitrary structs (dictionary, list or string) and native
> >+types are also used in the body of definitions.
> >+
> >+Here we use "DataObject" union to abstract all above schema. We want
> >+to provide more useful metadata, and used some enum/unions to indicate
> >+the dynamic type. In the output, some simple data is processed too
> >+unwieldy. In another side, some complex data is described clearly.
> >+It's also caused by some limitation of QAPI infrastructure.
> >+
> >+So we define 'DataObject' to be an union, it always has an object name
> >+except anonymous struct.
> >+
> >+'command', 'enumeration', 'type', 'unionobj' are common schema type,
> >+'union' is a build-in type, so I used unionobj here.
> >+
> >+'reference-type' will be used to describe native types and unextended
> >+types.
> >+
> >+'anonymous-struct' will be used to describe arbitrary structs
> >+(dictionary, list or string).
> >+
> >+== Avoid dead loop in recursive extending ==
> >+
> >+We have four types (ImageInfo, BlockStats, PciDeviceInfo, ObjectData)
> >+that uses themself in their own define data directly or indirectly,
> >+we will not repeatedly extend them to avoid dead loop.
> >+
> >+We use a string to record the visit path, type index of each node
> >+will be saved to the string, indexes are split by ':'.
> >+
> >+Push index to visit_path_str before extending, and pop index from
> >+visit_path_str after extending.
> >+
> >+If the type was already extended in parent node, we don't extend it
> >+again to avoid dead loop.
> >+
> >+'recursive' indicates if the type is extended or not.
> >diff --git a/qapi-schema.json b/qapi-schema.json
> >index c3c939c..db500ab 100644
> >--- a/qapi-schema.json
> >+++ b/qapi-schema.json
> >@@ -4235,3 +4235,153 @@
> >  # Since: 1.7
> >  ##
> >  { 'command': 'blockdev-add', 'data': { 'options': 'BlockdevOptions' } }
> >+
> >+##
> >+# @DataObjectBase
> >+#
> >+# Base of @DataObject
> >+#
> >+# @name: #optional @DataObject name
> >+# @type: @DataObject type
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'type': 'DataObjectBase',
> >+  'data': { '*name': 'str', 'type': 'str' } }
> >+
> >+##
> >+# @DataObjectMemberType
> >+#
> >+# Type of @DabaObjectMember
> >+#
> >+# @reference: reference string
> >+# @anonymous: arbitrary struct
> >+# @extend: the @DataObjectMember
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'union': 'DataObjectMemberType',
> >+  'discriminator': {},
> >+  'data': { 'reference': 'str',
> >+            'anonymous': 'DataObject',
> >+            'extend': 'DataObject' } }
> >+
> >+##
> >+# @DataObjectMember
> >+#
> >+# General member of @DataObject
> >+#
> >+# @type: type of @DataObjectMember
> >+# @name: #optional name
> >+# @optional: #optional key to indicate if the @DataObjectMember is optional
> >+# @recursive: #optional key to indicate if it's defined recursively
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'type': 'DataObjectMember',
> >+  'data': { 'type': 'DataObjectMemberType', '*name': 'str',
> >+            '*optional': 'bool', '*recursive': 'bool' } }
> >+
> >+##
> >+# @DataObjectAnonymousStruct
> >+#
> >+# Arbitrary struct, it can be dictionary, list or string
> >+#
> >+# @data: content of arbitrary struct
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'type': 'DataObjectAnonymousStruct',
> >+  'data': { 'data': [ 'DataObjectMember' ] } }
> >+
> >+##
> >+# @DataObjectCommand
> >+#
> >+# QMP Command schema
> >+#
> >+# @data: QMP command content
> >+# @returns: returns of executing command
> >+# @gen: a key to suppress code generation
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'type': 'DataObjectCommand',
> >+  'data': { '*data': [ 'DataObjectMember' ],
> >+            '*returns': 'DataObject',
> >+            '*gen': 'bool' } }
> >+
> >+##
> >+# @DataObjectEnumeration
> >+#
> >+# Enumeration schema
> >+#
> >+# @data: enumeration content, it's a string list
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'type': 'DataObjectEnumeration',
> >+  'data': { 'data': [ 'str' ] } }
> >+
> >+##
> >+# @DataObjectType
> >+#
> >+# Type schema
> >+#
> >+# @data: type content
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'type': 'DataObjectType',
> >+  'data': { 'data': [ 'DataObjectMember' ] } }
> >+
> >+##
> >+# @DataObjectUnion
> >+#
> >+# Union schema
> >+#
> >+# @data: union content
> >+# @base: union base
> >+# @discriminator: union discriminator
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'type': 'DataObjectUnion',
> >+  'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
> >+            '*discriminator': 'str' } }
> >+
> >+##
> >+# @DataObject
> >+#
> >+# Dynamic data struct, it can be command, enumeration, type, union, arbitrary
> >+# struct or native type.
> >+#
> >+# @anonymous-struct: arbitrary struct, it can be dictionary, list or string
> >+# @command: QMP command schema
> >+# @enumeration: enumeration schema
> >+# @reference-type: native type or unextended type
> >+# @type: type schema, it will be extended
> >+# @unionobj: union schema
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'union': 'DataObject',
> >+  'base': 'DataObjectBase',
> >+  'discriminator': 'type',
> >+  'data': {
> >+    'anonymous-struct': 'DataObjectAnonymousStruct',
> >+    'command': 'DataObjectCommand',
> >+    'enumeration': 'DataObjectEnumeration',
> >+    'reference-type': 'String',
> >+    'type': 'DataObjectType',
> >+    'unionobj': 'DataObjectUnion' } }
> >+
> >+##
> >+# @query-qmp-schema
> >+#
> >+# Query QMP schema information
> >+#
> >+# @returns: list of @DataObject
> >+#
> >+# Since: 1.8
> >+##
> >+{ 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
> >diff --git a/qmp-commands.hx b/qmp-commands.hx
> >index fba15cd..cf9c4aa 100644
> >--- a/qmp-commands.hx
> >+++ b/qmp-commands.hx
> >@@ -3237,7 +3237,48 @@ Example:
> >              "broadcast-allowed": false
> >          }
> >        ]
> >-   }
> >+
> >+EQMP
> >+    {
> >+        .name       = "query-qmp-schema",
> >+        .args_type  = "",
> >+        .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
> >+    },
> >+
> >+
> >+SQMP
> >+query-qmp-schema
> >+----------------
> >+
> >+query qmp schema information
> >+
> >+Return a json-object with the following information:
> >+
> >+- "name": qmp schema name (json-string)
> >+- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union'
> >+- "returns": return data of qmp command (json-object, optional)
> >+
> >+Example:
> >+
> >+-> { "execute": "query-qmp-schema" }
> >+-> { "return": [
> >+     {
> >+         "name": "query-name",
> >+         "type": "command",
> >+         "returns": {
> >+             "name": "NameInfo",
> >+             "type": "type",
> >+             "data": [
> >+                 {
> >+                     "name": "name",
> >+                     "optional": true,
> >+                     "recursive": false,
> >+                     "type": "str"
> >+                 }
> >+             ]
> >+         }
> >+     }
> >+  }
> >
> >  EQMP
> >
> >diff --git a/qmp.c b/qmp.c
> >index 1d7a04d..e9bba06 100644
> >--- a/qmp.c
> >+++ b/qmp.c
> >@@ -25,6 +25,8 @@
> >  #include "sysemu/blockdev.h"
> >  #include "qom/qom-qobject.h"
> >  #include "hw/boards.h"
> >+#include "qmp-schema.h"
> >+#include "qapi/qmp/qjson.h"
> >
> >  NameInfo *qmp_query_name(Error **errp)
> >  {
> >@@ -486,6 +488,386 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
> >      return arch_query_cpu_definitions(errp);
> >  }
> >
> >+/*
> >+ * use a passed string to record the visit path, schema index of
> >+ * each extended node will be appended to the string, indexes are
> >+ * split by ':'
> >+ */
> >+static char visit_path_str[1024];
> 
> Is it safe to use a global variable here? Is there any race
> condition (i.e. multiple monitor commands run parallel)?

This command is only executed once after installing/updating qemu
for Libvirt. But the problem you pointed is existed. I will allocate
/free dynamic memory for each query instance.
 
> IIUC this serves as a queue of number? Why not use array of int?
 
Great! It's easier to understand and update.

> >+
> >+/* push the type index into visit_path_str */
> >+static void push_id(int id)
> >+{
> >+    char *end = strrchr(visit_path_str, ':');
> >+    char type_idx[256];
> >+    int num;
> >+
> >+    num = sprintf(type_idx, "%d:", id);
> >+
> >+    if (end) {
> >+        /* avoid overflow */
> >+        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
> >+        sprintf(end + 1, "%d:", id);
> >+    } else {
> >+        sprintf(visit_path_str, "%d:", id);
> >+    }
> >+}
> >+
> >+/* pop the type index from visit_path_str */
> >+static void pop_id(void)
> >+{
> >+    char *p = strrchr(visit_path_str, ':');
> >+
> >+    assert(p != NULL);
> >+    *p = '\0';
> >+    p = strrchr(visit_path_str, ':');
> >+    if (p) {
> >+        *(p + 1) = '\0';
> >+    } else {
> >+        visit_path_str[0] = '\0';
> >+    }
> >+}
> >+
> >+static const char *qstring_copy_str(QObject *data)
> 
> The string is not copied, it's misleading. And I think you could
> just use qstring_get_str in the caller or define a new qstring_ util
> function in qobject/qstring.c.

OK.

> 
> >+{
> >+    QString *qstr;
> >+
> >+    if (!data) {
> >+        return NULL;
> >+    }
> >+    qstr = qobject_to_qstring(data);
> >+    if (qstr) {
> >+        return qstring_get_str(qstr);
> >+    } else {
> >+        return NULL;
> >+    }
> >+}
> >+
> >+static QObject *get_definition(const char *str, bool update_path)
> >+{
> >+    QObject *data, *value;
> >+    QDict *qdict;
> >+    int i;
> >+
> >+    if (!strcmp(str, "str") || !strcmp(str, "int") ||
> >+        !strcmp(str, "number") || !strcmp(str, "bool") ||
> >+        !strcmp(str, "int8") || !strcmp(str, "int16") ||
> >+        !strcmp(str, "int32") || !strcmp(str, "int64") ||
> >+        !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
> >+        !strcmp(str, "uint32") || !strcmp(str, "uint64") ||
> >+        !strcmp(str, "visitor") || !strcmp(str, "**") ||
> >+        !strcmp(str, "size")) {
> >+        return NULL;
> >+    }
> >+
> >+    for (i = 0; qmp_schema_table[i]; i++) {
> >+        data = qobject_from_json(qmp_schema_table[i]);
> >+        qdict = qobject_to_qdict(data);
> >+        assert(qdict != NULL);
> >+
> >+        if (qdict_get(qdict, "enum")) {
> >+            value = qdict_get(qdict, "enum");
> >+        } else if (qdict_get(qdict, "type")) {
> >+            value = qdict_get(qdict, "type");
> >+        } else if (qdict_get(qdict, "union")) {
> >+            value = qdict_get(qdict, "union");
> >+        } else {
> >+            continue;
> >+        }
> >+
> >+        if (!strcmp(str, qstring_copy_str(value)) && update_path) {
> 
> Simply:
> 
> if (strcmp(str, qstring_copy_str(value)) {
>     continue;
> }
> 
> Then avoid another check for this.
> 
> >+            char *start, *end;
> >+            char cur_idx[256];
> >+            char type_idx[256];
> >+
> >+            start = visit_path_str;
> >+            sprintf(type_idx, "%d", i);
> 
> Let's avoid sprintf completely.
> 
> >+            while (start) {
> >+                end = strchr(start, ':');
> >+                if (!end) {
> >+                    break;
> >+                }
> >+                snprintf(cur_idx, end - start + 1, "%s", start);
> >+                start = end + 1;
> >+                /* if the type was already extended in parent node,
> >+                 * we don't extend it again to avoid dead loop. */
> >+                if (!strcmp(cur_idx, type_idx)) {
> >+                    return NULL;
> >+                }
> >+            }
> 
> Using array of int will get you a simpler code here.
> 
> Furthermore, you could avoid linear lookup by using a bitmap beside
> visit_path_str to keep track of what is visited.
> 
> >+            /* push index to visit_path_str before extending */
> >+            push_id(i);
> >+        }
> >+        if (!strcmp(str, qstring_copy_str(value))) {
> >+
> >+            return qobject_from_json(qmp_schema_table[i]);
> >+        }
> >+    }
> >+    return NULL;
> >+}
> >+
> >+static DataObject *visit_qobj_dict(QObject *data);
> >+static DataObject *visit_qobj_list(QObject *data);
> >+
> >+/* extend defined type to json object */
> >+static DataObject *extend_type(const char *str)
> >+{
> >+    QObject *data;
> >+    DataObject *obj;
> >+
> >+    data = get_definition(str, true);
> >+
> >+    if (data) {
> >+        obj = visit_qobj_dict(data);
> >+        pop_id();
> >+    } else {
> >+        obj = g_malloc0(sizeof(struct DataObject));
> >+        obj->kind = DATA_OBJECT_KIND_REFERENCE_TYPE;
> >+        obj->reference_type = g_malloc0(sizeof(String));
> >+        obj->reference_type->str = g_strdup(str);
> >+    }
> >+
> >+    return obj;
> >+}
> >+
> >+static DataObjectMemberList *list_to_memberlist(QObject *data)
> >+{
> >+    DataObjectMemberList *mem_list, *entry, *last_entry;
> >+    QList *qlist;
> >+    const QListEntry *lent;
> >+
> >+        qlist = qobject_to_qlist(data);
> 
> Bad indentation starting from here.
> 
> >+
> >+        mem_list = NULL;
> >+        for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
> >+            entry = g_malloc0(sizeof(DataObjectMemberList *));
> 
> Pointer size allocated instead of structure.
> 
> >+            entry->value = g_malloc0(sizeof(DataObjectMember));
> >+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
> >+
> >+            if (get_definition(qstring_copy_str(lent->value), false)) {
> >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> >+                entry->value->has_recursive = true;
> >+                entry->value->recursive = true;
> >+                entry->value->type->extend =
> >+                    extend_type(qstring_copy_str(lent->value));
> >+            } else {
> >+                entry->value->type->kind =
> >+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
> >+                entry->value->has_recursive = true;
> >+                entry->value->recursive = false;
> >+                entry->value->type->reference =
> >+                    g_strdup(qstring_copy_str(lent->value));
> >+            }
> >+
> >+            entry->next = NULL;
> >+            if (!mem_list) {
> >+                mem_list = entry;
> >+            } else {
> >+                last_entry->next = entry;
> >+            }
> >+            last_entry = entry;
> >+        }
> >+        return mem_list;
> >+}
> >+
> >+static DataObjectMemberList *dict_to_memberlist(QObject *data)
> >+{
> >+    DataObjectMemberList *mem_list, *entry, *last_entry;
> >+    QDict *qdict;
> >+    const QDictEntry *dent;
> >+
> >+        qdict = qobject_to_qdict(data);
> >+
> >+        mem_list = NULL;
> >+        for (dent = qdict_first(qdict); dent; dent = qdict_next(qdict, dent)) {
> >+            entry = g_malloc0(sizeof(DataObjectMemberList *));
> 
> Same here.
> 
> >+            entry->value = g_malloc0(sizeof(DataObjectMember));
> >+
> >+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
> >+
> >+            if (dent->value->type->code == QTYPE_QDICT) {
> >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> >+                entry->value->type->extend = visit_qobj_dict(dent->value);
> >+            } else if (dent->value->type->code == QTYPE_QLIST) {
> >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> >+                entry->value->type->extend = visit_qobj_list(dent->value);
> >+            } else if (get_definition(qstring_copy_str(dent->value), false)) {
> >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> >+                entry->value->has_recursive = true;
> >+                entry->value->recursive = true;
> >+                entry->value->type->extend =
> >+                    extend_type(qstring_copy_str(dent->value));
> >+            } else {
> >+                entry->value->type->kind =
> >+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
> >+                entry->value->has_recursive = true;
> >+                entry->value->recursive = false;
> >+                entry->value->type->reference =
> >+                    g_strdup(qstring_copy_str(dent->value));
> >+            }
> >+            entry->value->has_optional = true;
> >+            entry->value->has_name = true;
> >+            if (dent->key[0] == '*') {
> >+                entry->value->optional = true;
> >+                entry->value->name = g_strdup(dent->key + 1);
> >+            } else {
> >+                entry->value->name = g_strdup(dent->key);
> >+            }
> >+
> >+            entry->next = NULL;
> >+            if (!mem_list) {
> >+                mem_list = entry;
> >+            } else {
> >+                last_entry->next = entry;
> >+            }
> >+            last_entry = entry;
> >+        }
> >+        return mem_list;
> >+}
> >+
> >+static DataObject *visit_qobj_list(QObject *data)
> >+{
> >+    DataObject *obj;
> >+
> >+    obj = g_malloc0(sizeof(struct DataObject));
> >+    obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
> >+    obj->anonymous_struct = g_malloc0(sizeof(struct
> >+                                             DataObjectAnonymousStruct));
> >+    obj->anonymous_struct->data = list_to_memberlist(data);
> >+
> >+    return obj;
> >+}
> >+
> >+static strList *get_str_list(QObject *data)
> >+{
> >+    strList *str_list, *last_str_entry, *str_entry;
> >+    QList *qlist;
> >+    const QListEntry *lent;
> >+
> >+    qlist = qobject_to_qlist(data);
> >+    str_list = NULL;
> >+    for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
> >+        str_entry = g_malloc0(sizeof(strList *));
> 
> And here.
> 
> >+        str_entry->value = g_strdup(qstring_copy_str(lent->value));
> >+        str_entry->next = NULL;
> >+        if (!str_list) {
> >+            str_list = str_entry;
> >+        } else {
> >+            last_str_entry->next = str_entry;
> >+        }
> >+        last_str_entry = str_entry;
> >+    }
> >+
> >+    return str_list;
> >+}
> >+
> >+static DataObject *visit_qobj_dict(QObject *data)
> >+{
> >+    DataObject *obj;
> >+    QObject *subdata;
> >+    QDict *qdict;
> >+
> >+    qdict = qobject_to_qdict(data);
> >+    assert(qdict != NULL);
> >+    obj = g_malloc0(sizeof(*obj));
> >+
> >+    if (qdict_get(qdict, "command")) {
> >+        obj->kind = DATA_OBJECT_KIND_COMMAND;
> >+        obj->has_name = true;
> >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "command")));
> >+        obj->command = g_malloc0(sizeof(struct DataObjectCommand));
> >+
> >+        subdata = qdict_get(qdict, "data");
> >+        if (subdata && subdata->type->code == QTYPE_QDICT) {
> >+            obj->command->has_data = true;
> >+            obj->command->data = dict_to_memberlist(subdata);
> >+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
> >+            abort();
> >+        } else if (subdata) {
> >+            obj->command->has_data = true;
> >+            obj->command->data =
> >+                dict_to_memberlist(get_definition(qstring_copy_str(subdata),
> >+                                                  true));
> >+            pop_id();
> >+        }
> >+
> >+        subdata = qdict_get(qdict, "returns");
> >+        if (subdata && subdata->type->code == QTYPE_QDICT) {
> >+            abort();
> >+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
> >+            obj->command->has_returns = true;
> >+            obj->command->returns = visit_qobj_list(subdata);
> >+        } else if (subdata && subdata->type->code == QTYPE_QSTRING) {
> >+            obj->command->has_returns = true;
> >+            obj->command->returns = extend_type(qstring_copy_str(subdata));
> >+        }
> >+
> >+        subdata = qdict_get(qdict, "gen");
> >+        if (subdata && subdata->type->code == QTYPE_QSTRING) {
> >+            obj->command->has_gen = true;
> >+            if (!strcmp(qstring_copy_str(subdata), "no")) {
> >+                obj->command->gen = false;
> >+            } else {
> >+                obj->command->gen = true;
> >+            }
> >+        }
> >+    } else if (qdict_get(qdict, "union")) {
> >+        obj->kind = DATA_OBJECT_KIND_UNIONOBJ;
> >+        obj->has_name = true;
> >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "union")));
> >+        obj->unionobj = g_malloc0(sizeof(struct DataObjectUnion));
> >+        subdata = qdict_get(qdict, "data");
> >+        obj->unionobj->data = dict_to_memberlist(subdata);
> >+    } else if (qdict_get(qdict, "type")) {
> >+        obj->kind = DATA_OBJECT_KIND_TYPE;
> >+        obj->has_name = true;
> >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "type")));
> >+        obj->type = g_malloc0(sizeof(struct DataObjectType));
> >+        subdata = qdict_get(qdict, "data");
> >+        obj->type->data = dict_to_memberlist(subdata);
> >+    } else if (qdict_get(qdict, "enum")) {
> >+        obj->kind = DATA_OBJECT_KIND_ENUMERATION;
> >+        obj->has_name = true;
> >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "enum")));
> >+        obj->enumeration = g_malloc0(sizeof(struct DataObjectEnumeration));
> >+        subdata = qdict_get(qdict, "data");
> >+        obj->enumeration->data = get_str_list(subdata);
> >+    } else {
> >+        obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
> >+        obj->anonymous_struct = g_malloc0(sizeof(struct
> >+                                                 DataObjectAnonymousStruct));
> >+        obj->anonymous_struct->data = dict_to_memberlist(data);
> >+    }
> >+
> >+    return obj;
> >+}
> >+
> >+DataObjectList *qmp_query_qmp_schema(Error **errp)
> >+{
> >+    DataObjectList *list, *last_entry, *entry;
> >+    QObject *data;
> >+    int i;
> >+
> >+    list = NULL;
> >+    for (i = 0; qmp_schema_table[i]; i++) {
> >+        data = qobject_from_json(qmp_schema_table[i]);
> >+        assert(data != NULL);
> >+
> >+        entry = g_malloc0(sizeof(DataObjectList *));
> 
> And here.
> 
> >+        memset(visit_path_str, 0, sizeof(visit_path_str));
> >+        entry->value = visit_qobj_dict(data);
> >+        entry->next = NULL;
> >+        if (!list) {
> 
> Why not use a pointer to pointer to avoid this if branch?
 
Will fix it.

> >+            list = entry;
> >+        } else {
> >+            last_entry->next = entry;
> >+        }
> >+        last_entry = entry;
> >+    }
> >+
> >+    return list;
> >+}
> >+
> >  void qmp_add_client(const char *protocol, const char *fdname,
> >                      bool has_skipauth, bool skipauth, bool has_tls, bool tls,
> >                      Error **errp)
> >
> 
> Fam
Luiz Capitulino Jan. 22, 2014, 6:18 p.m. UTC | #3
On Thu, 9 Jan 2014 17:49:56 +0800
Amos Kong <akong@redhat.com> wrote:

> On Mon, Jan 06, 2014 at 05:37:19PM +0800, Fam Zheng wrote:
> > On 2014年01月05日 20:02, Amos Kong wrote:
> > >This patch introduces a new monitor command to query QMP schema
> > >information, the return data is a range of schema structs, which
> > >contains the useful metadata to help management to check supported
> > >features, QMP commands detail, etc.
> > >
> > >It parses all json definition in qapi-schema.json, and generate a
> > >dynamic struct tree, QMP infrastructure will convert the tree to
> > >json string and return to QMP client.
> > >
> > >I defined a 'DataObject' union in qapi-schema.json, it's used to
> > >describe the dynamic data struct.
> > >
> > >I also added a document about QMP full introspection support
> > >(docs/qmp-full-introspection.txt), it helps to use the new interface
> > >and understand the abstract method in describing the dynamic struct.
> > >
> > >TODO:
> > >Wenchao Xia is working to convert QMP events to qapi-schema.json,
> > >then event can also be queried by this interface.
> > >
> > >I will introduce another command 'query-qga-schema' to query QGA
> > >schema information, it's easy to add this support based on this
> > >patch.
> > >
> > >Signed-off-by: Amos Kong <akong@redhat.com>
> > >---
> 
> Hi Fam,
>  
> I'm very happy to get your comments!
> 
> > I have a few comments on the current implementation below, there are
> > a few things to improve. However I agree to what Eric suggested in
> > reply to V2: it may be better to generate most of the response data
> > in python code at compile time and simplify the logic in C. Because
> > this implementation is slow and it is unnecessary runtime
> > computation. It also duplicates much of existing qapi.py logic (data
> > types and other semantics parsing).
> 
> The implemented code of QMP command will generate defined struct,
> the memory is allocated in run time, it will be auto released by
> QMP infrastructure.
> 
> We can implement the parse/extend work by Python and generate
> a result in a head file, that can be directly & easyly used C code.
> Maybe we can generate a nested & complex struct definition in the
> head file, and use less g_malloc() to generate return struct.
> I will try, thanks.

Which means this patch will completely change, right? In this case
I'm skipping review.

>  
> > >  docs/qmp-full-introspection.txt |  97 ++++++++++
> > >  qapi-schema.json                | 150 ++++++++++++++++
> > >  qmp-commands.hx                 |  43 ++++-
> > >  qmp.c                           | 382 ++++++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 671 insertions(+), 1 deletion(-)
> > >  create mode 100644 docs/qmp-full-introspection.txt
> > >
> > >diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
> > >new file mode 100644
> > >index 0000000..1617df7
> > >--- /dev/null
> > >+++ b/docs/qmp-full-introspection.txt
> > >@@ -0,0 +1,97 @@
> > >+= Full introspection support for QMP =
> > >+
> > >+
> > >+== Purpose ==
> > >+
> > >+Add a new monitor command for management to  query QMP schema
> > >+information, it returns a range of schema structs, which contain the
> > >+useful metadata to help management to check supported features, QMP
> > >+commands detail, etc.
> > >+
> > >+== Usage ==
> > >+
> > >+Json schema:
> > >+  { 'type': 'NameInfo', 'data': {'*name': 'str'} }
> > >+  { 'command': 'query-name', 'returns': 'NameInfo' }
> > >+
> > >+Execute QMP command:
> > >+
> > >+  { "execute": "query-qmp-schema" }
> > >+
> > >+Returns:
> > >+
> > >+  { "return": [
> > >+      {
> > >+          "name": "query-name",
> > >+          "type": "command",
> > >+          "returns": {
> > >+              "name": "NameInfo",
> > >+              "type": "type",
> > >+              "data": [
> > >+                  {
> > >+                      "name": "name",
> > >+                      "optional": true,
> > >+                      "recursive": false,
> > >+                      "type": "str"
> > >+                  }
> > >+              ]
> > >+          }
> > >+      },
> > >+      ...
> > >+   }
> > >+
> > >+The whole schema information will be returned in one go, it contains
> > >+all the schema entries. It doesn't support to be filtered by type
> > >+or name. Currently it takes about 5 seconds to return about 1.5M string.
> > >+
> > >+== 'DataObject' union ==
> > >+
> > >+{ 'union': 'DataObject',
> > >+  'base': 'DataObjectBase',
> > >+  'discriminator': 'type',
> > >+  'data': {
> > >+    'anonymous-struct': 'DataObjectAnonymousStruct',
> > >+    'command': 'DataObjectCommand',
> > >+    'enumeration': 'DataObjectEnumeration',
> > >+    'reference-type': 'String',
> > >+    'type': 'DataObjectType',
> > >+    'unionobj': 'DataObjectUnion' } }
> > >+
> > >+Currently we have schema difinitions for type, command, enumeration,
> > >+union. Some arbitrary structs (dictionary, list or string) and native
> > >+types are also used in the body of definitions.
> > >+
> > >+Here we use "DataObject" union to abstract all above schema. We want
> > >+to provide more useful metadata, and used some enum/unions to indicate
> > >+the dynamic type. In the output, some simple data is processed too
> > >+unwieldy. In another side, some complex data is described clearly.
> > >+It's also caused by some limitation of QAPI infrastructure.
> > >+
> > >+So we define 'DataObject' to be an union, it always has an object name
> > >+except anonymous struct.
> > >+
> > >+'command', 'enumeration', 'type', 'unionobj' are common schema type,
> > >+'union' is a build-in type, so I used unionobj here.
> > >+
> > >+'reference-type' will be used to describe native types and unextended
> > >+types.
> > >+
> > >+'anonymous-struct' will be used to describe arbitrary structs
> > >+(dictionary, list or string).
> > >+
> > >+== Avoid dead loop in recursive extending ==
> > >+
> > >+We have four types (ImageInfo, BlockStats, PciDeviceInfo, ObjectData)
> > >+that uses themself in their own define data directly or indirectly,
> > >+we will not repeatedly extend them to avoid dead loop.
> > >+
> > >+We use a string to record the visit path, type index of each node
> > >+will be saved to the string, indexes are split by ':'.
> > >+
> > >+Push index to visit_path_str before extending, and pop index from
> > >+visit_path_str after extending.
> > >+
> > >+If the type was already extended in parent node, we don't extend it
> > >+again to avoid dead loop.
> > >+
> > >+'recursive' indicates if the type is extended or not.
> > >diff --git a/qapi-schema.json b/qapi-schema.json
> > >index c3c939c..db500ab 100644
> > >--- a/qapi-schema.json
> > >+++ b/qapi-schema.json
> > >@@ -4235,3 +4235,153 @@
> > >  # Since: 1.7
> > >  ##
> > >  { 'command': 'blockdev-add', 'data': { 'options': 'BlockdevOptions' } }
> > >+
> > >+##
> > >+# @DataObjectBase
> > >+#
> > >+# Base of @DataObject
> > >+#
> > >+# @name: #optional @DataObject name
> > >+# @type: @DataObject type
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'type': 'DataObjectBase',
> > >+  'data': { '*name': 'str', 'type': 'str' } }
> > >+
> > >+##
> > >+# @DataObjectMemberType
> > >+#
> > >+# Type of @DabaObjectMember
> > >+#
> > >+# @reference: reference string
> > >+# @anonymous: arbitrary struct
> > >+# @extend: the @DataObjectMember
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'union': 'DataObjectMemberType',
> > >+  'discriminator': {},
> > >+  'data': { 'reference': 'str',
> > >+            'anonymous': 'DataObject',
> > >+            'extend': 'DataObject' } }
> > >+
> > >+##
> > >+# @DataObjectMember
> > >+#
> > >+# General member of @DataObject
> > >+#
> > >+# @type: type of @DataObjectMember
> > >+# @name: #optional name
> > >+# @optional: #optional key to indicate if the @DataObjectMember is optional
> > >+# @recursive: #optional key to indicate if it's defined recursively
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'type': 'DataObjectMember',
> > >+  'data': { 'type': 'DataObjectMemberType', '*name': 'str',
> > >+            '*optional': 'bool', '*recursive': 'bool' } }
> > >+
> > >+##
> > >+# @DataObjectAnonymousStruct
> > >+#
> > >+# Arbitrary struct, it can be dictionary, list or string
> > >+#
> > >+# @data: content of arbitrary struct
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'type': 'DataObjectAnonymousStruct',
> > >+  'data': { 'data': [ 'DataObjectMember' ] } }
> > >+
> > >+##
> > >+# @DataObjectCommand
> > >+#
> > >+# QMP Command schema
> > >+#
> > >+# @data: QMP command content
> > >+# @returns: returns of executing command
> > >+# @gen: a key to suppress code generation
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'type': 'DataObjectCommand',
> > >+  'data': { '*data': [ 'DataObjectMember' ],
> > >+            '*returns': 'DataObject',
> > >+            '*gen': 'bool' } }
> > >+
> > >+##
> > >+# @DataObjectEnumeration
> > >+#
> > >+# Enumeration schema
> > >+#
> > >+# @data: enumeration content, it's a string list
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'type': 'DataObjectEnumeration',
> > >+  'data': { 'data': [ 'str' ] } }
> > >+
> > >+##
> > >+# @DataObjectType
> > >+#
> > >+# Type schema
> > >+#
> > >+# @data: type content
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'type': 'DataObjectType',
> > >+  'data': { 'data': [ 'DataObjectMember' ] } }
> > >+
> > >+##
> > >+# @DataObjectUnion
> > >+#
> > >+# Union schema
> > >+#
> > >+# @data: union content
> > >+# @base: union base
> > >+# @discriminator: union discriminator
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'type': 'DataObjectUnion',
> > >+  'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
> > >+            '*discriminator': 'str' } }
> > >+
> > >+##
> > >+# @DataObject
> > >+#
> > >+# Dynamic data struct, it can be command, enumeration, type, union, arbitrary
> > >+# struct or native type.
> > >+#
> > >+# @anonymous-struct: arbitrary struct, it can be dictionary, list or string
> > >+# @command: QMP command schema
> > >+# @enumeration: enumeration schema
> > >+# @reference-type: native type or unextended type
> > >+# @type: type schema, it will be extended
> > >+# @unionobj: union schema
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'union': 'DataObject',
> > >+  'base': 'DataObjectBase',
> > >+  'discriminator': 'type',
> > >+  'data': {
> > >+    'anonymous-struct': 'DataObjectAnonymousStruct',
> > >+    'command': 'DataObjectCommand',
> > >+    'enumeration': 'DataObjectEnumeration',
> > >+    'reference-type': 'String',
> > >+    'type': 'DataObjectType',
> > >+    'unionobj': 'DataObjectUnion' } }
> > >+
> > >+##
> > >+# @query-qmp-schema
> > >+#
> > >+# Query QMP schema information
> > >+#
> > >+# @returns: list of @DataObject
> > >+#
> > >+# Since: 1.8
> > >+##
> > >+{ 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
> > >diff --git a/qmp-commands.hx b/qmp-commands.hx
> > >index fba15cd..cf9c4aa 100644
> > >--- a/qmp-commands.hx
> > >+++ b/qmp-commands.hx
> > >@@ -3237,7 +3237,48 @@ Example:
> > >              "broadcast-allowed": false
> > >          }
> > >        ]
> > >-   }
> > >+
> > >+EQMP
> > >+    {
> > >+        .name       = "query-qmp-schema",
> > >+        .args_type  = "",
> > >+        .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
> > >+    },
> > >+
> > >+
> > >+SQMP
> > >+query-qmp-schema
> > >+----------------
> > >+
> > >+query qmp schema information
> > >+
> > >+Return a json-object with the following information:
> > >+
> > >+- "name": qmp schema name (json-string)
> > >+- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union'
> > >+- "returns": return data of qmp command (json-object, optional)
> > >+
> > >+Example:
> > >+
> > >+-> { "execute": "query-qmp-schema" }
> > >+-> { "return": [
> > >+     {
> > >+         "name": "query-name",
> > >+         "type": "command",
> > >+         "returns": {
> > >+             "name": "NameInfo",
> > >+             "type": "type",
> > >+             "data": [
> > >+                 {
> > >+                     "name": "name",
> > >+                     "optional": true,
> > >+                     "recursive": false,
> > >+                     "type": "str"
> > >+                 }
> > >+             ]
> > >+         }
> > >+     }
> > >+  }
> > >
> > >  EQMP
> > >
> > >diff --git a/qmp.c b/qmp.c
> > >index 1d7a04d..e9bba06 100644
> > >--- a/qmp.c
> > >+++ b/qmp.c
> > >@@ -25,6 +25,8 @@
> > >  #include "sysemu/blockdev.h"
> > >  #include "qom/qom-qobject.h"
> > >  #include "hw/boards.h"
> > >+#include "qmp-schema.h"
> > >+#include "qapi/qmp/qjson.h"
> > >
> > >  NameInfo *qmp_query_name(Error **errp)
> > >  {
> > >@@ -486,6 +488,386 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
> > >      return arch_query_cpu_definitions(errp);
> > >  }
> > >
> > >+/*
> > >+ * use a passed string to record the visit path, schema index of
> > >+ * each extended node will be appended to the string, indexes are
> > >+ * split by ':'
> > >+ */
> > >+static char visit_path_str[1024];
> > 
> > Is it safe to use a global variable here? Is there any race
> > condition (i.e. multiple monitor commands run parallel)?
> 
> This command is only executed once after installing/updating qemu
> for Libvirt. But the problem you pointed is existed. I will allocate
> /free dynamic memory for each query instance.
>  
> > IIUC this serves as a queue of number? Why not use array of int?
>  
> Great! It's easier to understand and update.
> 
> > >+
> > >+/* push the type index into visit_path_str */
> > >+static void push_id(int id)
> > >+{
> > >+    char *end = strrchr(visit_path_str, ':');
> > >+    char type_idx[256];
> > >+    int num;
> > >+
> > >+    num = sprintf(type_idx, "%d:", id);
> > >+
> > >+    if (end) {
> > >+        /* avoid overflow */
> > >+        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
> > >+        sprintf(end + 1, "%d:", id);
> > >+    } else {
> > >+        sprintf(visit_path_str, "%d:", id);
> > >+    }
> > >+}
> > >+
> > >+/* pop the type index from visit_path_str */
> > >+static void pop_id(void)
> > >+{
> > >+    char *p = strrchr(visit_path_str, ':');
> > >+
> > >+    assert(p != NULL);
> > >+    *p = '\0';
> > >+    p = strrchr(visit_path_str, ':');
> > >+    if (p) {
> > >+        *(p + 1) = '\0';
> > >+    } else {
> > >+        visit_path_str[0] = '\0';
> > >+    }
> > >+}
> > >+
> > >+static const char *qstring_copy_str(QObject *data)
> > 
> > The string is not copied, it's misleading. And I think you could
> > just use qstring_get_str in the caller or define a new qstring_ util
> > function in qobject/qstring.c.
> 
> OK.
> 
> > 
> > >+{
> > >+    QString *qstr;
> > >+
> > >+    if (!data) {
> > >+        return NULL;
> > >+    }
> > >+    qstr = qobject_to_qstring(data);
> > >+    if (qstr) {
> > >+        return qstring_get_str(qstr);
> > >+    } else {
> > >+        return NULL;
> > >+    }
> > >+}
> > >+
> > >+static QObject *get_definition(const char *str, bool update_path)
> > >+{
> > >+    QObject *data, *value;
> > >+    QDict *qdict;
> > >+    int i;
> > >+
> > >+    if (!strcmp(str, "str") || !strcmp(str, "int") ||
> > >+        !strcmp(str, "number") || !strcmp(str, "bool") ||
> > >+        !strcmp(str, "int8") || !strcmp(str, "int16") ||
> > >+        !strcmp(str, "int32") || !strcmp(str, "int64") ||
> > >+        !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
> > >+        !strcmp(str, "uint32") || !strcmp(str, "uint64") ||
> > >+        !strcmp(str, "visitor") || !strcmp(str, "**") ||
> > >+        !strcmp(str, "size")) {
> > >+        return NULL;
> > >+    }
> > >+
> > >+    for (i = 0; qmp_schema_table[i]; i++) {
> > >+        data = qobject_from_json(qmp_schema_table[i]);
> > >+        qdict = qobject_to_qdict(data);
> > >+        assert(qdict != NULL);
> > >+
> > >+        if (qdict_get(qdict, "enum")) {
> > >+            value = qdict_get(qdict, "enum");
> > >+        } else if (qdict_get(qdict, "type")) {
> > >+            value = qdict_get(qdict, "type");
> > >+        } else if (qdict_get(qdict, "union")) {
> > >+            value = qdict_get(qdict, "union");
> > >+        } else {
> > >+            continue;
> > >+        }
> > >+
> > >+        if (!strcmp(str, qstring_copy_str(value)) && update_path) {
> > 
> > Simply:
> > 
> > if (strcmp(str, qstring_copy_str(value)) {
> >     continue;
> > }
> > 
> > Then avoid another check for this.
> > 
> > >+            char *start, *end;
> > >+            char cur_idx[256];
> > >+            char type_idx[256];
> > >+
> > >+            start = visit_path_str;
> > >+            sprintf(type_idx, "%d", i);
> > 
> > Let's avoid sprintf completely.
> > 
> > >+            while (start) {
> > >+                end = strchr(start, ':');
> > >+                if (!end) {
> > >+                    break;
> > >+                }
> > >+                snprintf(cur_idx, end - start + 1, "%s", start);
> > >+                start = end + 1;
> > >+                /* if the type was already extended in parent node,
> > >+                 * we don't extend it again to avoid dead loop. */
> > >+                if (!strcmp(cur_idx, type_idx)) {
> > >+                    return NULL;
> > >+                }
> > >+            }
> > 
> > Using array of int will get you a simpler code here.
> > 
> > Furthermore, you could avoid linear lookup by using a bitmap beside
> > visit_path_str to keep track of what is visited.
> > 
> > >+            /* push index to visit_path_str before extending */
> > >+            push_id(i);
> > >+        }
> > >+        if (!strcmp(str, qstring_copy_str(value))) {
> > >+
> > >+            return qobject_from_json(qmp_schema_table[i]);
> > >+        }
> > >+    }
> > >+    return NULL;
> > >+}
> > >+
> > >+static DataObject *visit_qobj_dict(QObject *data);
> > >+static DataObject *visit_qobj_list(QObject *data);
> > >+
> > >+/* extend defined type to json object */
> > >+static DataObject *extend_type(const char *str)
> > >+{
> > >+    QObject *data;
> > >+    DataObject *obj;
> > >+
> > >+    data = get_definition(str, true);
> > >+
> > >+    if (data) {
> > >+        obj = visit_qobj_dict(data);
> > >+        pop_id();
> > >+    } else {
> > >+        obj = g_malloc0(sizeof(struct DataObject));
> > >+        obj->kind = DATA_OBJECT_KIND_REFERENCE_TYPE;
> > >+        obj->reference_type = g_malloc0(sizeof(String));
> > >+        obj->reference_type->str = g_strdup(str);
> > >+    }
> > >+
> > >+    return obj;
> > >+}
> > >+
> > >+static DataObjectMemberList *list_to_memberlist(QObject *data)
> > >+{
> > >+    DataObjectMemberList *mem_list, *entry, *last_entry;
> > >+    QList *qlist;
> > >+    const QListEntry *lent;
> > >+
> > >+        qlist = qobject_to_qlist(data);
> > 
> > Bad indentation starting from here.
> > 
> > >+
> > >+        mem_list = NULL;
> > >+        for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
> > >+            entry = g_malloc0(sizeof(DataObjectMemberList *));
> > 
> > Pointer size allocated instead of structure.
> > 
> > >+            entry->value = g_malloc0(sizeof(DataObjectMember));
> > >+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
> > >+
> > >+            if (get_definition(qstring_copy_str(lent->value), false)) {
> > >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> > >+                entry->value->has_recursive = true;
> > >+                entry->value->recursive = true;
> > >+                entry->value->type->extend =
> > >+                    extend_type(qstring_copy_str(lent->value));
> > >+            } else {
> > >+                entry->value->type->kind =
> > >+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
> > >+                entry->value->has_recursive = true;
> > >+                entry->value->recursive = false;
> > >+                entry->value->type->reference =
> > >+                    g_strdup(qstring_copy_str(lent->value));
> > >+            }
> > >+
> > >+            entry->next = NULL;
> > >+            if (!mem_list) {
> > >+                mem_list = entry;
> > >+            } else {
> > >+                last_entry->next = entry;
> > >+            }
> > >+            last_entry = entry;
> > >+        }
> > >+        return mem_list;
> > >+}
> > >+
> > >+static DataObjectMemberList *dict_to_memberlist(QObject *data)
> > >+{
> > >+    DataObjectMemberList *mem_list, *entry, *last_entry;
> > >+    QDict *qdict;
> > >+    const QDictEntry *dent;
> > >+
> > >+        qdict = qobject_to_qdict(data);
> > >+
> > >+        mem_list = NULL;
> > >+        for (dent = qdict_first(qdict); dent; dent = qdict_next(qdict, dent)) {
> > >+            entry = g_malloc0(sizeof(DataObjectMemberList *));
> > 
> > Same here.
> > 
> > >+            entry->value = g_malloc0(sizeof(DataObjectMember));
> > >+
> > >+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
> > >+
> > >+            if (dent->value->type->code == QTYPE_QDICT) {
> > >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> > >+                entry->value->type->extend = visit_qobj_dict(dent->value);
> > >+            } else if (dent->value->type->code == QTYPE_QLIST) {
> > >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> > >+                entry->value->type->extend = visit_qobj_list(dent->value);
> > >+            } else if (get_definition(qstring_copy_str(dent->value), false)) {
> > >+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
> > >+                entry->value->has_recursive = true;
> > >+                entry->value->recursive = true;
> > >+                entry->value->type->extend =
> > >+                    extend_type(qstring_copy_str(dent->value));
> > >+            } else {
> > >+                entry->value->type->kind =
> > >+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
> > >+                entry->value->has_recursive = true;
> > >+                entry->value->recursive = false;
> > >+                entry->value->type->reference =
> > >+                    g_strdup(qstring_copy_str(dent->value));
> > >+            }
> > >+            entry->value->has_optional = true;
> > >+            entry->value->has_name = true;
> > >+            if (dent->key[0] == '*') {
> > >+                entry->value->optional = true;
> > >+                entry->value->name = g_strdup(dent->key + 1);
> > >+            } else {
> > >+                entry->value->name = g_strdup(dent->key);
> > >+            }
> > >+
> > >+            entry->next = NULL;
> > >+            if (!mem_list) {
> > >+                mem_list = entry;
> > >+            } else {
> > >+                last_entry->next = entry;
> > >+            }
> > >+            last_entry = entry;
> > >+        }
> > >+        return mem_list;
> > >+}
> > >+
> > >+static DataObject *visit_qobj_list(QObject *data)
> > >+{
> > >+    DataObject *obj;
> > >+
> > >+    obj = g_malloc0(sizeof(struct DataObject));
> > >+    obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
> > >+    obj->anonymous_struct = g_malloc0(sizeof(struct
> > >+                                             DataObjectAnonymousStruct));
> > >+    obj->anonymous_struct->data = list_to_memberlist(data);
> > >+
> > >+    return obj;
> > >+}
> > >+
> > >+static strList *get_str_list(QObject *data)
> > >+{
> > >+    strList *str_list, *last_str_entry, *str_entry;
> > >+    QList *qlist;
> > >+    const QListEntry *lent;
> > >+
> > >+    qlist = qobject_to_qlist(data);
> > >+    str_list = NULL;
> > >+    for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
> > >+        str_entry = g_malloc0(sizeof(strList *));
> > 
> > And here.
> > 
> > >+        str_entry->value = g_strdup(qstring_copy_str(lent->value));
> > >+        str_entry->next = NULL;
> > >+        if (!str_list) {
> > >+            str_list = str_entry;
> > >+        } else {
> > >+            last_str_entry->next = str_entry;
> > >+        }
> > >+        last_str_entry = str_entry;
> > >+    }
> > >+
> > >+    return str_list;
> > >+}
> > >+
> > >+static DataObject *visit_qobj_dict(QObject *data)
> > >+{
> > >+    DataObject *obj;
> > >+    QObject *subdata;
> > >+    QDict *qdict;
> > >+
> > >+    qdict = qobject_to_qdict(data);
> > >+    assert(qdict != NULL);
> > >+    obj = g_malloc0(sizeof(*obj));
> > >+
> > >+    if (qdict_get(qdict, "command")) {
> > >+        obj->kind = DATA_OBJECT_KIND_COMMAND;
> > >+        obj->has_name = true;
> > >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "command")));
> > >+        obj->command = g_malloc0(sizeof(struct DataObjectCommand));
> > >+
> > >+        subdata = qdict_get(qdict, "data");
> > >+        if (subdata && subdata->type->code == QTYPE_QDICT) {
> > >+            obj->command->has_data = true;
> > >+            obj->command->data = dict_to_memberlist(subdata);
> > >+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
> > >+            abort();
> > >+        } else if (subdata) {
> > >+            obj->command->has_data = true;
> > >+            obj->command->data =
> > >+                dict_to_memberlist(get_definition(qstring_copy_str(subdata),
> > >+                                                  true));
> > >+            pop_id();
> > >+        }
> > >+
> > >+        subdata = qdict_get(qdict, "returns");
> > >+        if (subdata && subdata->type->code == QTYPE_QDICT) {
> > >+            abort();
> > >+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
> > >+            obj->command->has_returns = true;
> > >+            obj->command->returns = visit_qobj_list(subdata);
> > >+        } else if (subdata && subdata->type->code == QTYPE_QSTRING) {
> > >+            obj->command->has_returns = true;
> > >+            obj->command->returns = extend_type(qstring_copy_str(subdata));
> > >+        }
> > >+
> > >+        subdata = qdict_get(qdict, "gen");
> > >+        if (subdata && subdata->type->code == QTYPE_QSTRING) {
> > >+            obj->command->has_gen = true;
> > >+            if (!strcmp(qstring_copy_str(subdata), "no")) {
> > >+                obj->command->gen = false;
> > >+            } else {
> > >+                obj->command->gen = true;
> > >+            }
> > >+        }
> > >+    } else if (qdict_get(qdict, "union")) {
> > >+        obj->kind = DATA_OBJECT_KIND_UNIONOBJ;
> > >+        obj->has_name = true;
> > >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "union")));
> > >+        obj->unionobj = g_malloc0(sizeof(struct DataObjectUnion));
> > >+        subdata = qdict_get(qdict, "data");
> > >+        obj->unionobj->data = dict_to_memberlist(subdata);
> > >+    } else if (qdict_get(qdict, "type")) {
> > >+        obj->kind = DATA_OBJECT_KIND_TYPE;
> > >+        obj->has_name = true;
> > >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "type")));
> > >+        obj->type = g_malloc0(sizeof(struct DataObjectType));
> > >+        subdata = qdict_get(qdict, "data");
> > >+        obj->type->data = dict_to_memberlist(subdata);
> > >+    } else if (qdict_get(qdict, "enum")) {
> > >+        obj->kind = DATA_OBJECT_KIND_ENUMERATION;
> > >+        obj->has_name = true;
> > >+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "enum")));
> > >+        obj->enumeration = g_malloc0(sizeof(struct DataObjectEnumeration));
> > >+        subdata = qdict_get(qdict, "data");
> > >+        obj->enumeration->data = get_str_list(subdata);
> > >+    } else {
> > >+        obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
> > >+        obj->anonymous_struct = g_malloc0(sizeof(struct
> > >+                                                 DataObjectAnonymousStruct));
> > >+        obj->anonymous_struct->data = dict_to_memberlist(data);
> > >+    }
> > >+
> > >+    return obj;
> > >+}
> > >+
> > >+DataObjectList *qmp_query_qmp_schema(Error **errp)
> > >+{
> > >+    DataObjectList *list, *last_entry, *entry;
> > >+    QObject *data;
> > >+    int i;
> > >+
> > >+    list = NULL;
> > >+    for (i = 0; qmp_schema_table[i]; i++) {
> > >+        data = qobject_from_json(qmp_schema_table[i]);
> > >+        assert(data != NULL);
> > >+
> > >+        entry = g_malloc0(sizeof(DataObjectList *));
> > 
> > And here.
> > 
> > >+        memset(visit_path_str, 0, sizeof(visit_path_str));
> > >+        entry->value = visit_qobj_dict(data);
> > >+        entry->next = NULL;
> > >+        if (!list) {
> > 
> > Why not use a pointer to pointer to avoid this if branch?
>  
> Will fix it.
> 
> > >+            list = entry;
> > >+        } else {
> > >+            last_entry->next = entry;
> > >+        }
> > >+        last_entry = entry;
> > >+    }
> > >+
> > >+    return list;
> > >+}
> > >+
> > >  void qmp_add_client(const char *protocol, const char *fdname,
> > >                      bool has_skipauth, bool skipauth, bool has_tls, bool tls,
> > >                      Error **errp)
> > >
> > 
> > Fam
>
diff mbox

Patch

diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
new file mode 100644
index 0000000..1617df7
--- /dev/null
+++ b/docs/qmp-full-introspection.txt
@@ -0,0 +1,97 @@ 
+= Full introspection support for QMP =
+
+
+== Purpose ==
+
+Add a new monitor command for management to  query QMP schema
+information, it returns a range of schema structs, which contain the
+useful metadata to help management to check supported features, QMP
+commands detail, etc.
+
+== Usage ==
+
+Json schema:
+  { 'type': 'NameInfo', 'data': {'*name': 'str'} }
+  { 'command': 'query-name', 'returns': 'NameInfo' }
+
+Execute QMP command:
+
+  { "execute": "query-qmp-schema" }
+
+Returns:
+
+  { "return": [
+      {
+          "name": "query-name",
+          "type": "command",
+          "returns": {
+              "name": "NameInfo",
+              "type": "type",
+              "data": [
+                  {
+                      "name": "name",
+                      "optional": true,
+                      "recursive": false,
+                      "type": "str"
+                  }
+              ]
+          }
+      },
+      ...
+   }
+
+The whole schema information will be returned in one go, it contains
+all the schema entries. It doesn't support to be filtered by type
+or name. Currently it takes about 5 seconds to return about 1.5M string.
+
+== 'DataObject' union ==
+
+{ 'union': 'DataObject',
+  'base': 'DataObjectBase',
+  'discriminator': 'type',
+  'data': {
+    'anonymous-struct': 'DataObjectAnonymousStruct',
+    'command': 'DataObjectCommand',
+    'enumeration': 'DataObjectEnumeration',
+    'reference-type': 'String',
+    'type': 'DataObjectType',
+    'unionobj': 'DataObjectUnion' } }
+
+Currently we have schema difinitions for type, command, enumeration,
+union. Some arbitrary structs (dictionary, list or string) and native
+types are also used in the body of definitions.
+
+Here we use "DataObject" union to abstract all above schema. We want
+to provide more useful metadata, and used some enum/unions to indicate
+the dynamic type. In the output, some simple data is processed too
+unwieldy. In another side, some complex data is described clearly.
+It's also caused by some limitation of QAPI infrastructure.
+
+So we define 'DataObject' to be an union, it always has an object name
+except anonymous struct.
+
+'command', 'enumeration', 'type', 'unionobj' are common schema type,
+'union' is a build-in type, so I used unionobj here.
+
+'reference-type' will be used to describe native types and unextended
+types.
+
+'anonymous-struct' will be used to describe arbitrary structs
+(dictionary, list or string).
+
+== Avoid dead loop in recursive extending ==
+
+We have four types (ImageInfo, BlockStats, PciDeviceInfo, ObjectData)
+that uses themself in their own define data directly or indirectly,
+we will not repeatedly extend them to avoid dead loop.
+
+We use a string to record the visit path, type index of each node
+will be saved to the string, indexes are split by ':'.
+
+Push index to visit_path_str before extending, and pop index from
+visit_path_str after extending.
+
+If the type was already extended in parent node, we don't extend it
+again to avoid dead loop.
+
+'recursive' indicates if the type is extended or not.
diff --git a/qapi-schema.json b/qapi-schema.json
index c3c939c..db500ab 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -4235,3 +4235,153 @@ 
 # Since: 1.7
 ##
 { 'command': 'blockdev-add', 'data': { 'options': 'BlockdevOptions' } }
+
+##
+# @DataObjectBase
+#
+# Base of @DataObject
+#
+# @name: #optional @DataObject name
+# @type: @DataObject type
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectBase',
+  'data': { '*name': 'str', 'type': 'str' } }
+
+##
+# @DataObjectMemberType
+#
+# Type of @DabaObjectMember
+#
+# @reference: reference string
+# @anonymous: arbitrary struct
+# @extend: the @DataObjectMember
+#
+# Since: 1.8
+##
+{ 'union': 'DataObjectMemberType',
+  'discriminator': {},
+  'data': { 'reference': 'str',
+            'anonymous': 'DataObject',
+            'extend': 'DataObject' } }
+
+##
+# @DataObjectMember
+#
+# General member of @DataObject
+#
+# @type: type of @DataObjectMember
+# @name: #optional name
+# @optional: #optional key to indicate if the @DataObjectMember is optional
+# @recursive: #optional key to indicate if it's defined recursively
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectMember',
+  'data': { 'type': 'DataObjectMemberType', '*name': 'str',
+            '*optional': 'bool', '*recursive': 'bool' } }
+
+##
+# @DataObjectAnonymousStruct
+#
+# Arbitrary struct, it can be dictionary, list or string
+#
+# @data: content of arbitrary struct
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectAnonymousStruct',
+  'data': { 'data': [ 'DataObjectMember' ] } }
+
+##
+# @DataObjectCommand
+#
+# QMP Command schema
+#
+# @data: QMP command content
+# @returns: returns of executing command
+# @gen: a key to suppress code generation
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectCommand',
+  'data': { '*data': [ 'DataObjectMember' ],
+            '*returns': 'DataObject',
+            '*gen': 'bool' } }
+
+##
+# @DataObjectEnumeration
+#
+# Enumeration schema
+#
+# @data: enumeration content, it's a string list
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectEnumeration',
+  'data': { 'data': [ 'str' ] } }
+
+##
+# @DataObjectType
+#
+# Type schema
+#
+# @data: type content
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectType',
+  'data': { 'data': [ 'DataObjectMember' ] } }
+
+##
+# @DataObjectUnion
+#
+# Union schema
+#
+# @data: union content
+# @base: union base
+# @discriminator: union discriminator
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectUnion',
+  'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
+            '*discriminator': 'str' } }
+
+##
+# @DataObject
+#
+# Dynamic data struct, it can be command, enumeration, type, union, arbitrary
+# struct or native type.
+#
+# @anonymous-struct: arbitrary struct, it can be dictionary, list or string
+# @command: QMP command schema
+# @enumeration: enumeration schema
+# @reference-type: native type or unextended type
+# @type: type schema, it will be extended
+# @unionobj: union schema
+#
+# Since: 1.8
+##
+{ 'union': 'DataObject',
+  'base': 'DataObjectBase',
+  'discriminator': 'type',
+  'data': {
+    'anonymous-struct': 'DataObjectAnonymousStruct',
+    'command': 'DataObjectCommand',
+    'enumeration': 'DataObjectEnumeration',
+    'reference-type': 'String',
+    'type': 'DataObjectType',
+    'unionobj': 'DataObjectUnion' } }
+
+##
+# @query-qmp-schema
+#
+# Query QMP schema information
+#
+# @returns: list of @DataObject
+#
+# Since: 1.8
+##
+{ 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index fba15cd..cf9c4aa 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3237,7 +3237,48 @@  Example:
             "broadcast-allowed": false
         }
       ]
-   }
+
+EQMP
+    {
+        .name       = "query-qmp-schema",
+        .args_type  = "",
+        .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
+    },
+
+
+SQMP
+query-qmp-schema
+----------------
+
+query qmp schema information
+
+Return a json-object with the following information:
+
+- "name": qmp schema name (json-string)
+- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union'
+- "returns": return data of qmp command (json-object, optional)
+
+Example:
+
+-> { "execute": "query-qmp-schema" }
+-> { "return": [
+     {
+         "name": "query-name",
+         "type": "command",
+         "returns": {
+             "name": "NameInfo",
+             "type": "type",
+             "data": [
+                 {
+                     "name": "name",
+                     "optional": true,
+                     "recursive": false,
+                     "type": "str"
+                 }
+             ]
+         }
+     }
+  }
 
 EQMP
 
diff --git a/qmp.c b/qmp.c
index 1d7a04d..e9bba06 100644
--- a/qmp.c
+++ b/qmp.c
@@ -25,6 +25,8 @@ 
 #include "sysemu/blockdev.h"
 #include "qom/qom-qobject.h"
 #include "hw/boards.h"
+#include "qmp-schema.h"
+#include "qapi/qmp/qjson.h"
 
 NameInfo *qmp_query_name(Error **errp)
 {
@@ -486,6 +488,386 @@  CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
     return arch_query_cpu_definitions(errp);
 }
 
+/*
+ * use a passed string to record the visit path, schema index of
+ * each extended node will be appended to the string, indexes are
+ * split by ':'
+ */
+static char visit_path_str[1024];
+
+/* push the type index into visit_path_str */
+static void push_id(int id)
+{
+    char *end = strrchr(visit_path_str, ':');
+    char type_idx[256];
+    int num;
+
+    num = sprintf(type_idx, "%d:", id);
+
+    if (end) {
+        /* avoid overflow */
+        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
+        sprintf(end + 1, "%d:", id);
+    } else {
+        sprintf(visit_path_str, "%d:", id);
+    }
+}
+
+/* pop the type index from visit_path_str */
+static void pop_id(void)
+{
+    char *p = strrchr(visit_path_str, ':');
+
+    assert(p != NULL);
+    *p = '\0';
+    p = strrchr(visit_path_str, ':');
+    if (p) {
+        *(p + 1) = '\0';
+    } else {
+        visit_path_str[0] = '\0';
+    }
+}
+
+static const char *qstring_copy_str(QObject *data)
+{
+    QString *qstr;
+
+    if (!data) {
+        return NULL;
+    }
+    qstr = qobject_to_qstring(data);
+    if (qstr) {
+        return qstring_get_str(qstr);
+    } else {
+        return NULL;
+    }
+}
+
+static QObject *get_definition(const char *str, bool update_path)
+{
+    QObject *data, *value;
+    QDict *qdict;
+    int i;
+
+    if (!strcmp(str, "str") || !strcmp(str, "int") ||
+        !strcmp(str, "number") || !strcmp(str, "bool") ||
+        !strcmp(str, "int8") || !strcmp(str, "int16") ||
+        !strcmp(str, "int32") || !strcmp(str, "int64") ||
+        !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
+        !strcmp(str, "uint32") || !strcmp(str, "uint64") ||
+        !strcmp(str, "visitor") || !strcmp(str, "**") ||
+        !strcmp(str, "size")) {
+        return NULL;
+    }
+
+    for (i = 0; qmp_schema_table[i]; i++) {
+        data = qobject_from_json(qmp_schema_table[i]);
+        qdict = qobject_to_qdict(data);
+        assert(qdict != NULL);
+
+        if (qdict_get(qdict, "enum")) {
+            value = qdict_get(qdict, "enum");
+        } else if (qdict_get(qdict, "type")) {
+            value = qdict_get(qdict, "type");
+        } else if (qdict_get(qdict, "union")) {
+            value = qdict_get(qdict, "union");
+        } else {
+            continue;
+        }
+
+        if (!strcmp(str, qstring_copy_str(value)) && update_path) {
+            char *start, *end;
+            char cur_idx[256];
+            char type_idx[256];
+
+            start = visit_path_str;
+            sprintf(type_idx, "%d", i);
+            while (start) {
+                end = strchr(start, ':');
+                if (!end) {
+                    break;
+                }
+                snprintf(cur_idx, end - start + 1, "%s", start);
+                start = end + 1;
+                /* if the type was already extended in parent node,
+                 * we don't extend it again to avoid dead loop. */
+                if (!strcmp(cur_idx, type_idx)) {
+                    return NULL;
+                }
+            }
+            /* push index to visit_path_str before extending */
+            push_id(i);
+        }
+        if (!strcmp(str, qstring_copy_str(value))) {
+
+            return qobject_from_json(qmp_schema_table[i]);
+        }
+    }
+    return NULL;
+}
+
+static DataObject *visit_qobj_dict(QObject *data);
+static DataObject *visit_qobj_list(QObject *data);
+
+/* extend defined type to json object */
+static DataObject *extend_type(const char *str)
+{
+    QObject *data;
+    DataObject *obj;
+
+    data = get_definition(str, true);
+
+    if (data) {
+        obj = visit_qobj_dict(data);
+        pop_id();
+    } else {
+        obj = g_malloc0(sizeof(struct DataObject));
+        obj->kind = DATA_OBJECT_KIND_REFERENCE_TYPE;
+        obj->reference_type = g_malloc0(sizeof(String));
+        obj->reference_type->str = g_strdup(str);
+    }
+
+    return obj;
+}
+
+static DataObjectMemberList *list_to_memberlist(QObject *data)
+{
+    DataObjectMemberList *mem_list, *entry, *last_entry;
+    QList *qlist;
+    const QListEntry *lent;
+
+        qlist = qobject_to_qlist(data);
+
+        mem_list = NULL;
+        for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
+            entry = g_malloc0(sizeof(DataObjectMemberList *));
+            entry->value = g_malloc0(sizeof(DataObjectMember));
+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
+
+            if (get_definition(qstring_copy_str(lent->value), false)) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->has_recursive = true;
+                entry->value->recursive = true;
+                entry->value->type->extend =
+                    extend_type(qstring_copy_str(lent->value));
+            } else {
+                entry->value->type->kind =
+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
+                entry->value->has_recursive = true;
+                entry->value->recursive = false;
+                entry->value->type->reference =
+                    g_strdup(qstring_copy_str(lent->value));
+            }
+
+            entry->next = NULL;
+            if (!mem_list) {
+                mem_list = entry;
+            } else {
+                last_entry->next = entry;
+            }
+            last_entry = entry;
+        }
+        return mem_list;
+}
+
+static DataObjectMemberList *dict_to_memberlist(QObject *data)
+{
+    DataObjectMemberList *mem_list, *entry, *last_entry;
+    QDict *qdict;
+    const QDictEntry *dent;
+
+        qdict = qobject_to_qdict(data);
+
+        mem_list = NULL;
+        for (dent = qdict_first(qdict); dent; dent = qdict_next(qdict, dent)) {
+            entry = g_malloc0(sizeof(DataObjectMemberList *));
+            entry->value = g_malloc0(sizeof(DataObjectMember));
+
+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
+
+            if (dent->value->type->code == QTYPE_QDICT) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->type->extend = visit_qobj_dict(dent->value);
+            } else if (dent->value->type->code == QTYPE_QLIST) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->type->extend = visit_qobj_list(dent->value);
+            } else if (get_definition(qstring_copy_str(dent->value), false)) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->has_recursive = true;
+                entry->value->recursive = true;
+                entry->value->type->extend =
+                    extend_type(qstring_copy_str(dent->value));
+            } else {
+                entry->value->type->kind =
+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
+                entry->value->has_recursive = true;
+                entry->value->recursive = false;
+                entry->value->type->reference =
+                    g_strdup(qstring_copy_str(dent->value));
+            }
+            entry->value->has_optional = true;
+            entry->value->has_name = true;
+            if (dent->key[0] == '*') {
+                entry->value->optional = true;
+                entry->value->name = g_strdup(dent->key + 1);
+            } else {
+                entry->value->name = g_strdup(dent->key);
+            }
+
+            entry->next = NULL;
+            if (!mem_list) {
+                mem_list = entry;
+            } else {
+                last_entry->next = entry;
+            }
+            last_entry = entry;
+        }
+        return mem_list;
+}
+
+static DataObject *visit_qobj_list(QObject *data)
+{
+    DataObject *obj;
+
+    obj = g_malloc0(sizeof(struct DataObject));
+    obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
+    obj->anonymous_struct = g_malloc0(sizeof(struct
+                                             DataObjectAnonymousStruct));
+    obj->anonymous_struct->data = list_to_memberlist(data);
+
+    return obj;
+}
+
+static strList *get_str_list(QObject *data)
+{
+    strList *str_list, *last_str_entry, *str_entry;
+    QList *qlist;
+    const QListEntry *lent;
+
+    qlist = qobject_to_qlist(data);
+    str_list = NULL;
+    for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
+        str_entry = g_malloc0(sizeof(strList *));
+        str_entry->value = g_strdup(qstring_copy_str(lent->value));
+        str_entry->next = NULL;
+        if (!str_list) {
+            str_list = str_entry;
+        } else {
+            last_str_entry->next = str_entry;
+        }
+        last_str_entry = str_entry;
+    }
+
+    return str_list;
+}
+
+static DataObject *visit_qobj_dict(QObject *data)
+{
+    DataObject *obj;
+    QObject *subdata;
+    QDict *qdict;
+
+    qdict = qobject_to_qdict(data);
+    assert(qdict != NULL);
+    obj = g_malloc0(sizeof(*obj));
+
+    if (qdict_get(qdict, "command")) {
+        obj->kind = DATA_OBJECT_KIND_COMMAND;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "command")));
+        obj->command = g_malloc0(sizeof(struct DataObjectCommand));
+
+        subdata = qdict_get(qdict, "data");
+        if (subdata && subdata->type->code == QTYPE_QDICT) {
+            obj->command->has_data = true;
+            obj->command->data = dict_to_memberlist(subdata);
+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
+            abort();
+        } else if (subdata) {
+            obj->command->has_data = true;
+            obj->command->data =
+                dict_to_memberlist(get_definition(qstring_copy_str(subdata),
+                                                  true));
+            pop_id();
+        }
+
+        subdata = qdict_get(qdict, "returns");
+        if (subdata && subdata->type->code == QTYPE_QDICT) {
+            abort();
+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
+            obj->command->has_returns = true;
+            obj->command->returns = visit_qobj_list(subdata);
+        } else if (subdata && subdata->type->code == QTYPE_QSTRING) {
+            obj->command->has_returns = true;
+            obj->command->returns = extend_type(qstring_copy_str(subdata));
+        }
+
+        subdata = qdict_get(qdict, "gen");
+        if (subdata && subdata->type->code == QTYPE_QSTRING) {
+            obj->command->has_gen = true;
+            if (!strcmp(qstring_copy_str(subdata), "no")) {
+                obj->command->gen = false;
+            } else {
+                obj->command->gen = true;
+            }
+        }
+    } else if (qdict_get(qdict, "union")) {
+        obj->kind = DATA_OBJECT_KIND_UNIONOBJ;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "union")));
+        obj->unionobj = g_malloc0(sizeof(struct DataObjectUnion));
+        subdata = qdict_get(qdict, "data");
+        obj->unionobj->data = dict_to_memberlist(subdata);
+    } else if (qdict_get(qdict, "type")) {
+        obj->kind = DATA_OBJECT_KIND_TYPE;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "type")));
+        obj->type = g_malloc0(sizeof(struct DataObjectType));
+        subdata = qdict_get(qdict, "data");
+        obj->type->data = dict_to_memberlist(subdata);
+    } else if (qdict_get(qdict, "enum")) {
+        obj->kind = DATA_OBJECT_KIND_ENUMERATION;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "enum")));
+        obj->enumeration = g_malloc0(sizeof(struct DataObjectEnumeration));
+        subdata = qdict_get(qdict, "data");
+        obj->enumeration->data = get_str_list(subdata);
+    } else {
+        obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
+        obj->anonymous_struct = g_malloc0(sizeof(struct
+                                                 DataObjectAnonymousStruct));
+        obj->anonymous_struct->data = dict_to_memberlist(data);
+    }
+
+    return obj;
+}
+
+DataObjectList *qmp_query_qmp_schema(Error **errp)
+{
+    DataObjectList *list, *last_entry, *entry;
+    QObject *data;
+    int i;
+
+    list = NULL;
+    for (i = 0; qmp_schema_table[i]; i++) {
+        data = qobject_from_json(qmp_schema_table[i]);
+        assert(data != NULL);
+
+        entry = g_malloc0(sizeof(DataObjectList *));
+        memset(visit_path_str, 0, sizeof(visit_path_str));
+        entry->value = visit_qobj_dict(data);
+        entry->next = NULL;
+        if (!list) {
+            list = entry;
+        } else {
+            last_entry->next = entry;
+        }
+        last_entry = entry;
+    }
+
+    return list;
+}
+
 void qmp_add_client(const char *protocol, const char *fdname,
                     bool has_skipauth, bool skipauth, bool has_tls, bool tls,
                     Error **errp)