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