qemu/qapi/opts-visitor.c
<<
>>
Prefs
   1/*
   2 * Options Visitor
   3 *
   4 * Copyright Red Hat, Inc. 2012
   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 "opts-visitor.h"
  14#include "qemu-queue.h"
  15#include "qemu-option-internal.h"
  16#include "qapi-visit-impl.h"
  17
  18
  19struct OptsVisitor
  20{
  21    Visitor visitor;
  22
  23    /* Ownership remains with opts_visitor_new()'s caller. */
  24    const QemuOpts *opts_root;
  25
  26    unsigned depth;
  27
  28    /* Non-null iff depth is positive. Each key is a QemuOpt name. Each value
  29     * is a non-empty GQueue, enumerating all QemuOpt occurrences with that
  30     * name. */
  31    GHashTable *unprocessed_opts;
  32
  33    /* The list currently being traversed with opts_start_list() /
  34     * opts_next_list(). The list must have a struct element type in the
  35     * schema, with a single mandatory scalar member. */
  36    GQueue *repeated_opts;
  37    bool repeated_opts_first;
  38
  39    /* If "opts_root->id" is set, reinstantiate it as a fake QemuOpt for
  40     * uniformity. Only its "name" and "str" fields are set. "fake_id_opt" does
  41     * not survive or escape the OptsVisitor object.
  42     */
  43    QemuOpt *fake_id_opt;
  44};
  45
  46
  47static void
  48destroy_list(gpointer list)
  49{
  50  g_queue_free(list);
  51}
  52
  53
  54static void
  55opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt)
  56{
  57    GQueue *list;
  58
  59    list = g_hash_table_lookup(unprocessed_opts, opt->name);
  60    if (list == NULL) {
  61        list = g_queue_new();
  62
  63        /* GHashTable will never try to free the keys -- we supply NULL as
  64         * "key_destroy_func" in opts_start_struct(). Thus cast away key
  65         * const-ness in order to suppress gcc's warning.
  66         */
  67        g_hash_table_insert(unprocessed_opts, (gpointer)opt->name, list);
  68    }
  69
  70    /* Similarly, destroy_list() doesn't call g_queue_free_full(). */
  71    g_queue_push_tail(list, (gpointer)opt);
  72}
  73
  74
  75static void
  76opts_start_struct(Visitor *v, void **obj, const char *kind,
  77                  const char *name, size_t size, Error **errp)
  78{
  79    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
  80    const QemuOpt *opt;
  81
  82    *obj = g_malloc0(size > 0 ? size : 1);
  83    if (ov->depth++ > 0) {
  84        return;
  85    }
  86
  87    ov->unprocessed_opts = g_hash_table_new_full(&g_str_hash, &g_str_equal,
  88                                                 NULL, &destroy_list);
  89    QTAILQ_FOREACH(opt, &ov->opts_root->head, next) {
  90        /* ensured by qemu-option.c::opts_do_parse() */
  91        assert(strcmp(opt->name, "id") != 0);
  92
  93        opts_visitor_insert(ov->unprocessed_opts, opt);
  94    }
  95
  96    if (ov->opts_root->id != NULL) {
  97        ov->fake_id_opt = g_malloc0(sizeof *ov->fake_id_opt);
  98
  99        ov->fake_id_opt->name = "id";
 100        ov->fake_id_opt->str = ov->opts_root->id;
 101        opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt);
 102    }
 103}
 104
 105
 106static gboolean
 107ghr_true(gpointer ign_key, gpointer ign_value, gpointer ign_user_data)
 108{
 109    return TRUE;
 110}
 111
 112
 113static void
 114opts_end_struct(Visitor *v, Error **errp)
 115{
 116    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 117    GQueue *any;
 118
 119    if (--ov->depth > 0) {
 120        return;
 121    }
 122
 123    /* we should have processed all (distinct) QemuOpt instances */
 124    any = g_hash_table_find(ov->unprocessed_opts, &ghr_true, NULL);
 125    if (any) {
 126        const QemuOpt *first;
 127
 128        first = g_queue_peek_head(any);
 129        error_set(errp, QERR_INVALID_PARAMETER, first->name);
 130    }
 131    g_hash_table_destroy(ov->unprocessed_opts);
 132    ov->unprocessed_opts = NULL;
 133    g_free(ov->fake_id_opt);
 134    ov->fake_id_opt = NULL;
 135}
 136
 137
 138static GQueue *
 139lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
 140{
 141    GQueue *list;
 142
 143    list = g_hash_table_lookup(ov->unprocessed_opts, name);
 144    if (!list) {
 145        error_set(errp, QERR_MISSING_PARAMETER, name);
 146    }
 147    return list;
 148}
 149
 150
 151static void
 152opts_start_list(Visitor *v, const char *name, Error **errp)
 153{
 154    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 155
 156    /* we can't traverse a list in a list */
 157    assert(ov->repeated_opts == NULL);
 158    ov->repeated_opts = lookup_distinct(ov, name, errp);
 159    ov->repeated_opts_first = (ov->repeated_opts != NULL);
 160}
 161
 162
 163static GenericList *
 164opts_next_list(Visitor *v, GenericList **list, Error **errp)
 165{
 166    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 167    GenericList **link;
 168
 169    if (ov->repeated_opts_first) {
 170        ov->repeated_opts_first = false;
 171        link = list;
 172    } else {
 173        const QemuOpt *opt;
 174
 175        opt = g_queue_pop_head(ov->repeated_opts);
 176        if (g_queue_is_empty(ov->repeated_opts)) {
 177            g_hash_table_remove(ov->unprocessed_opts, opt->name);
 178            return NULL;
 179        }
 180        link = &(*list)->next;
 181    }
 182
 183    *link = g_malloc0(sizeof **link);
 184    return *link;
 185}
 186
 187
 188static void
 189opts_end_list(Visitor *v, Error **errp)
 190{
 191    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 192
 193    ov->repeated_opts = NULL;
 194}
 195
 196
 197static const QemuOpt *
 198lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp)
 199{
 200    if (ov->repeated_opts == NULL) {
 201        GQueue *list;
 202
 203        /* the last occurrence of any QemuOpt takes effect when queried by name
 204         */
 205        list = lookup_distinct(ov, name, errp);
 206        return list ? g_queue_peek_tail(list) : NULL;
 207    }
 208    return g_queue_peek_head(ov->repeated_opts);
 209}
 210
 211
 212static void
 213processed(OptsVisitor *ov, const char *name)
 214{
 215    if (ov->repeated_opts == NULL) {
 216        g_hash_table_remove(ov->unprocessed_opts, name);
 217    }
 218}
 219
 220
 221static void
 222opts_type_str(Visitor *v, char **obj, const char *name, Error **errp)
 223{
 224    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 225    const QemuOpt *opt;
 226
 227    opt = lookup_scalar(ov, name, errp);
 228    if (!opt) {
 229        return;
 230    }
 231    *obj = g_strdup(opt->str ? opt->str : "");
 232    processed(ov, name);
 233}
 234
 235
 236/* mimics qemu-option.c::parse_option_bool() */
 237static void
 238opts_type_bool(Visitor *v, bool *obj, const char *name, Error **errp)
 239{
 240    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 241    const QemuOpt *opt;
 242
 243    opt = lookup_scalar(ov, name, errp);
 244    if (!opt) {
 245        return;
 246    }
 247
 248    if (opt->str) {
 249        if (strcmp(opt->str, "on") == 0 ||
 250            strcmp(opt->str, "yes") == 0 ||
 251            strcmp(opt->str, "y") == 0) {
 252            *obj = true;
 253        } else if (strcmp(opt->str, "off") == 0 ||
 254            strcmp(opt->str, "no") == 0 ||
 255            strcmp(opt->str, "n") == 0) {
 256            *obj = false;
 257        } else {
 258            error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
 259                "on|yes|y|off|no|n");
 260            return;
 261        }
 262    } else {
 263        *obj = true;
 264    }
 265
 266    processed(ov, name);
 267}
 268
 269
 270static void
 271opts_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp)
 272{
 273    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 274    const QemuOpt *opt;
 275    const char *str;
 276    long long val;
 277    char *endptr;
 278
 279    opt = lookup_scalar(ov, name, errp);
 280    if (!opt) {
 281        return;
 282    }
 283    str = opt->str ? opt->str : "";
 284
 285    errno = 0;
 286    val = strtoll(str, &endptr, 0);
 287    if (*str != '\0' && *endptr == '\0' && errno == 0 && INT64_MIN <= val &&
 288        val <= INT64_MAX) {
 289        *obj = val;
 290        processed(ov, name);
 291        return;
 292    }
 293    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, "an int64 value");
 294}
 295
 296
 297static void
 298opts_type_uint64(Visitor *v, uint64_t *obj, const char *name, Error **errp)
 299{
 300    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 301    const QemuOpt *opt;
 302    const char *str;
 303
 304    opt = lookup_scalar(ov, name, errp);
 305    if (!opt) {
 306        return;
 307    }
 308
 309    str = opt->str;
 310    if (str != NULL) {
 311        while (isspace((unsigned char)*str)) {
 312            ++str;
 313        }
 314
 315        if (*str != '-' && *str != '\0') {
 316            unsigned long long val;
 317            char *endptr;
 318
 319            /* non-empty, non-negative subject sequence */
 320            errno = 0;
 321            val = strtoull(str, &endptr, 0);
 322            if (*endptr == '\0' && errno == 0 && val <= UINT64_MAX) {
 323                *obj = val;
 324                processed(ov, name);
 325                return;
 326            }
 327        }
 328    }
 329    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
 330              "an uint64 value");
 331}
 332
 333
 334static void
 335opts_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp)
 336{
 337    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 338    const QemuOpt *opt;
 339    int64_t val;
 340    char *endptr;
 341
 342    opt = lookup_scalar(ov, name, errp);
 343    if (!opt) {
 344        return;
 345    }
 346
 347    val = strtosz_suffix(opt->str ? opt->str : "", &endptr,
 348                         STRTOSZ_DEFSUFFIX_B);
 349    if (val != -1 && *endptr == '\0') {
 350        *obj = val;
 351        processed(ov, name);
 352        return;
 353    }
 354    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
 355              "a size value representible as a non-negative int64");
 356}
 357
 358
 359static void
 360opts_start_optional(Visitor *v, bool *present, const char *name,
 361                       Error **errp)
 362{
 363    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
 364
 365    /* we only support a single mandatory scalar field in a list node */
 366    assert(ov->repeated_opts == NULL);
 367    *present = (lookup_distinct(ov, name, NULL) != NULL);
 368}
 369
 370
 371OptsVisitor *
 372opts_visitor_new(const QemuOpts *opts)
 373{
 374    OptsVisitor *ov;
 375
 376    ov = g_malloc0(sizeof *ov);
 377
 378    ov->visitor.start_struct = &opts_start_struct;
 379    ov->visitor.end_struct   = &opts_end_struct;
 380
 381    ov->visitor.start_list = &opts_start_list;
 382    ov->visitor.next_list  = &opts_next_list;
 383    ov->visitor.end_list   = &opts_end_list;
 384
 385    /* input_type_enum() covers both "normal" enums and union discriminators.
 386     * The union discriminator field is always generated as "type"; it should
 387     * match the "type" QemuOpt child of any QemuOpts.
 388     *
 389     * input_type_enum() will remove the looked-up key from the
 390     * "unprocessed_opts" hash even if the lookup fails, because the removal is
 391     * done earlier in opts_type_str(). This should be harmless.
 392     */
 393    ov->visitor.type_enum = &input_type_enum;
 394
 395    ov->visitor.type_int    = &opts_type_int;
 396    ov->visitor.type_uint64 = &opts_type_uint64;
 397    ov->visitor.type_size   = &opts_type_size;
 398    ov->visitor.type_bool   = &opts_type_bool;
 399    ov->visitor.type_str    = &opts_type_str;
 400
 401    /* type_number() is not filled in, but this is not the first visitor to
 402     * skip some mandatory methods... */
 403
 404    ov->visitor.start_optional = &opts_start_optional;
 405
 406    ov->opts_root = opts;
 407
 408    return ov;
 409}
 410
 411
 412void
 413opts_visitor_cleanup(OptsVisitor *ov)
 414{
 415    if (ov->unprocessed_opts != NULL) {
 416        g_hash_table_destroy(ov->unprocessed_opts);
 417    }
 418    g_free(ov->fake_id_opt);
 419    g_free(ov);
 420}
 421
 422
 423Visitor *
 424opts_get_visitor(OptsVisitor *ov)
 425{
 426    return &ov->visitor;
 427}
 428