2018-03-20 07:44:11 +00:00
|
|
|
/*
|
|
|
|
* qemu_qapi.c: helper functions for QEMU QAPI schema handling
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library. If not, see
|
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include "qemu_qapi.h"
|
|
|
|
|
|
|
|
#include "viralloc.h"
|
|
|
|
#include "virstring.h"
|
|
|
|
#include "virerror.h"
|
|
|
|
#include "virlog.h"
|
|
|
|
|
2018-08-15 06:32:04 +00:00
|
|
|
#include "c-ctype.h"
|
|
|
|
|
2018-03-20 07:44:11 +00:00
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
|
|
|
|
VIR_LOG_INIT("qemu.qemu_qapi");
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-08-15 06:39:19 +00:00
|
|
|
* virQEMUQAPISchemaObjectGet:
|
2018-03-20 07:44:11 +00:00
|
|
|
* @field: name of the object containing the requested type
|
|
|
|
* @name: name of the requested type
|
|
|
|
* @namefield: name of the object property holding @name
|
2018-08-15 06:39:19 +00:00
|
|
|
* @elem: QAPI schema entry JSON object
|
2018-03-20 07:44:11 +00:00
|
|
|
*
|
|
|
|
* Helper that selects the type of a QMP schema object member or it's variant
|
2018-08-15 06:39:19 +00:00
|
|
|
* member. Returns the QMP entry on success or NULL on error.
|
2018-03-20 07:44:11 +00:00
|
|
|
*/
|
2018-08-15 06:39:19 +00:00
|
|
|
static virJSONValuePtr
|
|
|
|
virQEMUQAPISchemaObjectGet(const char *field,
|
|
|
|
const char *name,
|
|
|
|
const char *namefield,
|
|
|
|
virJSONValuePtr elem)
|
2018-03-20 07:44:11 +00:00
|
|
|
{
|
|
|
|
virJSONValuePtr arr;
|
|
|
|
virJSONValuePtr cur;
|
|
|
|
const char *curname;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!(arr = virJSONValueObjectGetArray(elem, field)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < virJSONValueArraySize(arr); i++) {
|
|
|
|
if (!(cur = virJSONValueArrayGet(arr, i)) ||
|
2018-08-15 06:39:19 +00:00
|
|
|
!(curname = virJSONValueObjectGetString(cur, namefield)))
|
2018-03-20 07:44:11 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (STREQ(name, curname))
|
2018-08-15 06:39:19 +00:00
|
|
|
return cur;
|
2018-03-20 07:44:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
struct virQEMUQAPISchemaTraverseContext {
|
|
|
|
virHashTablePtr schema;
|
|
|
|
char **query;
|
|
|
|
virJSONValuePtr returnType;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2019-04-09 11:25:49 +00:00
|
|
|
static int
|
2018-03-20 07:57:44 +00:00
|
|
|
virQEMUQAPISchemaTraverse(const char *baseName,
|
2019-04-12 10:54:11 +00:00
|
|
|
struct virQEMUQAPISchemaTraverseContext *ctxt);
|
2019-04-10 12:14:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virQEMUQAPISchemaTraverseObject(virJSONValuePtr cur,
|
2019-04-12 10:54:11 +00:00
|
|
|
struct virQEMUQAPISchemaTraverseContext *ctxt)
|
2018-03-20 07:44:11 +00:00
|
|
|
{
|
2018-08-15 06:51:01 +00:00
|
|
|
virJSONValuePtr obj;
|
2019-04-12 13:55:54 +00:00
|
|
|
const char *query = *ctxt->query;
|
|
|
|
char modifier = *query;
|
2019-04-10 12:14:10 +00:00
|
|
|
|
|
|
|
if (!c_isalpha(modifier))
|
2019-04-12 13:55:54 +00:00
|
|
|
query++;
|
2019-04-10 12:14:10 +00:00
|
|
|
|
|
|
|
if (modifier == '+') {
|
2019-04-12 13:55:54 +00:00
|
|
|
obj = virQEMUQAPISchemaObjectGet("variants", query, "case", cur);
|
2019-04-10 12:14:10 +00:00
|
|
|
} else {
|
2019-04-12 13:55:54 +00:00
|
|
|
obj = virQEMUQAPISchemaObjectGet("members", query, "name", cur);
|
2019-04-10 12:14:10 +00:00
|
|
|
|
|
|
|
if (modifier == '*' &&
|
|
|
|
!virJSONValueObjectHasKey(obj, "default"))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-12 07:19:39 +00:00
|
|
|
if (!obj)
|
|
|
|
return 0;
|
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
ctxt->query++;
|
|
|
|
|
|
|
|
return virQEMUQAPISchemaTraverse(virJSONValueObjectGetString(obj, "type"), ctxt);
|
2019-04-10 12:14:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virQEMUQAPISchemaTraverseArray(virJSONValuePtr cur,
|
2019-04-12 10:54:11 +00:00
|
|
|
struct virQEMUQAPISchemaTraverseContext *ctxt)
|
2019-04-10 12:14:10 +00:00
|
|
|
{
|
|
|
|
const char *querytype;
|
|
|
|
|
|
|
|
/* arrays are just flattened by default */
|
|
|
|
if (!(querytype = virJSONValueObjectGetString(cur, "element-type")))
|
|
|
|
return 0;
|
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
return virQEMUQAPISchemaTraverse(querytype, ctxt);
|
2019-04-10 12:14:10 +00:00
|
|
|
}
|
2018-03-20 07:44:11 +00:00
|
|
|
|
2019-04-10 12:14:10 +00:00
|
|
|
|
|
|
|
static int
|
|
|
|
virQEMUQAPISchemaTraverseCommand(virJSONValuePtr cur,
|
2019-04-12 10:54:11 +00:00
|
|
|
struct virQEMUQAPISchemaTraverseContext *ctxt)
|
2019-04-10 12:14:10 +00:00
|
|
|
{
|
|
|
|
const char *querytype;
|
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
if (!(querytype = virJSONValueObjectGetString(cur, *ctxt->query)))
|
2019-04-10 12:14:10 +00:00
|
|
|
return 0;
|
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
ctxt->query++;
|
|
|
|
|
|
|
|
return virQEMUQAPISchemaTraverse(querytype, ctxt);
|
2019-04-10 12:14:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virQEMUQAPISchemaTraverse(const char *baseName,
|
2019-04-12 10:54:11 +00:00
|
|
|
struct virQEMUQAPISchemaTraverseContext *ctxt)
|
2019-04-10 12:14:10 +00:00
|
|
|
{
|
|
|
|
virJSONValuePtr cur;
|
|
|
|
const char *metatype;
|
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
if (!(cur = virHashLookup(ctxt->schema, baseName)))
|
2019-04-09 13:11:22 +00:00
|
|
|
return 0;
|
2018-03-20 07:44:11 +00:00
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
if (!ctxt->query[0]) {
|
|
|
|
ctxt->returnType = cur;
|
2019-04-09 13:11:22 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2018-08-15 06:32:04 +00:00
|
|
|
|
2019-04-10 12:14:10 +00:00
|
|
|
if (!(metatype = virJSONValueObjectGetString(cur, "meta-type")))
|
2019-04-09 13:11:22 +00:00
|
|
|
return 0;
|
2018-08-15 06:32:04 +00:00
|
|
|
|
2019-04-09 13:11:22 +00:00
|
|
|
if (STREQ(metatype, "array")) {
|
2019-04-12 10:54:11 +00:00
|
|
|
return virQEMUQAPISchemaTraverseArray(cur, ctxt);
|
2019-04-09 13:11:22 +00:00
|
|
|
} else if (STREQ(metatype, "object")) {
|
2019-04-12 10:54:11 +00:00
|
|
|
return virQEMUQAPISchemaTraverseObject(cur, ctxt);
|
2019-04-09 13:11:22 +00:00
|
|
|
} else if (STREQ(metatype, "command") ||
|
|
|
|
STREQ(metatype, "event")) {
|
2019-04-12 10:54:11 +00:00
|
|
|
return virQEMUQAPISchemaTraverseCommand(cur, ctxt);
|
2019-04-09 13:11:22 +00:00
|
|
|
} else {
|
|
|
|
/* alternates, basic types and enums can't be entered */
|
|
|
|
return 0;
|
2018-03-21 15:51:29 +00:00
|
|
|
}
|
2018-03-20 07:44:11 +00:00
|
|
|
|
2019-04-10 12:14:10 +00:00
|
|
|
return 0;
|
2018-03-20 07:44:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-03-20 07:57:44 +00:00
|
|
|
* virQEMUQAPISchemaPathGet:
|
2018-03-20 07:44:11 +00:00
|
|
|
* @query: string specifying the required data type (see below)
|
|
|
|
* @schema: hash table containing the schema data
|
2019-04-09 12:09:33 +00:00
|
|
|
* @entry: filled with the located schema object requested by @query (optional)
|
2018-03-20 07:44:11 +00:00
|
|
|
*
|
|
|
|
* Retrieves the requested schema entry specified by @query to @entry. The
|
|
|
|
* @query parameter has the following syntax which is very closely tied to the
|
|
|
|
* qemu schema syntax entries separated by slashes with a few special characters:
|
|
|
|
*
|
|
|
|
* "command_or_event/attribute/subattribute/+variant_discriminator/subattribute"
|
|
|
|
*
|
|
|
|
* command_or_event: name of the event or attribute to introspect
|
|
|
|
* attribute: selects whether arguments or return type should be introspected
|
|
|
|
* ("arg-type" or "ret-type" for commands, "arg-type" for events)
|
|
|
|
* subattribute: specifies member name of object types
|
2018-08-15 06:51:01 +00:00
|
|
|
* *subattribute: same as above but must be optional (has a property named
|
|
|
|
* 'default' field in the schema)
|
2018-03-20 07:44:11 +00:00
|
|
|
* +variant_discriminator: In the case of unionized objects, select a
|
|
|
|
* specific case to introspect.
|
|
|
|
*
|
2018-08-15 06:32:04 +00:00
|
|
|
* If the name of any (sub)attribute starts with non-alphabetical symbols it
|
|
|
|
* needs to be prefixed by a single space.
|
|
|
|
*
|
2018-03-20 07:44:11 +00:00
|
|
|
* Array types are automatically flattened to the singular type. Alternate
|
|
|
|
* types are currently not supported.
|
|
|
|
*
|
|
|
|
* The above types can be chained arbitrarily using slashes to construct any
|
|
|
|
* path into the schema tree.
|
|
|
|
*
|
2019-04-09 12:09:33 +00:00
|
|
|
* Returns 1 if @query was found in @schema filling @entry if non-NULL, 0 if
|
|
|
|
* @query was not found in @schema and -1 on other errors along with an appropriate
|
2018-03-20 07:44:11 +00:00
|
|
|
* error message.
|
|
|
|
*/
|
|
|
|
int
|
2018-03-20 07:57:44 +00:00
|
|
|
virQEMUQAPISchemaPathGet(const char *query,
|
|
|
|
virHashTablePtr schema,
|
|
|
|
virJSONValuePtr *entry)
|
2018-03-20 07:44:11 +00:00
|
|
|
{
|
2019-04-09 08:36:16 +00:00
|
|
|
VIR_AUTOSTRINGLIST elems = NULL;
|
2019-04-12 10:54:11 +00:00
|
|
|
struct virQEMUQAPISchemaTraverseContext ctxt = { .schema = schema };
|
|
|
|
int rc;
|
2018-03-20 07:44:11 +00:00
|
|
|
|
2019-04-09 12:09:33 +00:00
|
|
|
if (entry)
|
|
|
|
*entry = NULL;
|
2018-03-20 07:44:11 +00:00
|
|
|
|
|
|
|
if (!(elems = virStringSplit(query, "/", 0)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!*elems) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed query string"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-04-12 10:54:11 +00:00
|
|
|
ctxt.query = elems + 1;
|
|
|
|
|
|
|
|
rc = virQEMUQAPISchemaTraverse(elems[0], &ctxt);
|
|
|
|
|
|
|
|
if (entry)
|
|
|
|
*entry = ctxt.returnType;
|
|
|
|
|
|
|
|
return rc;
|
2018-03-20 07:44:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
2018-03-20 07:57:44 +00:00
|
|
|
virQEMUQAPISchemaPathExists(const char *query,
|
|
|
|
virHashTablePtr schema)
|
2018-03-20 07:44:11 +00:00
|
|
|
{
|
2019-04-09 12:09:33 +00:00
|
|
|
return virQEMUQAPISchemaPathGet(query, schema, NULL) == 1;
|
2018-03-20 07:44:11 +00:00
|
|
|
}
|
2018-03-20 08:29:30 +00:00
|
|
|
|
|
|
|
static int
|
|
|
|
virQEMUQAPISchemaEntryProcess(size_t pos ATTRIBUTE_UNUSED,
|
|
|
|
virJSONValuePtr item,
|
|
|
|
void *opaque)
|
|
|
|
{
|
|
|
|
const char *name;
|
|
|
|
virHashTablePtr schema = opaque;
|
|
|
|
|
|
|
|
if (!(name = virJSONValueObjectGetString(item, "name"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("malformed QMP schema"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virHashAddEntry(schema, name, item) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virQEMUQAPISchemaConvert:
|
|
|
|
* @schemareply: Schema data as returned by the qemu monitor
|
|
|
|
*
|
|
|
|
* Converts the schema into the hash-table used by the functions working with
|
|
|
|
* the schema. @schemareply is consumed and freed.
|
|
|
|
*/
|
|
|
|
virHashTablePtr
|
|
|
|
virQEMUQAPISchemaConvert(virJSONValuePtr schemareply)
|
|
|
|
{
|
2019-04-09 08:36:16 +00:00
|
|
|
VIR_AUTOPTR(virHashTable) schema = NULL;
|
|
|
|
VIR_AUTOPTR(virJSONValue) schemajson = schemareply;
|
2018-03-20 08:29:30 +00:00
|
|
|
|
|
|
|
if (!(schema = virHashCreate(512, virJSONValueHashFree)))
|
2019-04-09 08:36:16 +00:00
|
|
|
return NULL;
|
2018-03-20 08:29:30 +00:00
|
|
|
|
2019-04-09 08:36:16 +00:00
|
|
|
if (virJSONValueArrayForeachSteal(schemajson,
|
2018-03-20 08:29:30 +00:00
|
|
|
virQEMUQAPISchemaEntryProcess,
|
|
|
|
schema) < 0)
|
2019-04-09 08:36:16 +00:00
|
|
|
return NULL;
|
2018-03-20 08:29:30 +00:00
|
|
|
|
2019-04-09 08:36:16 +00:00
|
|
|
VIR_RETURN_PTR(schema);
|
2018-03-20 08:29:30 +00:00
|
|
|
}
|