| /* | |
| * String printing Visitor | |
| * | |
| * Copyright Red Hat, Inc. 2012-2016 | |
| * | |
| * Author: Paolo Bonzini <pbonzini@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. | |
| * | |
| */ | |
| #include "qemu/osdep.h" | |
| #include "qemu/cutils.h" | |
| #include "qapi/string-output-visitor.h" | |
| #include "qapi/visitor-impl.h" | |
| #include <math.h> | |
| #include "qemu/range.h" | |
| enum ListMode { | |
| LM_NONE, /* not traversing a list of repeated options */ | |
| LM_STARTED, /* next_list() ready to be called */ | |
| LM_IN_PROGRESS, /* next_list() has been called. | |
| * | |
| * 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, /* 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. */ | |
| LM_END, /* next_list() called, about to see last element. */ | |
| }; | |
| typedef enum ListMode ListMode; | |
| struct StringOutputVisitor | |
| { | |
| Visitor visitor; | |
| bool human; | |
| GString *string; | |
| char **result; | |
| ListMode list_mode; | |
| union { | |
| int64_t s; | |
| uint64_t u; | |
| } range_start, range_end; | |
| GList *ranges; | |
| void *list; /* Only needed for sanity checking the caller */ | |
| unsigned int struct_nesting; | |
| }; | |
| static StringOutputVisitor *to_sov(Visitor *v) | |
| { | |
| return container_of(v, StringOutputVisitor, visitor); | |
| } | |
| static void string_output_set(StringOutputVisitor *sov, char *string) | |
| { | |
| switch (sov->list_mode) { | |
| case LM_STARTED: | |
| sov->list_mode = LM_IN_PROGRESS; | |
| /* fall through */ | |
| case LM_NONE: | |
| if (sov->string) { | |
| g_string_free(sov->string, true); | |
| } | |
| sov->string = g_string_new(string); | |
| g_free(string); | |
| break; | |
| case LM_IN_PROGRESS: | |
| case LM_END: | |
| g_string_append(sov->string, ", "); | |
| g_string_append(sov->string, string); | |
| break; | |
| default: | |
| abort(); | |
| } | |
| } | |
| static void string_output_append(StringOutputVisitor *sov, int64_t a) | |
| { | |
| Range *r = g_malloc0(sizeof(*r)); | |
| range_set_bounds(r, a, a); | |
| sov->ranges = range_list_insert(sov->ranges, r); | |
| } | |
| static void string_output_append_range(StringOutputVisitor *sov, | |
| int64_t s, int64_t e) | |
| { | |
| Range *r = g_malloc0(sizeof(*r)); | |
| range_set_bounds(r, s, e); | |
| sov->ranges = range_list_insert(sov->ranges, r); | |
| } | |
| static void format_string(StringOutputVisitor *sov, Range *r, bool next, | |
| bool human) | |
| { | |
| if (range_lob(r) != range_upb(r)) { | |
| if (human) { | |
| g_string_append_printf(sov->string, "0x%" PRIx64 "-0x%" PRIx64, | |
| range_lob(r), range_upb(r)); | |
| } else { | |
| g_string_append_printf(sov->string, "%" PRId64 "-%" PRId64, | |
| range_lob(r), range_upb(r)); | |
| } | |
| } else { | |
| if (human) { | |
| g_string_append_printf(sov->string, "0x%" PRIx64, range_lob(r)); | |
| } else { | |
| g_string_append_printf(sov->string, "%" PRId64, range_lob(r)); | |
| } | |
| } | |
| if (next) { | |
| g_string_append(sov->string, ","); | |
| } | |
| } | |
| static bool print_type_int64(Visitor *v, const char *name, int64_t *obj, | |
| Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| GList *l; | |
| if (sov->struct_nesting) { | |
| return true; | |
| } | |
| switch (sov->list_mode) { | |
| case LM_NONE: | |
| string_output_append(sov, *obj); | |
| break; | |
| case LM_STARTED: | |
| sov->range_start.s = *obj; | |
| sov->range_end.s = *obj; | |
| sov->list_mode = LM_IN_PROGRESS; | |
| return true; | |
| case LM_IN_PROGRESS: | |
| if (sov->range_end.s + 1 == *obj) { | |
| sov->range_end.s++; | |
| } else { | |
| if (sov->range_start.s == sov->range_end.s) { | |
| string_output_append(sov, sov->range_end.s); | |
| } else { | |
| assert(sov->range_start.s < sov->range_end.s); | |
| string_output_append_range(sov, sov->range_start.s, | |
| sov->range_end.s); | |
| } | |
| sov->range_start.s = *obj; | |
| sov->range_end.s = *obj; | |
| } | |
| return true; | |
| case LM_END: | |
| if (sov->range_end.s + 1 == *obj) { | |
| sov->range_end.s++; | |
| assert(sov->range_start.s < sov->range_end.s); | |
| string_output_append_range(sov, sov->range_start.s, | |
| sov->range_end.s); | |
| } else { | |
| if (sov->range_start.s == sov->range_end.s) { | |
| string_output_append(sov, sov->range_end.s); | |
| } else { | |
| assert(sov->range_start.s < sov->range_end.s); | |
| string_output_append_range(sov, sov->range_start.s, | |
| sov->range_end.s); | |
| } | |
| string_output_append(sov, *obj); | |
| } | |
| break; | |
| default: | |
| abort(); | |
| } | |
| l = sov->ranges; | |
| while (l) { | |
| Range *r = l->data; | |
| format_string(sov, r, l->next != NULL, false); | |
| l = l->next; | |
| } | |
| if (sov->human) { | |
| l = sov->ranges; | |
| g_string_append(sov->string, " ("); | |
| while (l) { | |
| Range *r = l->data; | |
| format_string(sov, r, l->next != NULL, true); | |
| l = l->next; | |
| } | |
| g_string_append(sov->string, ")"); | |
| } | |
| return true; | |
| } | |
| static bool print_type_uint64(Visitor *v, const char *name, uint64_t *obj, | |
| Error **errp) | |
| { | |
| /* FIXME: print_type_int64 mishandles values over INT64_MAX */ | |
| int64_t i = *obj; | |
| return print_type_int64(v, name, &i, errp); | |
| } | |
| static bool print_type_size(Visitor *v, const char *name, uint64_t *obj, | |
| Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| uint64_t val; | |
| char *out, *psize; | |
| if (sov->struct_nesting) { | |
| return true; | |
| } | |
| if (!sov->human) { | |
| out = g_strdup_printf("%"PRIu64, *obj); | |
| string_output_set(sov, out); | |
| return true; | |
| } | |
| val = *obj; | |
| psize = size_to_str(val); | |
| out = g_strdup_printf("%"PRIu64" (%s)", val, psize); | |
| string_output_set(sov, out); | |
| g_free(psize); | |
| return true; | |
| } | |
| static bool print_type_bool(Visitor *v, const char *name, bool *obj, | |
| Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| if (sov->struct_nesting) { | |
| return true; | |
| } | |
| string_output_set(sov, g_strdup(*obj ? "true" : "false")); | |
| return true; | |
| } | |
| static bool print_type_str(Visitor *v, const char *name, char **obj, | |
| Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| char *out; | |
| if (sov->struct_nesting) { | |
| return true; | |
| } | |
| if (sov->human) { | |
| out = *obj ? g_strdup_printf("\"%s\"", *obj) : g_strdup("<null>"); | |
| } else { | |
| out = g_strdup(*obj ? *obj : ""); | |
| } | |
| string_output_set(sov, out); | |
| return true; | |
| } | |
| static bool print_type_number(Visitor *v, const char *name, double *obj, | |
| Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| if (sov->struct_nesting) { | |
| return true; | |
| } | |
| string_output_set(sov, g_strdup_printf("%.17g", *obj)); | |
| return true; | |
| } | |
| static bool print_type_null(Visitor *v, const char *name, QNull **obj, | |
| Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| char *out; | |
| if (sov->struct_nesting) { | |
| return true; | |
| } | |
| if (sov->human) { | |
| out = g_strdup("<null>"); | |
| } else { | |
| out = g_strdup(""); | |
| } | |
| string_output_set(sov, out); | |
| return true; | |
| } | |
| static bool start_struct(Visitor *v, const char *name, void **obj, | |
| size_t size, Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| sov->struct_nesting++; | |
| return true; | |
| } | |
| static void end_struct(Visitor *v, void **obj) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| if (--sov->struct_nesting) { | |
| return; | |
| } | |
| /* TODO actually print struct fields */ | |
| string_output_set(sov, g_strdup("<omitted>")); | |
| } | |
| static bool | |
| start_list(Visitor *v, const char *name, GenericList **list, size_t size, | |
| Error **errp) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| if (sov->struct_nesting) { | |
| return true; | |
| } | |
| /* we can't traverse a list in a list */ | |
| assert(sov->list_mode == LM_NONE); | |
| /* We don't support visits without a list */ | |
| assert(list); | |
| sov->list = list; | |
| /* List handling is only needed if there are at least two elements */ | |
| if (*list && (*list)->next) { | |
| sov->list_mode = LM_STARTED; | |
| } | |
| return true; | |
| } | |
| static GenericList *next_list(Visitor *v, GenericList *tail, size_t size) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| GenericList *ret = tail->next; | |
| if (sov->struct_nesting) { | |
| return ret; | |
| } | |
| if (ret && !ret->next) { | |
| sov->list_mode = LM_END; | |
| } | |
| return ret; | |
| } | |
| static void end_list(Visitor *v, void **obj) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| if (sov->struct_nesting) { | |
| return; | |
| } | |
| assert(sov->list == obj); | |
| assert(sov->list_mode == LM_STARTED || | |
| sov->list_mode == LM_END || | |
| sov->list_mode == LM_NONE || | |
| sov->list_mode == LM_IN_PROGRESS); | |
| sov->list_mode = LM_NONE; | |
| } | |
| static void string_output_complete(Visitor *v, void *opaque) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| assert(opaque == sov->result); | |
| *sov->result = g_string_free(sov->string, false); | |
| sov->string = NULL; | |
| } | |
| static void free_range(void *range, void *dummy) | |
| { | |
| g_free(range); | |
| } | |
| static void string_output_free(Visitor *v) | |
| { | |
| StringOutputVisitor *sov = to_sov(v); | |
| if (sov->string) { | |
| g_string_free(sov->string, true); | |
| } | |
| g_list_foreach(sov->ranges, free_range, NULL); | |
| g_list_free(sov->ranges); | |
| g_free(sov); | |
| } | |
| Visitor *string_output_visitor_new(bool human, char **result) | |
| { | |
| StringOutputVisitor *v; | |
| v = g_malloc0(sizeof(*v)); | |
| v->string = g_string_new(NULL); | |
| v->human = human; | |
| v->result = result; | |
| *result = NULL; | |
| v->visitor.type = VISITOR_OUTPUT; | |
| v->visitor.type_int64 = print_type_int64; | |
| v->visitor.type_uint64 = print_type_uint64; | |
| v->visitor.type_size = print_type_size; | |
| v->visitor.type_bool = print_type_bool; | |
| v->visitor.type_str = print_type_str; | |
| v->visitor.type_number = print_type_number; | |
| v->visitor.type_null = print_type_null; | |
| v->visitor.start_struct = start_struct; | |
| v->visitor.end_struct = end_struct; | |
| v->visitor.start_list = start_list; | |
| v->visitor.next_list = next_list; | |
| v->visitor.end_list = end_list; | |
| v->visitor.complete = string_output_complete; | |
| v->visitor.free = string_output_free; | |
| return &v->visitor; | |
| } |