qemu_qapi.c 15.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
    virJSONValuePtr returnType;
77
    size_t depth;
78 79 80
};


81 82 83 84 85 86 87 88 89 90 91 92 93
static int
virQEMUQAPISchemaTraverseContextValidateDepth(struct virQEMUQAPISchemaTraverseContext *ctxt)
{
    if (ctxt->depth++ > 1000) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("possible loop in QMP schema"));
        return -1;
    }

    return 0;
}


94 95 96 97 98 99 100 101 102 103 104 105 106 107
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)
{
108
    ctxt->prevquery = ctxt->queries[0];
109
    ctxt->queries++;
110
    return ctxt->prevquery;
111 112 113 114 115 116 117 118 119 120
}


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


121
static int
122
virQEMUQAPISchemaTraverse(const char *baseName,
123
                          struct virQEMUQAPISchemaTraverseContext *ctxt);
124 125


126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
/**
 * @featurename: name of 'feature' field to select
 * @elem: QAPI JSON entry for a type
 *
 * Looks for @featurename in the array of 'features' for given type passed in
 * via @elem. Returns 1 if @featurename is present, 0 if it's not present
 * (or @elem has no 'features') or -2 if the schema is malformed.
 * (see virQEMUQAPISchemaTraverseFunc)
 */
static int
virQEMUQAPISchemaTraverseHasObjectFeature(const char *featurename,
                                          virJSONValuePtr elem)
{
    virJSONValuePtr featuresarray;
    virJSONValuePtr cur;
    const char *curstr;
    size_t i;

    if (!(featuresarray = virJSONValueObjectGetArray(elem, "features")))
        return 0;

    for (i = 0; i < virJSONValueArraySize(featuresarray); i++) {
        if (!(cur = virJSONValueArrayGet(featuresarray, i)) ||
            !(curstr = virJSONValueGetString(cur)))
            return -2;

        if (STREQ(featurename, curstr))
            return 1;
    }

    return 0;
}


160 161
static int
virQEMUQAPISchemaTraverseObject(virJSONValuePtr cur,
162
                                struct virQEMUQAPISchemaTraverseContext *ctxt)
163
{
164
    virJSONValuePtr obj;
165
    const char *query = virQEMUQAPISchemaTraverseContextNextQuery(ctxt);
166
    char modifier = *query;
167 168

    if (!c_isalpha(modifier))
169
        query++;
170

171
    /* exit on modifers for other types */
172
    if (modifier == '^' || modifier == '!')
173 174
        return 0;

175 176 177 178 179 180 181
    if (modifier == '$') {
        if (virQEMUQAPISchemaTraverseContextHasNextQuery(ctxt))
            return -3;

        return virQEMUQAPISchemaTraverseHasObjectFeature(query, cur);
    }

182
    if (modifier == '+') {
183
        obj = virQEMUQAPISchemaObjectGet("variants", query, "case", cur);
184
    } else {
185
        obj = virQEMUQAPISchemaObjectGet("members", query, "name", cur);
186 187 188 189 190 191

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

192 193 194
    if (!obj)
        return 0;

195
    return virQEMUQAPISchemaTraverse(virJSONValueObjectGetString(obj, "type"), ctxt);
196 197 198 199 200
}


static int
virQEMUQAPISchemaTraverseArray(virJSONValuePtr cur,
201
                               struct virQEMUQAPISchemaTraverseContext *ctxt)
202 203 204 205 206
{
    const char *querytype;

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

209
    return virQEMUQAPISchemaTraverse(querytype, ctxt);
210
}
211

212 213 214

static int
virQEMUQAPISchemaTraverseCommand(virJSONValuePtr cur,
215
                                 struct virQEMUQAPISchemaTraverseContext *ctxt)
216
{
217
    const char *query = virQEMUQAPISchemaTraverseContextNextQuery(ctxt);
218
    const char *querytype;
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    char modifier = *query;

    if (!c_isalpha(modifier))
        query++;

    /* exit on modifers for other types */
    if (modifier == '^' || modifier == '!' || modifier == '+' || modifier == '*')
        return 0;

    if (modifier == '$') {
        if (virQEMUQAPISchemaTraverseContextHasNextQuery(ctxt))
            return -3;

        return virQEMUQAPISchemaTraverseHasObjectFeature(query, cur);
    }
234

235
    if (!(querytype = virJSONValueObjectGetString(cur, query)))
236 237
        return 0;

238
    return virQEMUQAPISchemaTraverse(querytype, ctxt);
239 240
}

241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275

static int
virQEMUQAPISchemaTraverseEnum(virJSONValuePtr cur,
                              struct virQEMUQAPISchemaTraverseContext *ctxt)
{
    const char *query = virQEMUQAPISchemaTraverseContextNextQuery(ctxt);
    virJSONValuePtr values;
    virJSONValuePtr enumval;
    const char *value;
    size_t i;

    if (query[0] != '^')
        return 0;

    if (virQEMUQAPISchemaTraverseContextHasNextQuery(ctxt))
        return -3;

    query++;

    if (!(values = virJSONValueObjectGetArray(cur, "values")))
        return -2;

    for (i = 0; i < virJSONValueArraySize(values); i++) {
        if (!(enumval = virJSONValueArrayGet(values, i)) ||
            !(value = virJSONValueGetString(enumval)))
            continue;

        if (STREQ(value, query))
            return 1;
    }

    return 0;
}


276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
static int
virQEMUQAPISchemaTraverseBuiltin(virJSONValuePtr cur,
                                 struct virQEMUQAPISchemaTraverseContext *ctxt)
{
    const char *query = virQEMUQAPISchemaTraverseContextNextQuery(ctxt);
    const char *jsontype;

    if (query[0] != '!')
        return 0;

    if (virQEMUQAPISchemaTraverseContextHasNextQuery(ctxt))
        return -3;

    query++;

    if (!(jsontype = virJSONValueObjectGetString(cur, "json-type")))
        return -1;

    if (STREQ(jsontype, query))
        return 1;

    return 0;
}


301 302 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
static int
virQEMUQAPISchemaTraverseAlternate(virJSONValuePtr cur,
                                   struct virQEMUQAPISchemaTraverseContext *ctxt)
{
    struct virQEMUQAPISchemaTraverseContext savectxt = *ctxt;
    virJSONValuePtr members;
    virJSONValuePtr member;
    const char *membertype;
    int rc;
    size_t i;

    if (!(members = virJSONValueObjectGetArray(cur, "members")))
        return -2;

    for (i = 0; i < virJSONValueArraySize(members); i++) {
        if (!(member = virJSONValueArrayGet(members, i)) ||
            !(membertype = virJSONValueObjectGetString(member, "type")))
            continue;

        *ctxt = savectxt;

        if ((rc = virQEMUQAPISchemaTraverse(membertype, ctxt)) != 0)
            return rc;
    }

    return 0;
}


330 331 332
/* 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. */
333 334 335 336 337 338 339 340 341 342 343 344 345 346
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 },
347
    { "enum", virQEMUQAPISchemaTraverseEnum },
348
    { "builtin", virQEMUQAPISchemaTraverseBuiltin },
349
    { "alternate", virQEMUQAPISchemaTraverseAlternate },
350 351 352
};


353 354
static int
virQEMUQAPISchemaTraverse(const char *baseName,
355
                          struct virQEMUQAPISchemaTraverseContext *ctxt)
356 357 358
{
    virJSONValuePtr cur;
    const char *metatype;
359
    size_t i;
360

361 362 363
    if (virQEMUQAPISchemaTraverseContextValidateDepth(ctxt) < 0)
        return -2;

364
    if (!(cur = virHashLookup(ctxt->schema, baseName)))
365
        return -2;
366

367
    if (!virQEMUQAPISchemaTraverseContextHasNextQuery(ctxt)) {
368
        ctxt->returnType = cur;
369 370
        return 1;
    }
371

372
    if (!(metatype = virJSONValueObjectGetString(cur, "meta-type")))
373
        return -2;
374

375
    for (i = 0; i < G_N_ELEMENTS(traverseMetaType); i++) {
376 377
        if (STREQ(metatype, traverseMetaType[i].metatype))
            return traverseMetaType[i].func(cur, ctxt);
378
    }
379

380
    return 0;
381 382 383 384
}


/**
385
 * virQEMUQAPISchemaPathGet:
386 387
 * @query: string specifying the required data type (see below)
 * @schema: hash table containing the schema data
388
 * @entry: filled with the located schema object requested by @query (optional)
389 390 391 392 393
 *
 * 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:
 *
394
 * "command_or_event/attribute/subattribute/subattribute/..."
395 396 397 398
 *
 * 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)
399 400 401 402 403 404 405 406 407 408 409 410
 *
 * 'subattribute' may be one or more of the following depending on the first
 * character.
 *
 * - Type queries - @entry is filled on success with the corresponding schema entry:
 *   'subattribute': selects a plain object member named 'subattribute'
 *   '*subattribute': same as above but the selected member must be optional
 *                    (has a property named 'default' in the schema)
 *   '+variant": In the case of unionized objects, select a specific variant of
 *               the prevously selected member
 *
 * - Boolean queries - @entry remains NULL, return value indicates success:
411
 *   '^enumval': returns true if the previously selected enum contains 'enumval'
412 413 414
 *   '!basictype': returns true if previously selected type is of 'basictype'
 *                 JSON type. Spported are 'null', 'string', 'number', 'value',
 *                 'int' and 'boolean.
415 416
 *   '$feature': returns true if the previously selected type supports 'feature'
 *               ('feature' is in the 'features' array of given type)
417
 *
418 419 420
 * If the name of any (sub)attribute starts with non-alphabetical symbols it
 * needs to be prefixed by a single space.
 *
421 422
 * Array types are automatically flattened to the singular type. Alternates are
 * iterated until first success.
423 424
 *
 * The above types can be chained arbitrarily using slashes to construct any
425 426
 * path into the schema tree, booleans must be always the last component as they
 * don't refer to a type.
427
 *
428 429
 * 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
430 431 432
 * error message.
 */
int
433 434 435
virQEMUQAPISchemaPathGet(const char *query,
                         virHashTablePtr schema,
                         virJSONValuePtr *entry)
436
{
437
    VIR_AUTOSTRINGLIST elems = NULL;
438
    struct virQEMUQAPISchemaTraverseContext ctxt;
439
    const char *cmdname;
440
    int rc;
441

442 443
    if (entry)
        *entry = NULL;
444 445 446 447 448 449 450 451 452

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

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

453
    virQEMUQAPISchemaTraverseContextInit(&ctxt, elems, schema);
454
    cmdname = virQEMUQAPISchemaTraverseContextNextQuery(&ctxt);
455

456 457 458 459
    if (!virHashLookup(schema, cmdname))
        return 0;

    rc = virQEMUQAPISchemaTraverse(cmdname, &ctxt);
460 461 462 463

    if (entry)
        *entry = ctxt.returnType;

464 465 466 467 468 469 470 471 472 473 474 475 476 477
    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;
478 479 480 481
}


bool
482 483
virQEMUQAPISchemaPathExists(const char *query,
                            virHashTablePtr schema)
484
{
485
    return virQEMUQAPISchemaPathGet(query, schema, NULL) == 1;
486
}
487 488

static int
J
Ján Tomko 已提交
489
virQEMUQAPISchemaEntryProcess(size_t pos G_GNUC_UNUSED,
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
                              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)
{
J
Ján Tomko 已提交
519 520
    g_autoptr(virHashTable) schema = NULL;
    g_autoptr(virJSONValue) schemajson = schemareply;
521 522

    if (!(schema = virHashCreate(512, virJSONValueHashFree)))
523
        return NULL;
524

525
    if (virJSONValueArrayForeachSteal(schemajson,
526 527
                                      virQEMUQAPISchemaEntryProcess,
                                      schema) < 0)
528
        return NULL;
529

J
Ján Tomko 已提交
530
    return g_steal_pointer(&schema);
531
}