qemu_qapi.c 10.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 * 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"

28 29
#include "c-ctype.h"

30 31 32 33 34 35
#define VIR_FROM_THIS VIR_FROM_QEMU

VIR_LOG_INIT("qemu.qemu_qapi");


/**
36
 * virQEMUQAPISchemaObjectGet:
37 38 39
 * @field: name of the object containing the requested type
 * @name: name of the requested type
 * @namefield: name of the object property holding @name
40
 * @elem: QAPI schema entry JSON object
41 42
 *
 * Helper that selects the type of a QMP schema object member or it's variant
43
 * member. Returns the QMP entry on success or NULL on error.
44
 */
45 46 47 48 49
static virJSONValuePtr
virQEMUQAPISchemaObjectGet(const char *field,
                           const char *name,
                           const char *namefield,
                           virJSONValuePtr elem)
50 51 52 53 54 55 56 57 58 59 60
{
    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)) ||
61
            !(curname = virJSONValueObjectGetString(cur, namefield)))
62 63 64
            continue;

        if (STREQ(name, curname))
65
            return cur;
66 67 68 69 70 71
    }

    return NULL;
}


72
struct virQEMUQAPISchemaTraverseContext {
73
    const char *prevquery;
74
    virHashTablePtr schema;
75
    char **queries;
76 77 78 79
    virJSONValuePtr returnType;
};


80 81 82 83 84 85 86 87 88 89 90 91 92 93
static void
virQEMUQAPISchemaTraverseContextInit(struct virQEMUQAPISchemaTraverseContext *ctxt,
                                     char **queries,
                                     virHashTablePtr schema)
{
    memset(ctxt, 0, sizeof(*ctxt));
    ctxt->schema = schema;
    ctxt->queries = queries;
}


static const char *
virQEMUQAPISchemaTraverseContextNextQuery(struct virQEMUQAPISchemaTraverseContext *ctxt)
{
94
    ctxt->prevquery = ctxt->queries[0];
95
    ctxt->queries++;
96
    return ctxt->prevquery;
97 98 99 100 101 102 103 104 105 106
}


static bool
virQEMUQAPISchemaTraverseContextHasNextQuery(struct virQEMUQAPISchemaTraverseContext *ctxt)
{
    return !!ctxt->queries[0];
}


107
static int
108
virQEMUQAPISchemaTraverse(const char *baseName,
109
                          struct virQEMUQAPISchemaTraverseContext *ctxt);
110 111 112 113


static int
virQEMUQAPISchemaTraverseObject(virJSONValuePtr cur,
114
                                struct virQEMUQAPISchemaTraverseContext *ctxt)
115
{
116
    virJSONValuePtr obj;
117
    const char *query = virQEMUQAPISchemaTraverseContextNextQuery(ctxt);
118
    char modifier = *query;
119 120

    if (!c_isalpha(modifier))
121
        query++;
122 123

    if (modifier == '+') {
124
        obj = virQEMUQAPISchemaObjectGet("variants", query, "case", cur);
125
    } else {
126
        obj = virQEMUQAPISchemaObjectGet("members", query, "name", cur);
127 128 129 130 131 132

        if (modifier == '*' &&
            !virJSONValueObjectHasKey(obj, "default"))
            return 0;
    }

133 134 135
    if (!obj)
        return 0;

136
    return virQEMUQAPISchemaTraverse(virJSONValueObjectGetString(obj, "type"), ctxt);
137 138 139 140 141
}


static int
virQEMUQAPISchemaTraverseArray(virJSONValuePtr cur,
142
                               struct virQEMUQAPISchemaTraverseContext *ctxt)
143 144 145 146 147
{
    const char *querytype;

    /* arrays are just flattened by default */
    if (!(querytype = virJSONValueObjectGetString(cur, "element-type")))
148
        return -2;
149

150
    return virQEMUQAPISchemaTraverse(querytype, ctxt);
151
}
152

153 154 155

static int
virQEMUQAPISchemaTraverseCommand(virJSONValuePtr cur,
156
                                 struct virQEMUQAPISchemaTraverseContext *ctxt)
157
{
158
    const char *query = virQEMUQAPISchemaTraverseContextNextQuery(ctxt);
159 160
    const char *querytype;

161
    if (!(querytype = virJSONValueObjectGetString(cur, query)))
162 163
        return 0;

164
    return virQEMUQAPISchemaTraverse(querytype, ctxt);
165 166
}

167 168 169
/* The function must return 1 on successful query, 0 if the query was not found
 * -1 when a libvirt error is reported, -2 if the schema is invalid and -3 if
 *  the query component is malformed. */
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
typedef int (*virQEMUQAPISchemaTraverseFunc)(virJSONValuePtr cur,
                                             struct virQEMUQAPISchemaTraverseContext *ctxt);

struct virQEMUQAPISchemaTraverseMetaType {
    const char *metatype;
    virQEMUQAPISchemaTraverseFunc func;
};


static const struct virQEMUQAPISchemaTraverseMetaType traverseMetaType[] = {
    { "object", virQEMUQAPISchemaTraverseObject },
    { "array", virQEMUQAPISchemaTraverseArray },
    { "command", virQEMUQAPISchemaTraverseCommand },
    { "event", virQEMUQAPISchemaTraverseCommand },
};


187 188
static int
virQEMUQAPISchemaTraverse(const char *baseName,
189
                          struct virQEMUQAPISchemaTraverseContext *ctxt)
190 191 192
{
    virJSONValuePtr cur;
    const char *metatype;
193
    size_t i;
194

195
    if (!(cur = virHashLookup(ctxt->schema, baseName)))
196
        return -2;
197

198
    if (!virQEMUQAPISchemaTraverseContextHasNextQuery(ctxt)) {
199
        ctxt->returnType = cur;
200 201
        return 1;
    }
202

203
    if (!(metatype = virJSONValueObjectGetString(cur, "meta-type")))
204
        return -2;
205

206 207 208
    for (i = 0; i < ARRAY_CARDINALITY(traverseMetaType); i++) {
        if (STREQ(metatype, traverseMetaType[i].metatype))
            return traverseMetaType[i].func(cur, ctxt);
209
    }
210

211
    return 0;
212 213 214 215
}


/**
216
 * virQEMUQAPISchemaPathGet:
217 218
 * @query: string specifying the required data type (see below)
 * @schema: hash table containing the schema data
219
 * @entry: filled with the located schema object requested by @query (optional)
220 221 222 223 224 225 226 227 228 229 230
 *
 * 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
231 232
 * *subattribute: same as above but must be optional (has a property named
 *                'default' field in the schema)
233 234 235
 * +variant_discriminator: In the case of unionized objects, select a
 *                         specific case to introspect.
 *
236 237 238
 * If the name of any (sub)attribute starts with non-alphabetical symbols it
 * needs to be prefixed by a single space.
 *
239 240 241 242 243 244
 * 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.
 *
245 246
 * 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
247 248 249
 * error message.
 */
int
250 251 252
virQEMUQAPISchemaPathGet(const char *query,
                         virHashTablePtr schema,
                         virJSONValuePtr *entry)
253
{
254
    VIR_AUTOSTRINGLIST elems = NULL;
255
    struct virQEMUQAPISchemaTraverseContext ctxt;
256
    const char *cmdname;
257
    int rc;
258

259 260
    if (entry)
        *entry = NULL;
261 262 263 264 265 266 267 268 269

    if (!(elems = virStringSplit(query, "/", 0)))
        return -1;

    if (!*elems) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed query string"));
        return -1;
    }

270
    virQEMUQAPISchemaTraverseContextInit(&ctxt, elems, schema);
271
    cmdname = virQEMUQAPISchemaTraverseContextNextQuery(&ctxt);
272

273 274 275 276
    if (!virHashLookup(schema, cmdname))
        return 0;

    rc = virQEMUQAPISchemaTraverse(cmdname, &ctxt);
277 278 279 280

    if (entry)
        *entry = ctxt.returnType;

281 282 283 284 285 286 287 288 289 290 291 292 293 294
    if (rc >= 0)
        return rc;

    if (rc == -2) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("malformed QAPI schema when querying '%s' of '%s'"),
                       NULLSTR(ctxt.prevquery), query);
    } else if (rc == -3) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("terminal QAPI query component '%s' of '%s' must not have followers"),
                       NULLSTR(ctxt.prevquery), query);
    }

    return -1;
295 296 297 298
}


bool
299 300
virQEMUQAPISchemaPathExists(const char *query,
                            virHashTablePtr schema)
301
{
302
    return virQEMUQAPISchemaPathGet(query, schema, NULL) == 1;
303
}
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335

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)
{
336 337
    VIR_AUTOPTR(virHashTable) schema = NULL;
    VIR_AUTOPTR(virJSONValue) schemajson = schemareply;
338 339

    if (!(schema = virHashCreate(512, virJSONValueHashFree)))
340
        return NULL;
341

342
    if (virJSONValueArrayForeachSteal(schemajson,
343 344
                                      virQEMUQAPISchemaEntryProcess,
                                      schema) < 0)
345
        return NULL;
346

347
    VIR_RETURN_PTR(schema);
348
}