From: "Daniel P. Berrange" <berrange@redhat.com>
To: Max Reitz <mreitz@redhat.com>
Cc: "Paolo Bonzini" <pbonzini@redhat.com>,
qemu-devel@nongnu.org, "Andreas Färber" <afaerber@suse.de>,
"Markus Armbruster" <armbru@redhat.com>
Subject: Re: [Qemu-devel] [PATCH v1 01/10] qdict: implement a qdict_crumple method for un-flattening a dict
Date: Thu, 3 Mar 2016 11:01:35 +0000 [thread overview]
Message-ID: <20160303110135.GB32270@redhat.com> (raw)
In-Reply-To: <56D71147.4050204@redhat.com>
On Wed, Mar 02, 2016 at 05:13:59PM +0100, Max Reitz wrote:
> On 19.02.2016 17:47, Daniel P. Berrange wrote:
> > The qdict_flatten() method will take a dict whose elements are
> > further nested dicts/lists and flatten them by concatenating
> > keys.
> >
> > The qdict_crumple() method aims todo the reverse, taking a flat
> > qdict, and turning it into a set of nested dicts/lists. It will
> > apply nesting based on the key name, with a '.' indicating a
> > new level in the hierarchy. If the keys in the nested structure
> > are all numeric, it will create a list, otherwise it will create
> > a dict.
> >
> > If the keys are a mixture of numeric and non-numeric, or the
> > numeric keys are not in strictly ascending order, an error will
> > be reported.
> >
> > As an example, a flat dict containing
> >
> > {
> > 'foo.0.bar': 'one',
> > 'foo.0.wizz': '1',
> > 'foo.1.bar': 'two',
> > 'foo.1.wizz': '2'
> > }
> >
> > will get turned into a dict with one element 'foo' whose
> > value is a list. The list elements will each in turn be
> > dicts.
> >
> > {
> > 'foo' => [
> > { 'bar': 'one', 'wizz': '1' }
> > { 'bar': 'two', 'wizz': '2' }
> > ],
> > }
> >
> > If the key is intended to contain a literal '.', then it must
> > be escaped as '..'. ie a flat dict
> >
> > {
> > 'foo..bar': 'wizz',
> > 'bar.foo..bar': 'eek',
> > 'bar.hello': 'world'
> > }
> >
> > Will end up as
> >
> > {
> > 'foo.bar': 'wizz',
> > 'bar': {
> > 'foo.bar': 'eek',
> > 'hello': 'world'
> > }
> > }
> >
> > The intent of this function is that it allows a set of QemuOpts
> > to be turned into a nested data structure that mirrors the nested
> > used when the same object is defined over QMP.
> >
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> > include/qapi/qmp/qdict.h | 1 +
> > qobject/qdict.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++
> > tests/check-qdict.c | 39 ++++++++++
> > 3 files changed, 220 insertions(+)
> >
> > +QObject *qdict_crumple(QDict *src, Error **errp)
> > +{
> > + const QDictEntry *entry, *next;
> > + const char *p = NULL;
> > + QDict *tmp1 = NULL, *tmp2 = NULL;
> > + QObject *dst = NULL, *child;
> > + bool isList = false;
> > + ssize_t listMax = -1;
> > + size_t listLen = 0;
>
> As far as I'm aware we don't have any strict policy on names, but in
> general structs use UpperCamelCase and variables and functions use
> c_style_naming.
Yep, ok
> > + size_t i, j;
> > + int64_t val;
> > + char *key;
> > +
> > + tmp1 = qdict_new();
>
> I guess I'd like clearer variable names.
Sure
>
> > + entry = qdict_first(src);
> > +
> > + /* Step 1: extract everything as nested dicts */
> > + while (entry != NULL) {
> > + next = qdict_next(src, entry);
> > + qobject_incref(entry->value);
> > +
> > + /* Find first '.' separator, but treat '..' as
> > + * an escape sequence */
> > + p = NULL;
>
> How about at least "dot" or "separator"?
Sure.
> > + do {
> > + if (p) {
> > + p += 2;
> > + } else {
> > + p = entry->key;
> > + }
> > + p = strchr(p, '.');
> > + } while (p && *(p + 1) == '.');
> > +
> > + if (p) {
> > + key = g_strndup(entry->key,
> > + p - entry->key);
> > + } else {
> > + key = g_strdup(entry->key);
> > + }
> > +
> > + for (i = 0, j = 0; key[i] != '\0'; i++, j++) {
> > + if (key[i] == '.' &&
> > + key[i + 1] == '.') {
> > + i++;
> > + }
> > + key[j] = key[i];
> > + }
> > + key[j] = '\0';
>
> I general I think it would be nice to put this escaping code into an own
> static function which returns a strdup'd key for the QDict and this
> entry's key in that QDict.
Yep, good idea.
>
> > +
> > + if (p) {
> > + tmp2 = qdict_get_qdict(tmp1, key);
> > + p++;
> > + if (!tmp2) {
> > + tmp2 = qdict_new();
> > + qdict_put(tmp1, key, tmp2);
>
> This...
>
> > + }
> > + qdict_put_obj(tmp2, p, entry->value);
> > + } else {
> > + qdict_put_obj(tmp1, key, entry->value);
>
> ...and this may silently overwrite entries, e.g. when crumpling
>
> { "x": 42, "x.y": 23 }
>
> > + }
>
> key is leaked.
Oh that should result in an error being raised as it is
a contradictory input.
> > +
> > + entry = next;
> > + }
> > +
> > + /* Step 2: crumple the new dicts we just created */
> > + tmp2 = qdict_new();
>
> Please use more variables with clearer names.
>
> > + entry = qdict_first(tmp1);
> > + while (entry != NULL) {
> > + next = qdict_next(tmp1, entry);
> > +
> > + if (qobject_type(entry->value) == QTYPE_QDICT) {
> > + child = qdict_crumple((QDict *)entry->value, errp);
>
> The cast works, but I'd prefer qobject_to_qdict() nonetheless.
Ok
>
> > + if (!child) {
> > + goto error;
> > + }
> > +
> > + qdict_put_obj(tmp2, entry->key, child);
> > + } else {
> > + qobject_incref(entry->value);
> > + qdict_put_obj(tmp2, entry->key, entry->value);
> > + }
> > +
> > + entry = next;
> > + }
> > + QDECREF(tmp1);
> > +
> > + /* Step 3: detect if we need to turn our dict into list */
>
> You could use qdict_array_entries(tmp2, "") > 0 for this.
>
> If you do want to make { "0": 42, "2": 23 } and { "0": 42, "x": 23 }
> errors (my qdict_unflatten() just kept those QDicts), then you'd have to
> check on qdict_array_entries() error whether the QDict contained an
> integer key, but that would still be simpler than the following loop and
> the check in step 4.
If I use qdict_array_entries() then it does 2 O(N) iterations
of the input qdict, and to check the errors, I'll have todo
yet another iteration. My inlined code here does everything
in a single O(N) iteration instead of 3. I think that's
compelling enough to reason to not use that function.
> > + entry = qdict_first(tmp2);
> > + while (entry != NULL) {
> > + next = qdict_next(tmp2, entry);
> > +
> > + errno = 0;
> > + if (qemu_strtoll(entry->key, NULL, 10, &val) == 0) {
> > + if (!dst) {
> > + dst = (QObject *)qlist_new();
> > + isList = true;
> > + } else if (!isList) {
> > + error_setg(errp,
> > + "Key '%s' is for a list, but previous key is "
> > + "for a dict", entry->key);
> > + goto error;
> > + }
> > + listLen++;
> > + if (val > listMax) {
> > + listMax = val;
> > + }
> > + } else {
> > + if (!dst) {
> > + dst = (QObject *)tmp2;
> > + qobject_incref(dst);
> > + isList = false;
> > + } else if (isList) {
> > + error_setg(errp,
> > + "Key '%s' is for a dict, but previous key is "
> > + "for a list", entry->key);
> > + goto error;
> > + }
> > + }
> > +
> > + entry = next;
> > + }
> > +
> > + /* Step 4: Turn the dict into a list */
>
> Why not just use qdict_array_split(tmp2, &dst);?
Again qdict_array_split() is a pretty inefficiently written
function. It does multiple nested iterations over its input
giving it O(n^2) time, compared to O(n) for my code here.
The only user of qdict_array_entries() and qdict_array_split()
is the block quorum driver. From a brief look at that code
I think it could probably be changed to just call this new
qdict_crumple() method, and those 2 inefficient functions
could be deleted, so we don't have duplication of code.
> > + if (isList) {
> > + if (listLen != (listMax + 1)) {
> > + error_setg(errp, "List indexes are not continuous, "
> > + "saw %zu elements but %zu largest index",
> > + listLen, listMax);
> > + goto error;
> > + }
> > +
> > + for (i = 0; i < listLen; i++) {
> > + char *key = g_strdup_printf("%zu", i);
> > +
> > + child = qdict_get(tmp2, key);
> > + g_free(key);
> > + if (!child) {
> > + error_setg(errp, "Unexpected missing list entry %zu", i);
> > + goto error;
> > + }
> > +
> > + qobject_incref(child);
> > + qlist_append_obj((QList *)dst, child);
> > + }
> > + }
> > + QDECREF(tmp2);
> > +
> > + return dst;
> > +
> > + error:
> > + QDECREF(tmp2);
> > + QDECREF(tmp1);
> > + qobject_decref(dst);
> > + return NULL;
> > +}
> > +
> > +
Regards,
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://autobuild.org -o- http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
next prev parent reply other threads:[~2016-03-03 11:01 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-02-19 16:47 [Qemu-devel] [PATCH v1 00/10] Provide a QOM-based authorization API Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 01/10] qdict: implement a qdict_crumple method for un-flattening a dict Daniel P. Berrange
2016-02-19 17:01 ` Eric Blake
2016-02-19 17:08 ` Daniel P. Berrange
2016-03-02 16:13 ` Max Reitz
2016-03-03 11:01 ` Daniel P. Berrange [this message]
2016-03-05 15:15 ` Max Reitz
2016-03-07 15:06 ` Daniel P. Berrange
2016-03-07 15:49 ` Eric Blake
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 02/10] qapi: allow QmpInputVisitor to auto-cast types Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 03/10] qom: support arbitrary non-scalar properties with -object Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 04/10] util: add QAuthZ object as an authorization base class Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 05/10] util: add QAuthZSimple object type for a simple access control list Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 06/10] acl: delete existing ACL implementation Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 07/10] qemu-nbd: add support for ACLs for TLS clients Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 08/10] nbd: allow an ACL to be set with nbd-server-start QMP command Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 09/10] chardev: add support for ACLs for TLS clients Daniel P. Berrange
2016-02-19 16:47 ` [Qemu-devel] [PATCH v1 10/10] vnc: allow specifying a custom ACL object name Daniel P. Berrange
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20160303110135.GB32270@redhat.com \
--to=berrange@redhat.com \
--cc=afaerber@suse.de \
--cc=armbru@redhat.com \
--cc=mreitz@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.