qemu/qapi/opts-visitor.c
<<
>>
Prefs
   1/*
   2 * Options Visitor
   3 *
   4 * Copyright Red Hat, Inc. 2012-2016
   5 *
   6 * Author: Laszlo Ersek <lersek@redhat.com>
   7 *
   8 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
   9 * See the COPYING.LIB file in the top-level directory.
  10 *
  11 */
  12
  13#include "qemu/osdep.h"
  14#include "qapi/error.h"
  15#include "qemu/cutils.h"
  16#include "qapi/qmp/qerror.h"
  17#include "qapi/opts-visitor.h"
  18#include "qemu/queue.h"
  19#include "qemu/option_int.h"
  20#include "qapi/visitor-impl.h"
  21
  22
  23enum ListMode
  24{
  25    LM_NONE,             /* not traversing a list of repeated options */
  26
  27    LM_IN_PROGRESS,      /* opts_next_list() ready to be called.
  28                          *
  29                          * Generating the next list link will consume the most
  30                          * recently parsed QemuOpt instance of the repeated
  31                          * option.
  32                          *
  33                          * Parsing a value into the list link will examine the
  34                          * next QemuOpt instance of the repeated option, and
  35                          * possibly enter LM_SIGNED_INTERVAL or
  36                          * LM_UNSIGNED_INTERVAL.
  37                          */
  38
  39    LM_SIGNED_INTERVAL,  /* opts_next_list() has been called.
  40                          *
  41                          * Generating the next list link will consume the most
  42                          * recently stored element from the signed interval,
  43                          * parsed from the most recent QemuOpt instance of the
  44                          * repeated option. This may consume QemuOpt itself
  45                          * and return to LM_IN_PROGRESS.
  46                          *
  47                          * Parsing a value into the list link will store the
  48                          * next element of the signed interval.
  49                          */
  50
  51    LM_UNSIGNED_INTERVAL /* Same as above, only for an unsigned interval. */
  52};
  53
  54typedef enum ListMode ListMode;
  55
  56struct OptsVisitor
  57{
  58    Visitor visitor;
  59
  60    /* Ownership remains with opts_visitor_new()'s caller. */
  61    const QemuOpts *opts_root;
  62
  63    unsigned depth;
  64
  65    /* Non-null iff depth is positive. Each key is a QemuOpt name. Each value
  66     * is a non-empty GQueue, enumerating all QemuOpt occurrences with that
  67     * name. */
  68    GHashTable *unprocessed_opts;
  69
  70    /* The list currently being traversed with opts_start_list() /
  71     * opts_next_list(). The list must have a struct element type in the
  72     * schema, with a single mandatory scalar member. */
  73    ListMode list_mode;
  74    GQueue *repeated_opts;
  75
  76    /* When parsing a list of repeating options as integers, values of the form
  77     * "a-b", representing a closed interval, are allowed. Elements in the
  78     * range are generated individually.
  79     */
  80    union {
  81        int64_t s;
  82        uint64_t u;
  83    } range_next, range_limit;
  84
  85    /* If "opts_root->id" is set, reinstantiate it as a fake QemuOpt for
  86     * uniformity. Only its "name" and "str" fields are set. "fake_id_opt" does
  87     * not survive or escape the OptsVisitor object.
  88     */
  89    QemuOpt *fake_id_opt;
  90};
  91
  92
  93static OptsVisitor *to_ov(Visitor *v)
  94{
  95    return container_of(v, OptsVisitor, visitor);
  96}
  97
  98
  99static void
 100destroy_list(gpointer list)
 101{
 102  g_queue_free(list);
 103}
 104
 105
 106static void
 107opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt)
 108{
 109    GQueue *list;
 110
 111    list = g_hash_table_lookup(unprocessed_opts, opt->name);
 112    if (list == NULL) {
 113        list = g_queue_new();
 114
 115        /* GHashTable will never try to free the keys -- we supply NULL as
 116         * "key_destroy_func" in opts_start_struct(). Thus cast away key
 117         * const-ness in order to suppress gcc's warning.
 118         */
 119        g_hash_table_insert(unprocessed_opts, (gpointer)opt->name, list);
 120    }
 121
 122    /* Similarly, destroy_list() doesn't call g_queue_free_full(). */
 123    g_queue_push_tail(list, (gpointer)opt);
 124}
 125
 126
 127static void
 128opts_start_struct(Visitor *v, const char *name, void **obj,
 129                  size_t size, Error **errp)
 130{
 131    OptsVisitor *ov = to_ov(v);
 132    const QemuOpt *opt;
 133
 134    if (obj) {
 135        *obj = g_malloc0(size);
 136    }
 137    if (ov->depth++ > 0) {
 138        return;
 139    }
 140
 141    ov->unprocessed_opts = g_hash_table_new_full(&g_str_hash, &g_str_equal,
 142                                                 NULL, &destroy_list);
 143    QTAILQ_FOREACH(opt, &ov->opts_root->head, next) {
 144        /* ensured by qemu-option.c::opts_do_parse() */
 145        assert(strcmp(opt->name, "id") != 0);
 146
 147        opts_visitor_insert(ov->unprocessed_opts, opt);
 148    }
 149
 150    if (ov->opts_root->id != NULL) {
 151        ov->fake_id_opt = g_malloc0(sizeof *ov->fake_id_opt);
 152
 153        ov->fake_id_opt->name = g_strdup("id");
 154        ov->fake_id_opt->str = g_strdup(ov->opts_root->id);
 155        opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt);
 156    }
 157}
 158
 159
 160static void
 161opts_check_struct(Visitor *v, Error **errp)
 162{
 163    OptsVisitor *ov = to_ov(v);
 164    GHashTableIter iter;
 165    GQueue *any;
 166
 167    if (ov->depth > 1) {
 168        return;
 169    }
 170
 171    /* we should have processed all (distinct) QemuOpt instances */
 172    g_hash_table_iter_init(&iter, ov->unprocessed_opts);
 173    if (g_hash_table_iter_next(&iter, NULL, (void **)&any)) {
 174        const QemuOpt *first;
 175
 176        first = g_queue_peek_head(any);
 177        error_setg(errp, QERR_INVALID_PARAMETER, first->name);
 178    }
 179}
 180
 181
 182static void
 183opts_end_struct(Visitor *v, void **obj)
 184{
 185    OptsVisitor *ov = to_ov(v);
 186
 187    if (--ov->depth > 0) {
 188        return;
 189    }
 190
 191    g_hash_table_destroy(ov->unprocessed_opts);
 192    ov->unprocessed_opts = NULL;
 193    if (ov->fake_id_opt) {
 194        g_free(ov->fake_id_opt->name);
 195        g_free(ov->fake_id_opt->str);
 196        g_free(ov->fake_id_opt);
 197    }
 198    ov->fake_id_opt = NULL;
 199}
 200
 201
 202static GQueue *
 203lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
 204{
 205    GQueue *list;
 206
 207    list = g_hash_table_lookup(ov->unprocessed_opts, name);
 208    if (!list) {
 209        error_setg(errp, QERR_MISSING_PARAMETER, name);
 210    }
 211    return list;
 212}
 213
 214
 215static void
 216opts_start_list(Visitor *v, const char *name, GenericList **list, size_t size,
 217                Error **errp)
 218{
 219    OptsVisitor *ov = to_ov(v);
 220
 221    /* we can't traverse a list in a list */
 222    assert(ov->list_mode == LM_NONE);
 223    /* we don't support visits without a list */
 224    assert(list);
 225    ov->repeated_opts = lookup_distinct(ov, name, errp);
 226    if (ov->repeated_opts) {
 227        ov->list_mode = LM_IN_PROGRESS;
 228        *list = g_malloc0(size);
 229    } else {
 230        *list = NULL;
 231    }
 232}
 233
 234
 235static GenericList *
 236opts_next_list(Visitor *v, GenericList *tail, size_t size)
 237{
 238    OptsVisitor *ov = to_ov(v);
 239
 240    switch (ov->list_mode) {
 241    case LM_SIGNED_INTERVAL:
 242    case LM_UNSIGNED_INTERVAL:
 243        if (ov->list_mode == LM_SIGNED_INTERVAL) {
 244            if (ov->range_next.s < ov->range_limit.s) {
 245                ++ov->range_next.s;
 246                break;
 247            }
 248        } else if (ov->range_next.u < ov->range_limit.u) {
 249            ++ov->range_next.u;
 250            break;
 251        }
 252        ov->list_mode = LM_IN_PROGRESS;
 253        /* range has been completed, fall through in order to pop option */
 254
 255    case LM_IN_PROGRESS: {
 256        const QemuOpt *opt;
 257
 258        opt = g_queue_pop_head(ov->repeated_opts);
 259        if (g_queue_is_empty(ov->repeated_opts)) {
 260            g_hash_table_remove(ov->unprocessed_opts, opt->name);
 261            return NULL;
 262        }
 263        break;
 264    }
 265
 266    default:
 267        abort();
 268    }
 269
 270    tail->next = g_malloc0(size);
 271    return tail->next;
 272}
 273
 274
 275static void
 276opts_check_list(Visitor *v, Error **errp)
 277{
 278    /*
 279     * Unvisited list elements will be reported later when checking
 280     * whether unvisited struct members remain.
 281     */
 282}
 283
 284
 285static void
 286opts_end_list(Visitor *v, void **obj)
 287{
 288    OptsVisitor *ov = to_ov(v);
 289
 290    assert(ov->list_mode == LM_IN_PROGRESS ||
 291           ov->list_mode == LM_SIGNED_INTERVAL ||
 292           ov->list_mode == LM_UNSIGNED_INTERVAL);
 293    ov->repeated_opts = NULL;
 294    ov->list_mode = LM_NONE;
 295}
 296
 297
 298static const QemuOpt *
 299lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp)
 300{
 301    if (ov->list_mode == LM_NONE) {
 302        GQueue *list;
 303
 304        /* the last occurrence of any QemuOpt takes effect when queried by name
 305         */
 306        list = lookup_distinct(ov, name, errp);
 307        return list ? g_queue_peek_tail(list) : NULL;
 308    }
 309    assert(ov->list_mode == LM_IN_PROGRESS);
 310    return g_queue_peek_head(ov->repeated_opts);
 311}
 312
 313
 314static void
 315processed(OptsVisitor *ov, const char *name)
 316{
 317    if (ov->list_mode == LM_NONE) {
 318        g_hash_table_remove(ov->unprocessed_opts, name);
 319        return;
 320    }
 321    assert(ov->list_mode == LM_IN_PROGRESS);
 322    /* do nothing */
 323}
 324
 325
 326static void
 327opts_type_str(Visitor *v, const char *name, char **obj, Error **errp)
 328{
 329    OptsVisitor *ov = to_ov(v);
 330    const QemuOpt *opt;
 331
 332    opt = lookup_scalar(ov, name, errp);
 333    if (!opt) {
 334        *obj = NULL;
 335        return;
 336    }
 337    *obj = g_strdup(opt->str ? opt->str : "");
 338    /* Note that we consume a string even if this is called as part of
 339     * an enum visit that later fails because the string is not a
 340     * valid enum value; this is harmless because tracking what gets
 341     * consumed only matters to visit_end_struct() as the final error
 342     * check if there were no other failures during the visit.  */
 343    processed(ov, name);
 344}
 345
 346
 347/* mimics qemu-option.c::parse_option_bool() */
 348static void
 349opts_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)
 350{
 351    OptsVisitor *ov = to_ov(v);
 352    const QemuOpt *opt;
 353
 354    opt = lookup_scalar(ov, name, errp);
 355    if (!opt) {
 356        return;
 357    }
 358
 359    if (opt->str) {
 360        if (strcmp(opt->str, "on") == 0 ||
 361            strcmp(opt->str, "yes") == 0 ||
 362            strcmp(opt->str, "y") == 0) {
 363            *obj = true;
 364        } else if (strcmp(opt->str, "off") == 0 ||
 365            strcmp(opt->str, "no") == 0 ||
 366            strcmp(opt->str, "n") == 0) {
 367            *obj = false;
 368        } else {
 369            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
 370                       "on|yes|y|off|no|n");
 371            return;
 372        }
 373    } else {
 374        *obj = true;
 375    }
 376
 377    processed(ov, name);
 378}
 379
 380
 381static void
 382opts_type_int64(Visitor *v, const char *name, int64_t *obj, Error **errp)
 383{
 384    OptsVisitor *ov = to_ov(v);
 385    const QemuOpt *opt;
 386    const char *str;
 387    long long val;
 388    char *endptr;
 389
 390    if (ov->list_mode == LM_SIGNED_INTERVAL) {
 391        *obj = ov->range_next.s;
 392        return;
 393    }
 394
 395    opt = lookup_scalar(ov, name, errp);
 396    if (!opt) {
 397        return;
 398    }
 399    str = opt->str ? opt->str : "";
 400
 401    /* we've gotten past lookup_scalar() */
 402    assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS);
 403
 404    errno = 0;
 405    val = strtoll(str, &endptr, 0);
 406    if (errno == 0 && endptr > str && INT64_MIN <= val && val <= INT64_MAX) {
 407        if (*endptr == '\0') {
 408            *obj = val;
 409            processed(ov, name);
 410            return;
 411        }
 412        if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
 413            long long val2;
 414
 415            str = endptr + 1;
 416            val2 = strtoll(str, &endptr, 0);
 417            if (errno == 0 && endptr > str && *endptr == '\0' &&
 418                INT64_MIN <= val2 && val2 <= INT64_MAX && val <= val2 &&
 419                (val > INT64_MAX - OPTS_VISITOR_RANGE_MAX ||
 420                 val2 < val + OPTS_VISITOR_RANGE_MAX)) {
 421                ov->range_next.s = val;
 422                ov->range_limit.s = val2;
 423                ov->list_mode = LM_SIGNED_INTERVAL;
 424
 425                /* as if entering on the top */
 426                *obj = ov->range_next.s;
 427                return;
 428            }
 429        }
 430    }
 431    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
 432               (ov->list_mode == LM_NONE) ? "an int64 value" :
 433                                            "an int64 value or range");
 434}
 435
 436
 437static void
 438opts_type_uint64(Visitor *v, const char *name, uint64_t *obj, Error **errp)
 439{
 440    OptsVisitor *ov = to_ov(v);
 441    const QemuOpt *opt;
 442    const char *str;
 443    unsigned long long val;
 444    char *endptr;
 445
 446    if (ov->list_mode == LM_UNSIGNED_INTERVAL) {
 447        *obj = ov->range_next.u;
 448        return;
 449    }
 450
 451    opt = lookup_scalar(ov, name, errp);
 452    if (!opt) {
 453        return;
 454    }
 455    str = opt->str;
 456
 457    /* we've gotten past lookup_scalar() */
 458    assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS);
 459
 460    if (parse_uint(str, &val, &endptr, 0) == 0 && val <= UINT64_MAX) {
 461        if (*endptr == '\0') {
 462            *obj = val;
 463            processed(ov, name);
 464            return;
 465        }
 466        if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
 467            unsigned long long val2;
 468
 469            str = endptr + 1;
 470            if (parse_uint_full(str, &val2, 0) == 0 &&
 471                val2 <= UINT64_MAX && val <= val2 &&
 472                val2 - val < OPTS_VISITOR_RANGE_MAX) {
 473                ov->range_next.u = val;
 474                ov->range_limit.u = val2;
 475                ov->list_mode = LM_UNSIGNED_INTERVAL;
 476
 477                /* as if entering on the top */
 478                *obj = ov->range_next.u;
 479                return;
 480            }
 481        }
 482    }
 483    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
 484               (ov->list_mode == LM_NONE) ? "a uint64 value" :
 485                                            "a uint64 value or range");
 486}
 487
 488
 489static void
 490opts_type_size(Visitor *v, const char *name, uint64_t *obj, Error **errp)
 491{
 492    OptsVisitor *ov = to_ov(v);
 493    const QemuOpt *opt;
 494    int err;
 495
 496    opt = lookup_scalar(ov, name, errp);
 497    if (!opt) {
 498        return;
 499    }
 500
 501    err = qemu_strtosz(opt->str ? opt->str : "", NULL, obj);
 502    if (err < 0) {
 503        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
 504                   "a size value");
 505        return;
 506    }
 507
 508    processed(ov, name);
 509}
 510
 511
 512static void
 513opts_optional(Visitor *v, const char *name, bool *present)
 514{
 515    OptsVisitor *ov = to_ov(v);
 516
 517    /* we only support a single mandatory scalar field in a list node */
 518    assert(ov->list_mode == LM_NONE);
 519    *present = (lookup_distinct(ov, name, NULL) != NULL);
 520}
 521
 522
 523static void
 524opts_free(Visitor *v)
 525{
 526    OptsVisitor *ov = to_ov(v);
 527
 528    if (ov->unprocessed_opts != NULL) {
 529        g_hash_table_destroy(ov->unprocessed_opts);
 530    }
 531    g_free(ov->fake_id_opt);
 532    g_free(ov);
 533}
 534
 535
 536Visitor *
 537opts_visitor_new(const QemuOpts *opts)
 538{
 539    OptsVisitor *ov;
 540
 541    assert(opts);
 542    ov = g_malloc0(sizeof *ov);
 543
 544    ov->visitor.type = VISITOR_INPUT;
 545
 546    ov->visitor.start_struct = &opts_start_struct;
 547    ov->visitor.check_struct = &opts_check_struct;
 548    ov->visitor.end_struct   = &opts_end_struct;
 549
 550    ov->visitor.start_list = &opts_start_list;
 551    ov->visitor.next_list  = &opts_next_list;
 552    ov->visitor.check_list = &opts_check_list;
 553    ov->visitor.end_list   = &opts_end_list;
 554
 555    ov->visitor.type_int64  = &opts_type_int64;
 556    ov->visitor.type_uint64 = &opts_type_uint64;
 557    ov->visitor.type_size   = &opts_type_size;
 558    ov->visitor.type_bool   = &opts_type_bool;
 559    ov->visitor.type_str    = &opts_type_str;
 560
 561    /* type_number() is not filled in, but this is not the first visitor to
 562     * skip some mandatory methods... */
 563
 564    ov->visitor.optional = &opts_optional;
 565    ov->visitor.free = opts_free;
 566
 567    ov->opts_root = opts;
 568
 569    return &ov->visitor;
 570}
 571