opts-visitor.c 14.9 KB
Newer Older
L
Laszlo Ersek 已提交
1 2 3
/*
 * Options Visitor
 *
4
 * Copyright Red Hat, Inc. 2012-2016
L
Laszlo Ersek 已提交
5 6 7 8 9 10 11 12
 *
 * Author: Laszlo Ersek <lersek@redhat.com>
 *
 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
 * See the COPYING.LIB file in the top-level directory.
 *
 */

P
Peter Maydell 已提交
13
#include "qemu/osdep.h"
14
#include "qapi/error.h"
15
#include "qemu/cutils.h"
16 17
#include "qapi/qmp/qerror.h"
#include "qapi/opts-visitor.h"
18 19
#include "qemu/queue.h"
#include "qemu/option_int.h"
20
#include "qapi/visitor-impl.h"
L
Laszlo Ersek 已提交
21 22


23 24 25
enum ListMode
{
    LM_NONE,             /* not traversing a list of repeated options */
26

27
    LM_IN_PROGRESS,      /* opts_next_list() ready to be called.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
                          *
                          * Generating the next list link will consume the most
                          * recently parsed QemuOpt instance of the repeated
                          * option.
                          *
                          * Parsing a value into the list link will examine the
                          * next QemuOpt instance of the repeated option, and
                          * possibly enter LM_SIGNED_INTERVAL or
                          * LM_UNSIGNED_INTERVAL.
                          */

    LM_SIGNED_INTERVAL,  /* opts_next_list() has been called.
                          *
                          * Generating the next list link will consume the most
                          * recently stored element from the signed interval,
                          * parsed from the most recent QemuOpt instance of the
                          * repeated option. This may consume QemuOpt itself
                          * and return to LM_IN_PROGRESS.
                          *
                          * Parsing a value into the list link will store the
                          * next element of the signed interval.
                          */

    LM_UNSIGNED_INTERVAL /* Same as above, only for an unsigned interval. */
52 53 54 55
};

typedef enum ListMode ListMode;

L
Laszlo Ersek 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
struct OptsVisitor
{
    Visitor visitor;

    /* Ownership remains with opts_visitor_new()'s caller. */
    const QemuOpts *opts_root;

    unsigned depth;

    /* Non-null iff depth is positive. Each key is a QemuOpt name. Each value
     * is a non-empty GQueue, enumerating all QemuOpt occurrences with that
     * name. */
    GHashTable *unprocessed_opts;

    /* The list currently being traversed with opts_start_list() /
     * opts_next_list(). The list must have a struct element type in the
     * schema, with a single mandatory scalar member. */
73
    ListMode list_mode;
L
Laszlo Ersek 已提交
74 75
    GQueue *repeated_opts;

76 77 78 79 80 81 82 83 84
    /* When parsing a list of repeating options as integers, values of the form
     * "a-b", representing a closed interval, are allowed. Elements in the
     * range are generated individually.
     */
    union {
        int64_t s;
        uint64_t u;
    } range_next, range_limit;

L
Laszlo Ersek 已提交
85 86 87 88 89 90 91 92
    /* If "opts_root->id" is set, reinstantiate it as a fake QemuOpt for
     * uniformity. Only its "name" and "str" fields are set. "fake_id_opt" does
     * not survive or escape the OptsVisitor object.
     */
    QemuOpt *fake_id_opt;
};


93 94 95 96 97 98
static OptsVisitor *to_ov(Visitor *v)
{
    return container_of(v, OptsVisitor, visitor);
}


L
Laszlo Ersek 已提交
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
static void
destroy_list(gpointer list)
{
  g_queue_free(list);
}


static void
opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt)
{
    GQueue *list;

    list = g_hash_table_lookup(unprocessed_opts, opt->name);
    if (list == NULL) {
        list = g_queue_new();

        /* GHashTable will never try to free the keys -- we supply NULL as
         * "key_destroy_func" in opts_start_struct(). Thus cast away key
         * const-ness in order to suppress gcc's warning.
         */
        g_hash_table_insert(unprocessed_opts, (gpointer)opt->name, list);
    }

    /* Similarly, destroy_list() doesn't call g_queue_free_full(). */
    g_queue_push_tail(list, (gpointer)opt);
}


static void
128
opts_start_struct(Visitor *v, const char *name, void **obj,
129
                  size_t size, Error **errp)
L
Laszlo Ersek 已提交
130
{
131
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
132 133
    const QemuOpt *opt;

134
    if (obj) {
135
        *obj = g_malloc0(size);
136
    }
L
Laszlo Ersek 已提交
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
    if (ov->depth++ > 0) {
        return;
    }

    ov->unprocessed_opts = g_hash_table_new_full(&g_str_hash, &g_str_equal,
                                                 NULL, &destroy_list);
    QTAILQ_FOREACH(opt, &ov->opts_root->head, next) {
        /* ensured by qemu-option.c::opts_do_parse() */
        assert(strcmp(opt->name, "id") != 0);

        opts_visitor_insert(ov->unprocessed_opts, opt);
    }

    if (ov->opts_root->id != NULL) {
        ov->fake_id_opt = g_malloc0(sizeof *ov->fake_id_opt);

153 154
        ov->fake_id_opt->name = g_strdup("id");
        ov->fake_id_opt->str = g_strdup(ov->opts_root->id);
L
Laszlo Ersek 已提交
155 156 157 158 159 160
        opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt);
    }
}


static void
161
opts_check_struct(Visitor *v, Error **errp)
L
Laszlo Ersek 已提交
162
{
163
    OptsVisitor *ov = to_ov(v);
164
    GHashTableIter iter;
L
Laszlo Ersek 已提交
165 166
    GQueue *any;

167
    if (ov->depth > 0) {
L
Laszlo Ersek 已提交
168 169 170 171
        return;
    }

    /* we should have processed all (distinct) QemuOpt instances */
172 173
    g_hash_table_iter_init(&iter, ov->unprocessed_opts);
    if (g_hash_table_iter_next(&iter, NULL, (void **)&any)) {
L
Laszlo Ersek 已提交
174 175 176
        const QemuOpt *first;

        first = g_queue_peek_head(any);
177
        error_setg(errp, QERR_INVALID_PARAMETER, first->name);
L
Laszlo Ersek 已提交
178
    }
179 180 181 182
}


static void
E
Eric Blake 已提交
183
opts_end_struct(Visitor *v, void **obj)
184 185 186 187 188 189 190
{
    OptsVisitor *ov = to_ov(v);

    if (--ov->depth > 0) {
        return;
    }

L
Laszlo Ersek 已提交
191 192
    g_hash_table_destroy(ov->unprocessed_opts);
    ov->unprocessed_opts = NULL;
193 194 195 196 197
    if (ov->fake_id_opt) {
        g_free(ov->fake_id_opt->name);
        g_free(ov->fake_id_opt->str);
        g_free(ov->fake_id_opt);
    }
L
Laszlo Ersek 已提交
198 199 200 201 202 203 204 205 206 207 208
    ov->fake_id_opt = NULL;
}


static GQueue *
lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
{
    GQueue *list;

    list = g_hash_table_lookup(ov->unprocessed_opts, name);
    if (!list) {
209
        error_setg(errp, QERR_MISSING_PARAMETER, name);
L
Laszlo Ersek 已提交
210 211 212 213 214 215
    }
    return list;
}


static void
216 217
opts_start_list(Visitor *v, const char *name, GenericList **list, size_t size,
                Error **errp)
L
Laszlo Ersek 已提交
218
{
219
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
220 221

    /* we can't traverse a list in a list */
222
    assert(ov->list_mode == LM_NONE);
223 224
    /* we don't support visits without a list */
    assert(list);
L
Laszlo Ersek 已提交
225
    ov->repeated_opts = lookup_distinct(ov, name, errp);
226 227 228 229 230
    if (ov->repeated_opts) {
        ov->list_mode = LM_IN_PROGRESS;
        *list = g_malloc0(size);
    } else {
        *list = NULL;
231
    }
L
Laszlo Ersek 已提交
232 233 234 235
}


static GenericList *
236
opts_next_list(Visitor *v, GenericList *tail, size_t size)
L
Laszlo Ersek 已提交
237
{
238
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
239

240
    switch (ov->list_mode) {
241 242 243 244 245 246 247 248 249 250 251 252 253 254
    case LM_SIGNED_INTERVAL:
    case LM_UNSIGNED_INTERVAL:
        if (ov->list_mode == LM_SIGNED_INTERVAL) {
            if (ov->range_next.s < ov->range_limit.s) {
                ++ov->range_next.s;
                break;
            }
        } else if (ov->range_next.u < ov->range_limit.u) {
            ++ov->range_next.u;
            break;
        }
        ov->list_mode = LM_IN_PROGRESS;
        /* range has been completed, fall through in order to pop option */

255
    case LM_IN_PROGRESS: {
L
Laszlo Ersek 已提交
256 257 258 259 260 261 262
        const QemuOpt *opt;

        opt = g_queue_pop_head(ov->repeated_opts);
        if (g_queue_is_empty(ov->repeated_opts)) {
            g_hash_table_remove(ov->unprocessed_opts, opt->name);
            return NULL;
        }
263 264 265 266 267
        break;
    }

    default:
        abort();
L
Laszlo Ersek 已提交
268 269
    }

270 271
    tail->next = g_malloc0(size);
    return tail->next;
L
Laszlo Ersek 已提交
272 273 274 275
}


static void
E
Eric Blake 已提交
276
opts_end_list(Visitor *v, void **obj)
L
Laszlo Ersek 已提交
277
{
278
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
279

280
    assert(ov->list_mode == LM_IN_PROGRESS ||
281 282
           ov->list_mode == LM_SIGNED_INTERVAL ||
           ov->list_mode == LM_UNSIGNED_INTERVAL);
L
Laszlo Ersek 已提交
283
    ov->repeated_opts = NULL;
284
    ov->list_mode = LM_NONE;
L
Laszlo Ersek 已提交
285 286 287 288 289 290
}


static const QemuOpt *
lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp)
{
291
    if (ov->list_mode == LM_NONE) {
L
Laszlo Ersek 已提交
292 293 294 295 296 297 298
        GQueue *list;

        /* the last occurrence of any QemuOpt takes effect when queried by name
         */
        list = lookup_distinct(ov, name, errp);
        return list ? g_queue_peek_tail(list) : NULL;
    }
299
    assert(ov->list_mode == LM_IN_PROGRESS);
L
Laszlo Ersek 已提交
300 301 302 303 304 305 306
    return g_queue_peek_head(ov->repeated_opts);
}


static void
processed(OptsVisitor *ov, const char *name)
{
307
    if (ov->list_mode == LM_NONE) {
L
Laszlo Ersek 已提交
308
        g_hash_table_remove(ov->unprocessed_opts, name);
309
        return;
L
Laszlo Ersek 已提交
310
    }
311 312
    assert(ov->list_mode == LM_IN_PROGRESS);
    /* do nothing */
L
Laszlo Ersek 已提交
313 314 315 316
}


static void
317
opts_type_str(Visitor *v, const char *name, char **obj, Error **errp)
L
Laszlo Ersek 已提交
318
{
319
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
320 321 322 323
    const QemuOpt *opt;

    opt = lookup_scalar(ov, name, errp);
    if (!opt) {
324
        *obj = NULL;
L
Laszlo Ersek 已提交
325 326 327
        return;
    }
    *obj = g_strdup(opt->str ? opt->str : "");
328 329 330 331 332
    /* Note that we consume a string even if this is called as part of
     * an enum visit that later fails because the string is not a
     * valid enum value; this is harmless because tracking what gets
     * consumed only matters to visit_end_struct() as the final error
     * check if there were no other failures during the visit.  */
L
Laszlo Ersek 已提交
333 334 335 336 337 338
    processed(ov, name);
}


/* mimics qemu-option.c::parse_option_bool() */
static void
339
opts_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)
L
Laszlo Ersek 已提交
340
{
341
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
    const QemuOpt *opt;

    opt = lookup_scalar(ov, name, errp);
    if (!opt) {
        return;
    }

    if (opt->str) {
        if (strcmp(opt->str, "on") == 0 ||
            strcmp(opt->str, "yes") == 0 ||
            strcmp(opt->str, "y") == 0) {
            *obj = true;
        } else if (strcmp(opt->str, "off") == 0 ||
            strcmp(opt->str, "no") == 0 ||
            strcmp(opt->str, "n") == 0) {
            *obj = false;
        } else {
359 360
            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
                       "on|yes|y|off|no|n");
L
Laszlo Ersek 已提交
361 362 363 364 365 366 367 368 369 370 371
            return;
        }
    } else {
        *obj = true;
    }

    processed(ov, name);
}


static void
372
opts_type_int64(Visitor *v, const char *name, int64_t *obj, Error **errp)
L
Laszlo Ersek 已提交
373
{
374
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
375 376 377 378 379
    const QemuOpt *opt;
    const char *str;
    long long val;
    char *endptr;

380 381 382 383 384
    if (ov->list_mode == LM_SIGNED_INTERVAL) {
        *obj = ov->range_next.s;
        return;
    }

L
Laszlo Ersek 已提交
385 386 387 388 389 390
    opt = lookup_scalar(ov, name, errp);
    if (!opt) {
        return;
    }
    str = opt->str ? opt->str : "";

391 392 393
    /* we've gotten past lookup_scalar() */
    assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS);

L
Laszlo Ersek 已提交
394 395
    errno = 0;
    val = strtoll(str, &endptr, 0);
396 397 398 399 400 401 402 403 404 405 406 407
    if (errno == 0 && endptr > str && INT64_MIN <= val && val <= INT64_MAX) {
        if (*endptr == '\0') {
            *obj = val;
            processed(ov, name);
            return;
        }
        if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
            long long val2;

            str = endptr + 1;
            val2 = strtoll(str, &endptr, 0);
            if (errno == 0 && endptr > str && *endptr == '\0' &&
408 409 410
                INT64_MIN <= val2 && val2 <= INT64_MAX && val <= val2 &&
                (val > INT64_MAX - OPTS_VISITOR_RANGE_MAX ||
                 val2 < val + OPTS_VISITOR_RANGE_MAX)) {
411 412 413 414 415 416 417 418 419
                ov->range_next.s = val;
                ov->range_limit.s = val2;
                ov->list_mode = LM_SIGNED_INTERVAL;

                /* as if entering on the top */
                *obj = ov->range_next.s;
                return;
            }
        }
L
Laszlo Ersek 已提交
420
    }
421 422 423
    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
               (ov->list_mode == LM_NONE) ? "an int64 value" :
                                            "an int64 value or range");
L
Laszlo Ersek 已提交
424 425 426 427
}


static void
428
opts_type_uint64(Visitor *v, const char *name, uint64_t *obj, Error **errp)
L
Laszlo Ersek 已提交
429
{
430
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
431 432
    const QemuOpt *opt;
    const char *str;
433
    unsigned long long val;
434
    char *endptr;
L
Laszlo Ersek 已提交
435

436 437 438 439 440
    if (ov->list_mode == LM_UNSIGNED_INTERVAL) {
        *obj = ov->range_next.u;
        return;
    }

L
Laszlo Ersek 已提交
441 442 443 444 445 446
    opt = lookup_scalar(ov, name, errp);
    if (!opt) {
        return;
    }
    str = opt->str;

447 448 449 450 451 452 453 454 455 456 457 458 459 460
    /* we've gotten past lookup_scalar() */
    assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS);

    if (parse_uint(str, &val, &endptr, 0) == 0 && val <= UINT64_MAX) {
        if (*endptr == '\0') {
            *obj = val;
            processed(ov, name);
            return;
        }
        if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
            unsigned long long val2;

            str = endptr + 1;
            if (parse_uint_full(str, &val2, 0) == 0 &&
461 462
                val2 <= UINT64_MAX && val <= val2 &&
                val2 - val < OPTS_VISITOR_RANGE_MAX) {
463 464 465 466 467 468 469 470 471
                ov->range_next.u = val;
                ov->range_limit.u = val2;
                ov->list_mode = LM_UNSIGNED_INTERVAL;

                /* as if entering on the top */
                *obj = ov->range_next.u;
                return;
            }
        }
L
Laszlo Ersek 已提交
472
    }
473 474 475
    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
               (ov->list_mode == LM_NONE) ? "a uint64 value" :
                                            "a uint64 value or range");
L
Laszlo Ersek 已提交
476 477 478 479
}


static void
480
opts_type_size(Visitor *v, const char *name, uint64_t *obj, Error **errp)
L
Laszlo Ersek 已提交
481
{
482
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
483 484 485 486 487 488 489 490 491
    const QemuOpt *opt;
    int64_t val;
    char *endptr;

    opt = lookup_scalar(ov, name, errp);
    if (!opt) {
        return;
    }

492 493
    val = qemu_strtosz_suffix(opt->str ? opt->str : "", &endptr,
                         QEMU_STRTOSZ_DEFSUFFIX_B);
494
    if (val < 0 || *endptr) {
495 496
        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
                   "a size value representible as a non-negative int64");
L
Laszlo Ersek 已提交
497 498
        return;
    }
499 500 501

    *obj = val;
    processed(ov, name);
L
Laszlo Ersek 已提交
502 503 504 505
}


static void
506
opts_optional(Visitor *v, const char *name, bool *present)
L
Laszlo Ersek 已提交
507
{
508
    OptsVisitor *ov = to_ov(v);
L
Laszlo Ersek 已提交
509 510

    /* we only support a single mandatory scalar field in a list node */
511
    assert(ov->list_mode == LM_NONE);
L
Laszlo Ersek 已提交
512 513 514 515
    *present = (lookup_distinct(ov, name, NULL) != NULL);
}


E
Eric Blake 已提交
516 517 518 519 520
static void
opts_free(Visitor *v)
{
    OptsVisitor *ov = to_ov(v);

521 522 523 524 525
    if (ov->unprocessed_opts != NULL) {
        g_hash_table_destroy(ov->unprocessed_opts);
    }
    g_free(ov->fake_id_opt);
    g_free(ov);
E
Eric Blake 已提交
526 527 528
}


529
Visitor *
L
Laszlo Ersek 已提交
530 531 532 533 534 535
opts_visitor_new(const QemuOpts *opts)
{
    OptsVisitor *ov;

    ov = g_malloc0(sizeof *ov);

536 537
    ov->visitor.type = VISITOR_INPUT;

L
Laszlo Ersek 已提交
538
    ov->visitor.start_struct = &opts_start_struct;
539
    ov->visitor.check_struct = &opts_check_struct;
L
Laszlo Ersek 已提交
540 541 542 543 544 545
    ov->visitor.end_struct   = &opts_end_struct;

    ov->visitor.start_list = &opts_start_list;
    ov->visitor.next_list  = &opts_next_list;
    ov->visitor.end_list   = &opts_end_list;

546
    ov->visitor.type_int64  = &opts_type_int64;
L
Laszlo Ersek 已提交
547 548 549 550 551 552 553 554
    ov->visitor.type_uint64 = &opts_type_uint64;
    ov->visitor.type_size   = &opts_type_size;
    ov->visitor.type_bool   = &opts_type_bool;
    ov->visitor.type_str    = &opts_type_str;

    /* type_number() is not filled in, but this is not the first visitor to
     * skip some mandatory methods... */

555
    ov->visitor.optional = &opts_optional;
E
Eric Blake 已提交
556
    ov->visitor.free = opts_free;
L
Laszlo Ersek 已提交
557 558 559 560 561

    ov->opts_root = opts;

    return &ov->visitor;
}