[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH 08/25] qapi: support nested structs in OptsVisit
From: |
Marc-André Lureau |
Subject: |
Re: [Qemu-devel] [PATCH 08/25] qapi: support nested structs in OptsVisitor |
Date: |
Thu, 20 Aug 2015 17:55:38 +0200 |
Hi
On Thu, Aug 6, 2015 at 8:28 PM, Kővágó, Zoltán <address@hidden> wrote:
> The current OptsVisitor flattens the whole structure, if there are same
> named fields under different paths (like `in' and `out' in `Audiodev'),
> the current visitor can't cope with them (for example setting
> `frequency=44100' will set the in's frequency to 44100 and leave out's
> frequency unspecified).
>
> This patch fixes it, by always requiring a complete path in case of
> nested structs. Fields in the path are separated by dots, similar to C
> structs (without pointers), like `in.frequency' or`out.frequency'.
>
> You must provide a full path even in non-ambigous cases. The qapi
> flattening commits hopefully ensures that this change doesn't create
> backward compatibility problems.
>
> Signed-off-by: Kővágó, Zoltán <address@hidden>
> ---
> qapi/opts-visitor.c | 114
> ++++++++++++++++++++++++++------
> tests/qapi-schema/qapi-schema-test.json | 9 ++-
> tests/test-opts-visitor.c | 34 ++++++++++
> 3 files changed, 135 insertions(+), 22 deletions(-)
>
> diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
> index aa68814..ff61d42 100644
> --- a/qapi/opts-visitor.c
> +++ b/qapi/opts-visitor.c
> @@ -71,6 +71,7 @@ struct OptsVisitor
> * schema, with a single mandatory scalar member. */
> ListMode list_mode;
> GQueue *repeated_opts;
> + char *repeated_name;
>
> /* When parsing a list of repeating options as integers, values of the
> form
> * "a-b", representing a closed interval, are allowed. Elements in the
> @@ -86,6 +87,9 @@ struct OptsVisitor
> * not survive or escape the OptsVisitor object.
> */
> QemuOpt *fake_id_opt;
> +
> + /* List of field names leading to the current structure. */
> + GQueue *nested_names;
> };
>
>
> @@ -100,6 +104,7 @@ static void
> opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt)
> {
> GQueue *list;
> + assert(opt);
>
> list = g_hash_table_lookup(unprocessed_opts, opt->name);
> if (list == NULL) {
> @@ -127,6 +132,9 @@ opts_start_struct(Visitor *v, void **obj, const char
> *kind,
> if (obj) {
> *obj = g_malloc0(size > 0 ? size : 1);
> }
> +
> + g_queue_push_tail(ov->nested_names, (gpointer) name);
> +
> if (ov->depth++ > 0) {
> return;
> }
> @@ -169,6 +177,8 @@ opts_end_struct(Visitor *v, Error **errp)
> OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
> GQueue *any;
>
> + g_queue_pop_tail(ov->nested_names);
> +
> if (--ov->depth > 0) {
> return;
> }
> @@ -198,15 +208,54 @@ opts_end_implicit_struct(Visitor *v, Error **errp)
> }
>
>
> +static void
> +sum_strlen(gpointer data, gpointer user_data)
> +{
> + const char *str = data;
> + size_t *sum_len = user_data;
> +
> + if (str) { /* skip NULLs */
> + *sum_len += strlen(str) + 1;
> + }
> +}
> +
> +static void
> +append_str(gpointer data, gpointer user_data)
> +{
> + const char *str = data;
> + char *concat_str = user_data;
> +
> + if (str) {
> + strcat(concat_str, str);
> + strcat(concat_str, ".");
> + }
> +}
> +
> +/* lookup a name, using a fully qualified version */
> static GQueue *
> -lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
> +lookup_distinct(const OptsVisitor *ov, const char *name, char **out_key,
> + Error **errp)
> {
> - GQueue *list;
> + GQueue *list = NULL;
> + char *key;
> + size_t sum_len = strlen(name);
> +
> + g_queue_foreach(ov->nested_names, sum_strlen, &sum_len);
> + key = g_malloc(sum_len+1);
> + key[0] = 0;
> + g_queue_foreach(ov->nested_names, append_str, key);
> + strcat(key, name);
Instead of using a GQueue, I think you could use a GArray, and use
g_strjoin() here.
> +
> + list = g_hash_table_lookup(ov->unprocessed_opts, key);
> + if (list && out_key) {
> + *out_key = g_strdup(key);
or just *out_key = key; key = NULL; (g_free accepts NULL)
> + }
>
> - list = g_hash_table_lookup(ov->unprocessed_opts, name);
> if (!list) {
> error_setg(errp, QERR_MISSING_PARAMETER, name);
> }
> +
> + g_free(key);
> return list;
> }
>
> @@ -218,7 +267,7 @@ opts_start_list(Visitor *v, const char *name, Error
> **errp)
>
> /* we can't traverse a list in a list */
> assert(ov->list_mode == LM_NONE);
> - ov->repeated_opts = lookup_distinct(ov, name, errp);
It would make sense to add an assert(ov->repeated_name == NULL) imho,
this could catch potential leaks.
> + ov->repeated_opts = lookup_distinct(ov, name, &ov->repeated_name, errp);
> if (ov->repeated_opts != NULL) {
> ov->list_mode = LM_STARTED;
> }
> @@ -254,11 +303,9 @@ opts_next_list(Visitor *v, GenericList **list, Error
> **errp)
> /* range has been completed, fall through in order to pop option */
>
> case LM_IN_PROGRESS: {
> - const QemuOpt *opt;
> -
> - opt = g_queue_pop_head(ov->repeated_opts);
> + g_queue_pop_head(ov->repeated_opts);
> if (g_queue_is_empty(ov->repeated_opts)) {
> - g_hash_table_remove(ov->unprocessed_opts, opt->name);
> + g_hash_table_remove(ov->unprocessed_opts, ov->repeated_name);
> return NULL;
> }
> link = &(*list)->next;
> @@ -284,22 +331,28 @@ opts_end_list(Visitor *v, Error **errp)
> ov->list_mode == LM_SIGNED_INTERVAL ||
> ov->list_mode == LM_UNSIGNED_INTERVAL);
> ov->repeated_opts = NULL;
> +
> + g_free(ov->repeated_name);
> + ov->repeated_name = NULL;
> +
> ov->list_mode = LM_NONE;
> }
>
>
> static const QemuOpt *
> -lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp)
> +lookup_scalar(const OptsVisitor *ov, const char *name, char** out_key,
> + Error **errp)
> {
> if (ov->list_mode == LM_NONE) {
> GQueue *list;
>
> /* the last occurrence of any QemuOpt takes effect when queried by
> name
> */
> - list = lookup_distinct(ov, name, errp);
> + list = lookup_distinct(ov, name, out_key, errp);
> return list ? g_queue_peek_tail(list) : NULL;
> }
> assert(ov->list_mode == LM_IN_PROGRESS);
> + assert(out_key == NULL || *out_key == NULL);
> return g_queue_peek_head(ov->repeated_opts);
> }
>
> @@ -321,13 +374,15 @@ opts_type_str(Visitor *v, char **obj, const char *name,
> Error **errp)
> {
> OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
> const QemuOpt *opt;
> + char *key = NULL;
>
> - opt = lookup_scalar(ov, name, errp);
> + opt = lookup_scalar(ov, name, &key, errp);
> if (!opt) {
> return;
> }
> *obj = g_strdup(opt->str ? opt->str : "");
> - processed(ov, name);
> + processed(ov, key);
> + g_free(key);
> }
>
>
> @@ -337,8 +392,9 @@ opts_type_bool(Visitor *v, bool *obj, const char *name,
> Error **errp)
> {
> OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
> const QemuOpt *opt;
> + char *key = NULL;
>
> - opt = lookup_scalar(ov, name, errp);
> + opt = lookup_scalar(ov, name, &key, errp);
> if (!opt) {
> return;
> }
> @@ -355,13 +411,15 @@ opts_type_bool(Visitor *v, bool *obj, const char *name,
> Error **errp)
> } else {
> error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
> "on|yes|y|off|no|n");
> + g_free(key);
> return;
> }
> } else {
> *obj = true;
> }
>
> - processed(ov, name);
> + processed(ov, key);
> + g_free(key);
> }
>
>
> @@ -373,13 +431,14 @@ opts_type_int(Visitor *v, int64_t *obj, const char
> *name, Error **errp)
> const char *str;
> long long val;
> char *endptr;
> + char *key = NULL;
>
> if (ov->list_mode == LM_SIGNED_INTERVAL) {
> *obj = ov->range_next.s;
> return;
> }
>
> - opt = lookup_scalar(ov, name, errp);
> + opt = lookup_scalar(ov, name, &key, errp);
> if (!opt) {
> return;
> }
> @@ -393,11 +452,13 @@ opts_type_int(Visitor *v, int64_t *obj, const char
> *name, Error **errp)
> if (errno == 0 && endptr > str && INT64_MIN <= val && val <= INT64_MAX) {
> if (*endptr == '\0') {
> *obj = val;
> - processed(ov, name);
> + processed(ov, key);
> + g_free(key);
> return;
> }
> if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
> long long val2;
> + assert(key == NULL);
>
> str = endptr + 1;
> val2 = strtoll(str, &endptr, 0);
> @@ -418,6 +479,7 @@ opts_type_int(Visitor *v, int64_t *obj, const char *name,
> Error **errp)
> error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
> (ov->list_mode == LM_NONE) ? "an int64 value" :
> "an int64 value or range");
> + g_free(key);
> }
>
>
> @@ -429,13 +491,14 @@ opts_type_uint64(Visitor *v, uint64_t *obj, const char
> *name, Error **errp)
> const char *str;
> unsigned long long val;
> char *endptr;
> + char *key = NULL;
>
> if (ov->list_mode == LM_UNSIGNED_INTERVAL) {
> *obj = ov->range_next.u;
> return;
> }
>
> - opt = lookup_scalar(ov, name, errp);
> + opt = lookup_scalar(ov, name, &key, errp);
> if (!opt) {
> return;
> }
> @@ -447,11 +510,13 @@ opts_type_uint64(Visitor *v, uint64_t *obj, const char
> *name, Error **errp)
> if (parse_uint(str, &val, &endptr, 0) == 0 && val <= UINT64_MAX) {
> if (*endptr == '\0') {
> *obj = val;
> - processed(ov, name);
> + processed(ov, key);
> + g_free(key);
> return;
> }
> if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
> unsigned long long val2;
> + assert(key == NULL);
>
> str = endptr + 1;
> if (parse_uint_full(str, &val2, 0) == 0 &&
> @@ -470,6 +535,7 @@ opts_type_uint64(Visitor *v, uint64_t *obj, const char
> *name, Error **errp)
> error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
> (ov->list_mode == LM_NONE) ? "a uint64 value" :
> "a uint64 value or range");
> + g_free(key);
> }
>
>
> @@ -480,8 +546,9 @@ opts_type_size(Visitor *v, uint64_t *obj, const char
> *name, Error **errp)
> const QemuOpt *opt;
> int64_t val;
> char *endptr;
> + char *key = NULL;
>
> - opt = lookup_scalar(ov, name, errp);
> + opt = lookup_scalar(ov, name, &key, errp);
> if (!opt) {
> return;
> }
> @@ -491,11 +558,13 @@ opts_type_size(Visitor *v, uint64_t *obj, const char
> *name, Error **errp)
> if (val < 0 || *endptr) {
> error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
> "a size value representible as a non-negative int64");
> + g_free(key);
> return;
> }
>
> *obj = val;
> - processed(ov, name);
> + processed(ov, key);
> + g_free(key);
> }
>
>
> @@ -506,7 +575,7 @@ opts_optional(Visitor *v, bool *present, const char
> *name, Error **errp)
>
> /* we only support a single mandatory scalar field in a list node */
> assert(ov->list_mode == LM_NONE);
> - *present = (lookup_distinct(ov, name, NULL) != NULL);
> + *present = (lookup_distinct(ov, name, NULL, NULL) != NULL);
> }
>
>
> @@ -517,6 +586,8 @@ opts_visitor_new(const QemuOpts *opts)
>
> ov = g_malloc0(sizeof *ov);
>
> + ov->nested_names = g_queue_new();
> +
> ov->visitor.start_struct = &opts_start_struct;
> ov->visitor.end_struct = &opts_end_struct;
>
> @@ -560,6 +631,7 @@ opts_visitor_cleanup(OptsVisitor *ov)
> if (ov->unprocessed_opts != NULL) {
> g_hash_table_destroy(ov->unprocessed_opts);
> }
> + g_queue_free(ov->nested_names);
> g_free(ov->fake_id_opt);
> g_free(ov);
> }
> diff --git a/tests/qapi-schema/qapi-schema-test.json
> b/tests/qapi-schema/qapi-schema-test.json
> index c7eaa86..a818eff 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json
> @@ -81,6 +81,11 @@
> { 'command': 'user_def_cmd3', 'data': {'a': 'int', '*b': 'int' },
> 'returns': 'int' }
>
> +# For testing hierarchy support in opts-visitor
> +{ 'struct': 'UserDefOptionsSub',
> + 'data': {
> + '*nint': 'int' } }
> +
> # For testing integer range flattening in opts-visitor. The following schema
> # corresponds to the option format:
> #
> @@ -94,7 +99,9 @@
> '*u64' : [ 'uint64' ],
> '*u16' : [ 'uint16' ],
> '*i64x': 'int' ,
> - '*u64x': 'uint64' } }
> + '*u64x': 'uint64' ,
> + 'sub0': 'UserDefOptionsSub',
make check fails with this patch, you need to update
tests/qapi-schema/qapi-schema-test.out.
> + 'sub1': 'UserDefOptionsSub' } }
>
> # testing event
> { 'struct': 'EventStructOne',
> diff --git a/tests/test-opts-visitor.c b/tests/test-opts-visitor.c
> index 1c753d9..4393266 100644
> --- a/tests/test-opts-visitor.c
> +++ b/tests/test-opts-visitor.c
> @@ -178,6 +178,34 @@ expect_u64_max(OptsVisitorFixture *f, gconstpointer
> test_data)
> g_assert(f->userdef->u64->value == UINT64_MAX);
> }
>
> +static void
> +expect_both(OptsVisitorFixture *f, gconstpointer test_data)
> +{
> + expect_ok(f, test_data);
> + g_assert(f->userdef->sub0->has_nint);
> + g_assert(f->userdef->sub0->nint == 13);
> + g_assert(f->userdef->sub1->has_nint);
> + g_assert(f->userdef->sub1->nint == 17);
> +}
> +
> +static void
> +expect_sub0(OptsVisitorFixture *f, gconstpointer test_data)
> +{
> + expect_ok(f, test_data);
> + g_assert(f->userdef->sub0->has_nint);
> + g_assert(f->userdef->sub0->nint == 13);
> + g_assert(!f->userdef->sub1->has_nint);
> +}
> +
> +static void
> +expect_sub1(OptsVisitorFixture *f, gconstpointer test_data)
> +{
> + expect_ok(f, test_data);
> + g_assert(!f->userdef->sub0->has_nint);
> + g_assert(f->userdef->sub1->has_nint);
> + g_assert(f->userdef->sub1->nint == 13);
> +}
> +
> /* test cases */
>
> int
> @@ -271,6 +299,12 @@ main(int argc, char **argv)
> add_test("/visitor/opts/i64/range/2big/full", &expect_fail,
> "i64=-0x8000000000000000-0x7fffffffffffffff");
>
> + /* Test nested structs support */
> + add_test("/visitor/opts/nested/unqualified", &expect_fail, "nint=13");
> + add_test("/visitor/opts/nested/both", &expect_both,
> + "sub0.nint=13,sub1.nint=17");
> + add_test("/visitor/opts/nested/sub0", &expect_sub0,
> "sub0.nint=13");
> + add_test("/visitor/opts/nested/sub1", &expect_sub1,
> "sub1.nint=13");
> g_test_run();
> return 0;
> }
> --
> 2.4.5
>
>
--
Marc-André Lureau
[Qemu-devel] [PATCH 06/25] qapi: reorder NetdevBase and Netdev, Kővágó, Zoltán, 2015/08/06
[Qemu-devel] [PATCH 09/25] audio: use qapi AudioFormat instead of audfmt_e, Kővágó, Zoltán, 2015/08/06
[Qemu-devel] [PATCH 23/25] audio: make mixeng optional, Kővágó, Zoltán, 2015/08/06