Statistics
| Branch: | Revision:

root / qapi / opts-visitor.c @ 79ee7df8

History | View | Annotate | Download (10.6 kB)

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 "qemu-common.h"
14
#include "opts-visitor.h"
15
#include "qemu-queue.h"
16
#include "qemu-option-internal.h"
17
#include "qapi-visit-impl.h"
18

    
19

    
20
struct OptsVisitor
21
{
22
    Visitor visitor;
23

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

    
27
    unsigned depth;
28

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

    
34
    /* The list currently being traversed with opts_start_list() /
35
     * opts_next_list(). The list must have a struct element type in the
36
     * schema, with a single mandatory scalar member. */
37
    GQueue *repeated_opts;
38
    bool repeated_opts_first;
39

    
40
    /* If "opts_root->id" is set, reinstantiate it as a fake QemuOpt for
41
     * uniformity. Only its "name" and "str" fields are set. "fake_id_opt" does
42
     * not survive or escape the OptsVisitor object.
43
     */
44
    QemuOpt *fake_id_opt;
45
};
46

    
47

    
48
static void
49
destroy_list(gpointer list)
50
{
51
  g_queue_free(list);
52
}
53

    
54

    
55
static void
56
opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt)
57
{
58
    GQueue *list;
59

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

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

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

    
75

    
76
static void
77
opts_start_struct(Visitor *v, void **obj, const char *kind,
78
                  const char *name, size_t size, Error **errp)
79
{
80
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
81
    const QemuOpt *opt;
82

    
83
    *obj = g_malloc0(size > 0 ? size : 1);
84
    if (ov->depth++ > 0) {
85
        return;
86
    }
87

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

    
94
        opts_visitor_insert(ov->unprocessed_opts, opt);
95
    }
96

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

    
100
        ov->fake_id_opt->name = "id";
101
        ov->fake_id_opt->str = ov->opts_root->id;
102
        opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt);
103
    }
104
}
105

    
106

    
107
static gboolean
108
ghr_true(gpointer ign_key, gpointer ign_value, gpointer ign_user_data)
109
{
110
    return TRUE;
111
}
112

    
113

    
114
static void
115
opts_end_struct(Visitor *v, Error **errp)
116
{
117
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
118
    GQueue *any;
119

    
120
    if (--ov->depth > 0) {
121
        return;
122
    }
123

    
124
    /* we should have processed all (distinct) QemuOpt instances */
125
    any = g_hash_table_find(ov->unprocessed_opts, &ghr_true, NULL);
126
    if (any) {
127
        const QemuOpt *first;
128

    
129
        first = g_queue_peek_head(any);
130
        error_set(errp, QERR_INVALID_PARAMETER, first->name);
131
    }
132
    g_hash_table_destroy(ov->unprocessed_opts);
133
    ov->unprocessed_opts = NULL;
134
    g_free(ov->fake_id_opt);
135
    ov->fake_id_opt = NULL;
136
}
137

    
138

    
139
static GQueue *
140
lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
141
{
142
    GQueue *list;
143

    
144
    list = g_hash_table_lookup(ov->unprocessed_opts, name);
145
    if (!list) {
146
        error_set(errp, QERR_MISSING_PARAMETER, name);
147
    }
148
    return list;
149
}
150

    
151

    
152
static void
153
opts_start_list(Visitor *v, const char *name, Error **errp)
154
{
155
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
156

    
157
    /* we can't traverse a list in a list */
158
    assert(ov->repeated_opts == NULL);
159
    ov->repeated_opts = lookup_distinct(ov, name, errp);
160
    ov->repeated_opts_first = (ov->repeated_opts != NULL);
161
}
162

    
163

    
164
static GenericList *
165
opts_next_list(Visitor *v, GenericList **list, Error **errp)
166
{
167
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
168
    GenericList **link;
169

    
170
    if (ov->repeated_opts_first) {
171
        ov->repeated_opts_first = false;
172
        link = list;
173
    } else {
174
        const QemuOpt *opt;
175

    
176
        opt = g_queue_pop_head(ov->repeated_opts);
177
        if (g_queue_is_empty(ov->repeated_opts)) {
178
            g_hash_table_remove(ov->unprocessed_opts, opt->name);
179
            return NULL;
180
        }
181
        link = &(*list)->next;
182
    }
183

    
184
    *link = g_malloc0(sizeof **link);
185
    return *link;
186
}
187

    
188

    
189
static void
190
opts_end_list(Visitor *v, Error **errp)
191
{
192
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
193

    
194
    ov->repeated_opts = NULL;
195
}
196

    
197

    
198
static const QemuOpt *
199
lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp)
200
{
201
    if (ov->repeated_opts == NULL) {
202
        GQueue *list;
203

    
204
        /* the last occurrence of any QemuOpt takes effect when queried by name
205
         */
206
        list = lookup_distinct(ov, name, errp);
207
        return list ? g_queue_peek_tail(list) : NULL;
208
    }
209
    return g_queue_peek_head(ov->repeated_opts);
210
}
211

    
212

    
213
static void
214
processed(OptsVisitor *ov, const char *name)
215
{
216
    if (ov->repeated_opts == NULL) {
217
        g_hash_table_remove(ov->unprocessed_opts, name);
218
    }
219
}
220

    
221

    
222
static void
223
opts_type_str(Visitor *v, char **obj, const char *name, Error **errp)
224
{
225
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
226
    const QemuOpt *opt;
227

    
228
    opt = lookup_scalar(ov, name, errp);
229
    if (!opt) {
230
        return;
231
    }
232
    *obj = g_strdup(opt->str ? opt->str : "");
233
    processed(ov, name);
234
}
235

    
236

    
237
/* mimics qemu-option.c::parse_option_bool() */
238
static void
239
opts_type_bool(Visitor *v, bool *obj, const char *name, Error **errp)
240
{
241
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
242
    const QemuOpt *opt;
243

    
244
    opt = lookup_scalar(ov, name, errp);
245
    if (!opt) {
246
        return;
247
    }
248

    
249
    if (opt->str) {
250
        if (strcmp(opt->str, "on") == 0 ||
251
            strcmp(opt->str, "yes") == 0 ||
252
            strcmp(opt->str, "y") == 0) {
253
            *obj = true;
254
        } else if (strcmp(opt->str, "off") == 0 ||
255
            strcmp(opt->str, "no") == 0 ||
256
            strcmp(opt->str, "n") == 0) {
257
            *obj = false;
258
        } else {
259
            error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
260
                "on|yes|y|off|no|n");
261
            return;
262
        }
263
    } else {
264
        *obj = true;
265
    }
266

    
267
    processed(ov, name);
268
}
269

    
270

    
271
static void
272
opts_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp)
273
{
274
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
275
    const QemuOpt *opt;
276
    const char *str;
277
    long long val;
278
    char *endptr;
279

    
280
    opt = lookup_scalar(ov, name, errp);
281
    if (!opt) {
282
        return;
283
    }
284
    str = opt->str ? opt->str : "";
285

    
286
    errno = 0;
287
    val = strtoll(str, &endptr, 0);
288
    if (*str != '\0' && *endptr == '\0' && errno == 0 && INT64_MIN <= val &&
289
        val <= INT64_MAX) {
290
        *obj = val;
291
        processed(ov, name);
292
        return;
293
    }
294
    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, "an int64 value");
295
}
296

    
297

    
298
static void
299
opts_type_uint64(Visitor *v, uint64_t *obj, const char *name, Error **errp)
300
{
301
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
302
    const QemuOpt *opt;
303
    const char *str;
304

    
305
    opt = lookup_scalar(ov, name, errp);
306
    if (!opt) {
307
        return;
308
    }
309

    
310
    str = opt->str;
311
    if (str != NULL) {
312
        while (isspace((unsigned char)*str)) {
313
            ++str;
314
        }
315

    
316
        if (*str != '-' && *str != '\0') {
317
            unsigned long long val;
318
            char *endptr;
319

    
320
            /* non-empty, non-negative subject sequence */
321
            errno = 0;
322
            val = strtoull(str, &endptr, 0);
323
            if (*endptr == '\0' && errno == 0 && val <= UINT64_MAX) {
324
                *obj = val;
325
                processed(ov, name);
326
                return;
327
            }
328
        }
329
    }
330
    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
331
              "an uint64 value");
332
}
333

    
334

    
335
static void
336
opts_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp)
337
{
338
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
339
    const QemuOpt *opt;
340
    int64_t val;
341
    char *endptr;
342

    
343
    opt = lookup_scalar(ov, name, errp);
344
    if (!opt) {
345
        return;
346
    }
347

    
348
    val = strtosz_suffix(opt->str ? opt->str : "", &endptr,
349
                         STRTOSZ_DEFSUFFIX_B);
350
    if (val != -1 && *endptr == '\0') {
351
        *obj = val;
352
        processed(ov, name);
353
        return;
354
    }
355
    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
356
              "a size value representible as a non-negative int64");
357
}
358

    
359

    
360
static void
361
opts_start_optional(Visitor *v, bool *present, const char *name,
362
                       Error **errp)
363
{
364
    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
365

    
366
    /* we only support a single mandatory scalar field in a list node */
367
    assert(ov->repeated_opts == NULL);
368
    *present = (lookup_distinct(ov, name, NULL) != NULL);
369
}
370

    
371

    
372
OptsVisitor *
373
opts_visitor_new(const QemuOpts *opts)
374
{
375
    OptsVisitor *ov;
376

    
377
    ov = g_malloc0(sizeof *ov);
378

    
379
    ov->visitor.start_struct = &opts_start_struct;
380
    ov->visitor.end_struct   = &opts_end_struct;
381

    
382
    ov->visitor.start_list = &opts_start_list;
383
    ov->visitor.next_list  = &opts_next_list;
384
    ov->visitor.end_list   = &opts_end_list;
385

    
386
    /* input_type_enum() covers both "normal" enums and union discriminators.
387
     * The union discriminator field is always generated as "type"; it should
388
     * match the "type" QemuOpt child of any QemuOpts.
389
     *
390
     * input_type_enum() will remove the looked-up key from the
391
     * "unprocessed_opts" hash even if the lookup fails, because the removal is
392
     * done earlier in opts_type_str(). This should be harmless.
393
     */
394
    ov->visitor.type_enum = &input_type_enum;
395

    
396
    ov->visitor.type_int    = &opts_type_int;
397
    ov->visitor.type_uint64 = &opts_type_uint64;
398
    ov->visitor.type_size   = &opts_type_size;
399
    ov->visitor.type_bool   = &opts_type_bool;
400
    ov->visitor.type_str    = &opts_type_str;
401

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

    
405
    ov->visitor.start_optional = &opts_start_optional;
406

    
407
    ov->opts_root = opts;
408

    
409
    return ov;
410
}
411

    
412

    
413
void
414
opts_visitor_cleanup(OptsVisitor *ov)
415
{
416
    if (ov->unprocessed_opts != NULL) {
417
        g_hash_table_destroy(ov->unprocessed_opts);
418
    }
419
    g_free(ov->fake_id_opt);
420
    g_free(ov);
421
}
422

    
423

    
424
Visitor *
425
opts_get_visitor(OptsVisitor *ov)
426
{
427
    return &ov->visitor;
428
}