xref: /qemu/qapi/opts-visitor.c (revision a942d8fa)
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,      /* 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 
54 typedef enum ListMode ListMode;
55 
56 struct 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 
93 static OptsVisitor *to_ov(Visitor *v)
94 {
95     return container_of(v, OptsVisitor, visitor);
96 }
97 
98 
99 static void
100 destroy_list(gpointer list)
101 {
102   g_queue_free(list);
103 }
104 
105 
106 static void
107 opts_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 
127 static void
128 opts_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 
160 static void
161 opts_check_struct(Visitor *v, Error **errp)
162 {
163     OptsVisitor *ov = to_ov(v);
164     GHashTableIter iter;
165     GQueue *any;
166 
167     if (ov->depth > 0) {
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 
182 static void
183 opts_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 
202 static GQueue *
203 lookup_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 
215 static void
216 opts_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 
235 static GenericList *
236 opts_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 
275 static void
276 opts_end_list(Visitor *v, void **obj)
277 {
278     OptsVisitor *ov = to_ov(v);
279 
280     assert(ov->list_mode == LM_IN_PROGRESS ||
281            ov->list_mode == LM_SIGNED_INTERVAL ||
282            ov->list_mode == LM_UNSIGNED_INTERVAL);
283     ov->repeated_opts = NULL;
284     ov->list_mode = LM_NONE;
285 }
286 
287 
288 static const QemuOpt *
289 lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp)
290 {
291     if (ov->list_mode == LM_NONE) {
292         GQueue *list;
293 
294         /* the last occurrence of any QemuOpt takes effect when queried by name
295          */
296         list = lookup_distinct(ov, name, errp);
297         return list ? g_queue_peek_tail(list) : NULL;
298     }
299     assert(ov->list_mode == LM_IN_PROGRESS);
300     return g_queue_peek_head(ov->repeated_opts);
301 }
302 
303 
304 static void
305 processed(OptsVisitor *ov, const char *name)
306 {
307     if (ov->list_mode == LM_NONE) {
308         g_hash_table_remove(ov->unprocessed_opts, name);
309         return;
310     }
311     assert(ov->list_mode == LM_IN_PROGRESS);
312     /* do nothing */
313 }
314 
315 
316 static void
317 opts_type_str(Visitor *v, const char *name, char **obj, Error **errp)
318 {
319     OptsVisitor *ov = to_ov(v);
320     const QemuOpt *opt;
321 
322     opt = lookup_scalar(ov, name, errp);
323     if (!opt) {
324         *obj = NULL;
325         return;
326     }
327     *obj = g_strdup(opt->str ? opt->str : "");
328     /* Note that we consume a string even if this is called as part of
329      * an enum visit that later fails because the string is not a
330      * valid enum value; this is harmless because tracking what gets
331      * consumed only matters to visit_end_struct() as the final error
332      * check if there were no other failures during the visit.  */
333     processed(ov, name);
334 }
335 
336 
337 /* mimics qemu-option.c::parse_option_bool() */
338 static void
339 opts_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)
340 {
341     OptsVisitor *ov = to_ov(v);
342     const QemuOpt *opt;
343 
344     opt = lookup_scalar(ov, name, errp);
345     if (!opt) {
346         return;
347     }
348 
349     if (opt->str) {
350         if (strcmp(opt->str, "on") == 0 ||
351             strcmp(opt->str, "yes") == 0 ||
352             strcmp(opt->str, "y") == 0) {
353             *obj = true;
354         } else if (strcmp(opt->str, "off") == 0 ||
355             strcmp(opt->str, "no") == 0 ||
356             strcmp(opt->str, "n") == 0) {
357             *obj = false;
358         } else {
359             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
360                        "on|yes|y|off|no|n");
361             return;
362         }
363     } else {
364         *obj = true;
365     }
366 
367     processed(ov, name);
368 }
369 
370 
371 static void
372 opts_type_int64(Visitor *v, const char *name, int64_t *obj, Error **errp)
373 {
374     OptsVisitor *ov = to_ov(v);
375     const QemuOpt *opt;
376     const char *str;
377     long long val;
378     char *endptr;
379 
380     if (ov->list_mode == LM_SIGNED_INTERVAL) {
381         *obj = ov->range_next.s;
382         return;
383     }
384 
385     opt = lookup_scalar(ov, name, errp);
386     if (!opt) {
387         return;
388     }
389     str = opt->str ? opt->str : "";
390 
391     /* we've gotten past lookup_scalar() */
392     assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS);
393 
394     errno = 0;
395     val = strtoll(str, &endptr, 0);
396     if (errno == 0 && endptr > str && INT64_MIN <= val && val <= INT64_MAX) {
397         if (*endptr == '\0') {
398             *obj = val;
399             processed(ov, name);
400             return;
401         }
402         if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
403             long long val2;
404 
405             str = endptr + 1;
406             val2 = strtoll(str, &endptr, 0);
407             if (errno == 0 && endptr > str && *endptr == '\0' &&
408                 INT64_MIN <= val2 && val2 <= INT64_MAX && val <= val2 &&
409                 (val > INT64_MAX - OPTS_VISITOR_RANGE_MAX ||
410                  val2 < val + OPTS_VISITOR_RANGE_MAX)) {
411                 ov->range_next.s = val;
412                 ov->range_limit.s = val2;
413                 ov->list_mode = LM_SIGNED_INTERVAL;
414 
415                 /* as if entering on the top */
416                 *obj = ov->range_next.s;
417                 return;
418             }
419         }
420     }
421     error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
422                (ov->list_mode == LM_NONE) ? "an int64 value" :
423                                             "an int64 value or range");
424 }
425 
426 
427 static void
428 opts_type_uint64(Visitor *v, const char *name, uint64_t *obj, Error **errp)
429 {
430     OptsVisitor *ov = to_ov(v);
431     const QemuOpt *opt;
432     const char *str;
433     unsigned long long val;
434     char *endptr;
435 
436     if (ov->list_mode == LM_UNSIGNED_INTERVAL) {
437         *obj = ov->range_next.u;
438         return;
439     }
440 
441     opt = lookup_scalar(ov, name, errp);
442     if (!opt) {
443         return;
444     }
445     str = opt->str;
446 
447     /* we've gotten past lookup_scalar() */
448     assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS);
449 
450     if (parse_uint(str, &val, &endptr, 0) == 0 && val <= UINT64_MAX) {
451         if (*endptr == '\0') {
452             *obj = val;
453             processed(ov, name);
454             return;
455         }
456         if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
457             unsigned long long val2;
458 
459             str = endptr + 1;
460             if (parse_uint_full(str, &val2, 0) == 0 &&
461                 val2 <= UINT64_MAX && val <= val2 &&
462                 val2 - val < OPTS_VISITOR_RANGE_MAX) {
463                 ov->range_next.u = val;
464                 ov->range_limit.u = val2;
465                 ov->list_mode = LM_UNSIGNED_INTERVAL;
466 
467                 /* as if entering on the top */
468                 *obj = ov->range_next.u;
469                 return;
470             }
471         }
472     }
473     error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
474                (ov->list_mode == LM_NONE) ? "a uint64 value" :
475                                             "a uint64 value or range");
476 }
477 
478 
479 static void
480 opts_type_size(Visitor *v, const char *name, uint64_t *obj, Error **errp)
481 {
482     OptsVisitor *ov = to_ov(v);
483     const QemuOpt *opt;
484     int64_t val;
485     char *endptr;
486 
487     opt = lookup_scalar(ov, name, errp);
488     if (!opt) {
489         return;
490     }
491 
492     val = qemu_strtosz_suffix(opt->str ? opt->str : "", &endptr,
493                          QEMU_STRTOSZ_DEFSUFFIX_B);
494     if (val < 0 || *endptr) {
495         error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
496                    "a size value representible as a non-negative int64");
497         return;
498     }
499 
500     *obj = val;
501     processed(ov, name);
502 }
503 
504 
505 static void
506 opts_optional(Visitor *v, const char *name, bool *present)
507 {
508     OptsVisitor *ov = to_ov(v);
509 
510     /* we only support a single mandatory scalar field in a list node */
511     assert(ov->list_mode == LM_NONE);
512     *present = (lookup_distinct(ov, name, NULL) != NULL);
513 }
514 
515 
516 static void
517 opts_free(Visitor *v)
518 {
519     OptsVisitor *ov = to_ov(v);
520 
521     if (ov->unprocessed_opts != NULL) {
522         g_hash_table_destroy(ov->unprocessed_opts);
523     }
524     g_free(ov->fake_id_opt);
525     g_free(ov);
526 }
527 
528 
529 Visitor *
530 opts_visitor_new(const QemuOpts *opts)
531 {
532     OptsVisitor *ov;
533 
534     ov = g_malloc0(sizeof *ov);
535 
536     ov->visitor.type = VISITOR_INPUT;
537 
538     ov->visitor.start_struct = &opts_start_struct;
539     ov->visitor.check_struct = &opts_check_struct;
540     ov->visitor.end_struct   = &opts_end_struct;
541 
542     ov->visitor.start_list = &opts_start_list;
543     ov->visitor.next_list  = &opts_next_list;
544     ov->visitor.end_list   = &opts_end_list;
545 
546     ov->visitor.type_int64  = &opts_type_int64;
547     ov->visitor.type_uint64 = &opts_type_uint64;
548     ov->visitor.type_size   = &opts_type_size;
549     ov->visitor.type_bool   = &opts_type_bool;
550     ov->visitor.type_str    = &opts_type_str;
551 
552     /* type_number() is not filled in, but this is not the first visitor to
553      * skip some mandatory methods... */
554 
555     ov->visitor.optional = &opts_optional;
556     ov->visitor.free = opts_free;
557 
558     ov->opts_root = opts;
559 
560     return &ov->visitor;
561 }
562