From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:38852) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z4Pw4-0000gu-GJ for qemu-devel@nongnu.org; Mon, 15 Jun 2015 04:40:09 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Z4Pw0-00058H-2r for qemu-devel@nongnu.org; Mon, 15 Jun 2015 04:40:04 -0400 Received: from mx1.redhat.com ([209.132.183.28]:53840) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z4Pvz-00058C-P8 for qemu-devel@nongnu.org; Mon, 15 Jun 2015 04:40:00 -0400 Message-ID: <1434357591.31654.47.camel@redhat.com> From: Gerd Hoffmann Date: Mon, 15 Jun 2015 10:39:51 +0200 In-Reply-To: References: Content-Type: text/plain; charset="UTF-8" Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: Re: [Qemu-devel] [PATCH 08/12] qapi: support nested structs in OptsVisitor List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: =?UTF-8?Q?K=C5=91v=C3=A1g=C3=B3=2C_Zolt=C3=A1n?= Cc: Michael Roth , qemu-devel@nongnu.org, Markus Armbruster Hi, Adding qapi maintainers to Cc, full for them quote below, please review. For your next patch submission: There is a "MAINTAINERS" file with the people listed. There is a scripts/get_maintainer.pl scripts which will do the lookup for you (accepts patch as input, prints maintainers). You can hook this into your .git/config this way ... [sendemail] to =3D qemu-devel@nongnu.org cccmd =3D scripts/get_maintainer.pl --nogit-fallback ... and have 'git send-email' Cc the relevant maintainers automatically. cheers, Gerd On Fr, 2015-06-12 at 14:33 +0200, K=C5=91v=C3=A1g=C3=B3, Zolt=C3=A1n wrot= e: > The current OptsVisitor flattens the whole structure, if there are same= named > fields under different paths (like `in' and `out' in `Audiodev'), the c= urrent > visitor can't cope with them (for example setting `frequency=3D44100' w= ill set the > in's frequency to 44100 and leave out's frequency unspecified). >=20 > This patch fixes it, by the following changes: > 1) Specifying just the field name will apply to all fields that has the > specified name (this means it would set both in's and out's frequenc= y to > 44100 in the above example). > 2) Optionally user can specify the path in the hierarchy. Names are sep= arated by > a dot (e.g. `in.frequency', `foo.bar.something', etc). The user need= not > specify the whole path, only the last few components (i.e. `bar.some= thing' is > equivalent to `foo.bar.something' if only `foo' has a `bar' field). = This way > 1) is just a special case of this when only the last component is sp= ecified. > 3) In case of an ambiguity (e.g `frequency=3D44100,in.frequency=3D8000'= ) the longest > matching (the most specific) path wins (so in this example, in's fre= quency > would become 8000, because `in.frequency' is more specific that `fre= quency', > and out's frequency would become 44100, because only `frequency' mat= ches it). >=20 > Signed-off-by: K=C5=91v=C3=A1g=C3=B3, Zolt=C3=A1n > --- > qapi/opts-visitor.c | 144 ++++++++++++++++++++++++= +------- > tests/qapi-schema/qapi-schema-test.json | 9 +- > tests/test-opts-visitor.c | 34 ++++++++ > 3 files changed, 157 insertions(+), 30 deletions(-) >=20 > diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c > index f2ad6d7..409d8b7 100644 > --- a/qapi/opts-visitor.c > +++ b/qapi/opts-visitor.c > @@ -64,13 +64,14 @@ struct OptsVisitor > /* Non-null iff depth is positive. Each key is a QemuOpt name. Eac= h value > * is a non-empty GQueue, enumerating all QemuOpt occurrences with= that > * name. */ > - GHashTable *unprocessed_opts; > + GHashTable *unprocessed_opts, *opts; > =20 > /* The list currently being traversed with opts_start_list() / > * opts_next_list(). The list must have a struct element type in t= he > * schema, with a single mandatory scalar member. */ > ListMode list_mode; > GQueue *repeated_opts; > + char *repeated_name; > =20 > /* 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; > }; > =20 >=20 > @@ -97,11 +101,12 @@ destroy_list(gpointer list) > =20 >=20 > static void > -opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt) > +opts_visitor_insert(OptsVisitor *ov, const QemuOpt *opt) > { > GQueue *list; > + assert(opt); > =20 > - list =3D g_hash_table_lookup(unprocessed_opts, opt->name); > + list =3D g_hash_table_lookup(ov->opts, opt->name); > if (list =3D=3D NULL) { > list =3D g_queue_new(); > =20 > @@ -109,7 +114,8 @@ opts_visitor_insert(GHashTable *unprocessed_opts, c= onst QemuOpt *opt) > * "key_destroy_func" in opts_start_struct(). Thus cast away k= ey > * const-ness in order to suppress gcc's warning. > */ > - g_hash_table_insert(unprocessed_opts, (gpointer)opt->name, lis= t); > + g_hash_table_insert(ov->opts, (gpointer)opt->name, list); > + g_hash_table_insert(ov->unprocessed_opts, (gpointer)opt->name,= list); > } > =20 > /* Similarly, destroy_list() doesn't call g_queue_free_full(). */ > @@ -127,17 +133,27 @@ opts_start_struct(Visitor *v, void **obj, const c= har *kind, > if (obj) { > *obj =3D g_malloc0(size > 0 ? size : 1); > } > + > + /* assuming name is a statically allocated string (or at least it'= s lifetime > + * is longer than the visitor's) */ > + if (!name) { > + name =3D ""; > + } > + g_queue_push_tail(ov->nested_names, (gpointer) name); > + > if (ov->depth++ > 0) { > return; > } > =20 > - ov->unprocessed_opts =3D g_hash_table_new_full(&g_str_hash, &g_str= _equal, > - NULL, &destroy_list); > + ov->opts =3D g_hash_table_new_full(&g_str_hash, &g_str_equal, > + NULL, &destroy_list); > + ov->unprocessed_opts =3D g_hash_table_new(&g_str_hash, &g_str_equa= l); > + > QTAILQ_FOREACH(opt, &ov->opts_root->head, next) { > /* ensured by qemu-option.c::opts_do_parse() */ > assert(strcmp(opt->name, "id") !=3D 0); > =20 > - opts_visitor_insert(ov->unprocessed_opts, opt); > + opts_visitor_insert(ov, opt); > } > =20 > if (ov->opts_root->id !=3D NULL) { > @@ -145,7 +161,7 @@ opts_start_struct(Visitor *v, void **obj, const cha= r *kind, > =20 > ov->fake_id_opt->name =3D g_strdup("id"); > ov->fake_id_opt->str =3D g_strdup(ov->opts_root->id); > - opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt); > + opts_visitor_insert(ov, ov->fake_id_opt); > } > } > =20 > @@ -163,6 +179,8 @@ opts_end_struct(Visitor *v, Error **errp) > OptsVisitor *ov =3D DO_UPCAST(OptsVisitor, visitor, v); > GQueue *any; > =20 > + g_queue_pop_tail(ov->nested_names); > + > if (--ov->depth > 0) { > return; > } > @@ -177,6 +195,8 @@ opts_end_struct(Visitor *v, Error **errp) > } > g_hash_table_destroy(ov->unprocessed_opts); > ov->unprocessed_opts =3D NULL; > + g_hash_table_destroy(ov->opts); > + ov->opts =3D NULL; > if (ov->fake_id_opt) { > g_free(ov->fake_id_opt->name); > g_free(ov->fake_id_opt->str); > @@ -185,16 +205,56 @@ opts_end_struct(Visitor *v, Error **errp) > ov->fake_id_opt =3D NULL; > } > =20 > +static void > +sum_strlen(gpointer data, gpointer user_data) > +{ > + const char *str =3D data; > + size_t *sum_len =3D user_data; > =20 > + *sum_len +=3D strlen(str) + 1; > +} > + > +static void > +append_str(gpointer data, gpointer user_data) > +{ > + strcat(user_data, data); > + strcat(user_data, "."); > +} > + > +/* lookup a name, trying from the most qualified version (e.g. foo.bar= .asd) to > + * least qualified ones (i.e. foo.bar.asd overrides bar.asd or asd) */ > static GQueue * > -lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp) > +lookup_distinct(const OptsVisitor *ov, const char *name, char **out_ke= y, > + Error **errp) > { > - GQueue *list; > + GQueue *list =3D NULL; > + char *key, *key2; > + size_t sum_len =3D strlen(name); > =20 > - list =3D g_hash_table_lookup(ov->unprocessed_opts, name); > + g_queue_foreach(ov->nested_names, sum_strlen, &sum_len); > + key =3D g_malloc(sum_len+1); > + key[0] =3D 0; > + g_queue_foreach(ov->nested_names, append_str, key); > + strcat(key, name); > + > + key2 =3D key; > + while (*key2) { > + list =3D g_hash_table_lookup(ov->opts, key2); > + if (list) { > + if (out_key) { > + *out_key =3D g_strdup(key2); > + } > + break; > + } > + > + while (*key2 && *key2++ !=3D '.') { > + } > + } > if (!list) { > error_set(errp, QERR_MISSING_PARAMETER, name); > } > + > + g_free(key); > return list; > } > =20 > @@ -206,7 +266,7 @@ opts_start_list(Visitor *v, const char *name, Error= **errp) > =20 > /* we can't traverse a list in a list */ > assert(ov->list_mode =3D=3D LM_NONE); > - ov->repeated_opts =3D lookup_distinct(ov, name, errp); > + ov->repeated_opts =3D lookup_distinct(ov, name, &ov->repeated_name= , errp); > if (ov->repeated_opts !=3D NULL) { > ov->list_mode =3D LM_STARTED; > } > @@ -242,11 +302,9 @@ opts_next_list(Visitor *v, GenericList **list, Err= or **errp) > /* range has been completed, fall through in order to pop opti= on */ > =20 > case LM_IN_PROGRESS: { > - const QemuOpt *opt; > - > - opt =3D 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_nam= e); > return NULL; > } > link =3D &(*list)->next; > @@ -272,22 +330,28 @@ opts_end_list(Visitor *v, Error **errp) > ov->list_mode =3D=3D LM_SIGNED_INTERVAL || > ov->list_mode =3D=3D LM_UNSIGNED_INTERVAL); > ov->repeated_opts =3D NULL; > + > + g_free(ov->repeated_name); > + ov->repeated_name =3D NULL; > + > ov->list_mode =3D LM_NONE; > } > =20 >=20 > 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 =3D=3D LM_NONE) { > GQueue *list; > =20 > /* the last occurrence of any QemuOpt takes effect when querie= d by name > */ > - list =3D lookup_distinct(ov, name, errp); > + list =3D lookup_distinct(ov, name, out_key, errp); > return list ? g_queue_peek_tail(list) : NULL; > } > assert(ov->list_mode =3D=3D LM_IN_PROGRESS); > + assert(out_key =3D=3D NULL || *out_key =3D=3D NULL); > return g_queue_peek_head(ov->repeated_opts); > } > =20 > @@ -309,13 +373,15 @@ opts_type_str(Visitor *v, char **obj, const char = *name, Error **errp) > { > OptsVisitor *ov =3D DO_UPCAST(OptsVisitor, visitor, v); > const QemuOpt *opt; > + char *key =3D NULL; > =20 > - opt =3D lookup_scalar(ov, name, errp); > + opt =3D lookup_scalar(ov, name, &key, errp); > if (!opt) { > return; > } > *obj =3D g_strdup(opt->str ? opt->str : ""); > - processed(ov, name); > + processed(ov, key); > + g_free(key); > } > =20 >=20 > @@ -325,8 +391,9 @@ opts_type_bool(Visitor *v, bool *obj, const char *n= ame, Error **errp) > { > OptsVisitor *ov =3D DO_UPCAST(OptsVisitor, visitor, v); > const QemuOpt *opt; > + char *key =3D NULL; > =20 > - opt =3D lookup_scalar(ov, name, errp); > + opt =3D lookup_scalar(ov, name, &key, errp); > if (!opt) { > return; > } > @@ -343,13 +410,15 @@ opts_type_bool(Visitor *v, bool *obj, const char = *name, Error **errp) > } else { > error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, > "on|yes|y|off|no|n"); > + g_free(key); > return; > } > } else { > *obj =3D true; > } > =20 > - processed(ov, name); > + processed(ov, key); > + g_free(key); > } > =20 >=20 > @@ -361,13 +430,14 @@ opts_type_int(Visitor *v, int64_t *obj, const cha= r *name, Error **errp) > const char *str; > long long val; > char *endptr; > + char *key =3D NULL; > =20 > if (ov->list_mode =3D=3D LM_SIGNED_INTERVAL) { > *obj =3D ov->range_next.s; > return; > } > =20 > - opt =3D lookup_scalar(ov, name, errp); > + opt =3D lookup_scalar(ov, name, &key, errp); > if (!opt) { > return; > } > @@ -381,11 +451,13 @@ opts_type_int(Visitor *v, int64_t *obj, const cha= r *name, Error **errp) > if (errno =3D=3D 0 && endptr > str && INT64_MIN <=3D val && val <=3D= INT64_MAX) { > if (*endptr =3D=3D '\0') { > *obj =3D val; > - processed(ov, name); > + processed(ov, key); > + g_free(key); > return; > } > if (*endptr =3D=3D '-' && ov->list_mode =3D=3D LM_IN_PROGRESS)= { > long long val2; > + assert(key =3D=3D NULL); > =20 > str =3D endptr + 1; > val2 =3D strtoll(str, &endptr, 0); > @@ -406,6 +478,7 @@ opts_type_int(Visitor *v, int64_t *obj, const char = *name, Error **errp) > error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, > (ov->list_mode =3D=3D LM_NONE) ? "an int64 value" : > "an int64 value or range"); > + g_free(key); > } > =20 >=20 > @@ -417,13 +490,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 =3D NULL; > =20 > if (ov->list_mode =3D=3D LM_UNSIGNED_INTERVAL) { > *obj =3D ov->range_next.u; > return; > } > =20 > - opt =3D lookup_scalar(ov, name, errp); > + opt =3D lookup_scalar(ov, name, &key, errp); > if (!opt) { > return; > } > @@ -435,11 +509,13 @@ opts_type_uint64(Visitor *v, uint64_t *obj, const= char *name, Error **errp) > if (parse_uint(str, &val, &endptr, 0) =3D=3D 0 && val <=3D UINT64_= MAX) { > if (*endptr =3D=3D '\0') { > *obj =3D val; > - processed(ov, name); > + processed(ov, key); > + g_free(key); > return; > } > if (*endptr =3D=3D '-' && ov->list_mode =3D=3D LM_IN_PROGRESS)= { > unsigned long long val2; > + assert(key =3D=3D NULL); > =20 > str =3D endptr + 1; > if (parse_uint_full(str, &val2, 0) =3D=3D 0 && > @@ -458,6 +534,7 @@ opts_type_uint64(Visitor *v, uint64_t *obj, const c= har *name, Error **errp) > error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, > (ov->list_mode =3D=3D LM_NONE) ? "a uint64 value" : > "a uint64 value or range"); > + g_free(key); > } > =20 >=20 > @@ -468,8 +545,9 @@ opts_type_size(Visitor *v, uint64_t *obj, const cha= r *name, Error **errp) > const QemuOpt *opt; > int64_t val; > char *endptr; > + char *key =3D NULL; > =20 > - opt =3D lookup_scalar(ov, name, errp); > + opt =3D lookup_scalar(ov, name, &key, errp); > if (!opt) { > return; > } > @@ -479,11 +557,13 @@ opts_type_size(Visitor *v, uint64_t *obj, const c= har *name, Error **errp) > if (val < 0 || *endptr) { > error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, > "a size value representible as a non-negative int64"= ); > + g_free(key); > return; > } > =20 > *obj =3D val; > - processed(ov, name); > + processed(ov, key); > + g_free(key); > } > =20 >=20 > @@ -494,7 +574,7 @@ opts_optional(Visitor *v, bool *present, const char= *name, Error **errp) > =20 > /* we only support a single mandatory scalar field in a list node = */ > assert(ov->list_mode =3D=3D LM_NONE); > - *present =3D (lookup_distinct(ov, name, NULL) !=3D NULL); > + *present =3D (lookup_distinct(ov, name, NULL, NULL) !=3D NULL); > } > =20 >=20 > @@ -505,6 +585,8 @@ opts_visitor_new(const QemuOpts *opts) > =20 > ov =3D g_malloc0(sizeof *ov); > =20 > + ov->nested_names =3D g_queue_new(); > + > ov->visitor.start_struct =3D &opts_start_struct; > ov->visitor.end_struct =3D &opts_end_struct; > =20 > @@ -545,6 +627,10 @@ opts_visitor_cleanup(OptsVisitor *ov) > if (ov->unprocessed_opts !=3D NULL) { > g_hash_table_destroy(ov->unprocessed_opts); > } > + if (ov->opts !=3D NULL) { > + g_hash_table_destroy(ov->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-schem= a/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' } > =20 > +# 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', > + 'sub1': 'UserDefOptionsSub' } } > =20 > # testing event > { 'struct': 'EventStructOne', > diff --git a/tests/test-opts-visitor.c b/tests/test-opts-visitor.c > index ebeee5d..5862c7c 100644 > --- a/tests/test-opts-visitor.c > +++ b/tests/test-opts-visitor.c > @@ -177,6 +177,34 @@ expect_u64_max(OptsVisitorFixture *f, gconstpointe= r test_data) > g_assert(f->userdef->u64->value =3D=3D UINT64_MAX); > } > =20 > +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 =3D=3D 13); > + g_assert(f->userdef->sub1->has_nint); > + g_assert(f->userdef->sub1->nint =3D=3D 13); > +} > + > +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 =3D=3D 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 =3D=3D 13); > +} > + > /* test cases */ > =20 > int > @@ -270,6 +298,12 @@ main(int argc, char **argv) > add_test("/visitor/opts/i64/range/2big/full", &expect_fail, > "i64=3D-0x8000000000000000-0x7fffffffffffffff"); > =20 > + /* Test nested structs support */ > + add_test("/visitor/opts/nested/unqualified", &expect_both, "nint=3D= 13"); > + add_test("/visitor/opts/nested/both", &expect_both, > + "sub0.nint=3D13,sub1.nint=3D13"); > + add_test("/visitor/opts/nested/sub0", &expect_sub0, "sub0.n= int=3D13"); > + add_test("/visitor/opts/nested/sub1", &expect_sub1, "sub1.n= int=3D13"); > g_test_run(); > return 0; > }