qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>,
	Markus Armbruster <armbru@redhat.com>,
	qemu-devel@nongnu.org, Max Reitz <mreitz@redhat.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Luiz Capitulino <lcapitulino@redhat.com>
Subject: [Qemu-devel] [PATCH v2 02/16] qdict: Add qdict_unflatten()
Date: Tue,  1 Mar 2016 00:19:19 +0100	[thread overview]
Message-ID: <1456787973-19348-3-git-send-email-mreitz@redhat.com> (raw)
In-Reply-To: <1456787973-19348-1-git-send-email-mreitz@redhat.com>

The QMP input visitor is rather unhappy with flattened QDicts, which is
how they are generally used in the block layer. This function allows
unflattening a QDict so we can use an input visitor on it.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 include/qapi/qmp/qdict.h |   1 +
 qobject/qdict.c          | 189 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 190 insertions(+)

diff --git a/include/qapi/qmp/qdict.h b/include/qapi/qmp/qdict.h
index 223f746..0ec7477 100644
--- a/include/qapi/qmp/qdict.h
+++ b/include/qapi/qmp/qdict.h
@@ -70,6 +70,7 @@ void qdict_set_default_str(QDict *dst, const char *key, const char *val);
 
 QDict *qdict_clone_shallow(const QDict *src);
 void qdict_flatten(QDict *qdict);
+bool qdict_unflatten(QDict *qdict, Error **errp);
 
 void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start);
 void qdict_array_split(QDict *src, QList **dst);
diff --git a/qobject/qdict.c b/qobject/qdict.c
index bbfe39f..800af38 100644
--- a/qobject/qdict.c
+++ b/qobject/qdict.c
@@ -771,6 +771,195 @@ int qdict_array_entries(QDict *src, const char *subqdict)
 }
 
 /**
+ * qlist_unflatten(): Recursive helper function for qdict_unflatten(). Invokes
+ * qdict_unflatten() and qlist_unflatten() on all its QDict and QList members,
+ * respectively.
+ */
+static bool qlist_unflatten(QList *qlist, Error **errp)
+{
+    const QListEntry *entry;
+
+    for (entry = qlist_first(qlist); entry; entry = qlist_next(entry)) {
+        switch (qobject_type(entry->value)) {
+        case QTYPE_QDICT:
+            if (!qdict_unflatten(qobject_to_qdict(entry->value), errp)) {
+                return false;
+            }
+            break;
+
+        case QTYPE_QLIST:
+            if (!qlist_unflatten(qobject_to_qlist(entry->value), errp)) {
+                return false;
+            }
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * qdict_unflatten(): The opposite of qdict_flatten().
+ *
+ * Every entry whose key is of the form "${prefix}.${index}" is moved to index
+ * "${index}" in a QList whose key in @qdict is "${prefix}", if
+ * qdict_array_entries(qdict, "${prefix}.") yields a positive value.
+ *
+ * Every entry whose key is of the form "${prefix}.${index}.${trailing}" is
+ * moved into a QDict at index "${index}" in a QList whose key in @qdict is
+ * "${prefix}". The moved object's key in the nested QDict is "${trailing}".
+ * This is only done if qdict_array_entries(qdict, "${prefix}.") yields a
+ * positive value.
+ *
+ * Every remaining entry whose key is of the form "${prefix}.${trailing}" is
+ * moved into a QDict whose key in @qdict is "${prefix}". The moved object's key
+ * in the nested QDict is "${trailing}".
+ *
+ * This algorithm then recurses on all QDict members (including indirect ones
+ * in QLists) of this QDict.
+ *
+ * This function will never overwrite existing members. For instance:
+ *   qdict_unflatten({ "x": 42, "x.y": 23 })
+ * is an error because there already is an "x" element which is not a QDict.
+ * However,
+ *   qdict_unflatten({ "x": { "a": 0 }, "x.y": 23 })
+ *   => { "x": { "a": 0, "y": 23 } }
+ * because the flattened "x.y" can be merged into the existing "x" QDict without
+ * overwriting any of its members. In contrast to that,
+ *   qdict_unflatten({ "x": { "y": 0 }, "x.y": 23 })
+ * is an error because "y" nested in "x" would need to be overwritten.
+ *
+ * This function returns true on success and false on error (in which case *errp
+ * is set). On error, the contents of @qdict are undefined.
+ */
+bool qdict_unflatten(QDict *qdict, Error **errp)
+{
+    const QDictEntry *entry;
+
+    /* First pass: Unflatten this level */
+    entry = qdict_first(qdict);
+    while (entry) {
+        const char *prefix_end = strchr(entry->key, '.');
+
+        if (prefix_end) {
+            size_t prefix_length = prefix_end - entry->key;
+            char *prefix, *prefix_dot;
+
+            prefix = g_malloc(prefix_length + 1);
+            strncpy(prefix, entry->key, prefix_length);
+            prefix[prefix_length] = 0;
+
+            prefix_dot = g_strdup_printf("%s.", prefix);
+
+            if (qdict_array_entries(qdict, prefix_dot) > 0) {
+                /* Move all entries with this prefix into a nested QList */
+                QDict *array_qdict;
+                QList *target_qlist;
+
+                /* We cannot merge two non-empty lists without one overwriting
+                 * members of the other */
+                if (qdict_haskey(qdict, prefix)) {
+                    if (qobject_type(qdict_get(qdict, prefix)) != QTYPE_QLIST ||
+                        !qlist_empty(qdict_get_qlist(qdict, prefix)))
+                    {
+                        error_setg(errp, "Cannot unflatten list '%s': Overlaps "
+                                   "with existing member", prefix);
+                        g_free(prefix);
+                        g_free(prefix_dot);
+                        return false;
+                    }
+
+                    /* Remove the existing empty list so we can replace it */
+                    qdict_del(qdict, prefix);
+                }
+
+                qdict_extract_subqdict(qdict, &array_qdict, prefix_dot);
+                qdict_array_split(array_qdict, &target_qlist);
+                assert(!qdict_size(array_qdict));
+                QDECREF(array_qdict);
+                qdict_put(qdict, prefix, target_qlist);
+            } else {
+                /* Move all entries with this prefix into a nested QDict */
+                QDict *target_qdict, *tmp_qdict;
+
+                if (qdict_haskey(qdict, prefix)) {
+                    if (qobject_type(qdict_get(qdict, prefix)) != QTYPE_QDICT) {
+                        error_setg(errp, "Cannot unflatten dict '%s': Overlaps "
+                                   "with non-dict member", prefix);
+                        g_free(prefix);
+                        g_free(prefix_dot);
+                        return false;
+                    }
+
+                    /* If there already is a QDict with this prefix, try to
+                     * merge the unflattened members into it */
+                    target_qdict = qdict_get_qdict(qdict, prefix);
+                } else {
+                    /* Otherwise created a new one */
+                    target_qdict = qdict_new();
+                    qdict_put(qdict, prefix, target_qdict);
+                }
+
+                qdict_extract_subqdict(qdict, &tmp_qdict, prefix_dot);
+                qdict_join(target_qdict, tmp_qdict, false);
+
+                if (qdict_size(tmp_qdict)) {
+                    error_setg(errp, "Flattened member '%s.%s' exists in "
+                               "previously existing unflattened dict",
+                               prefix, qdict_first(tmp_qdict)->key);
+                    QDECREF(tmp_qdict);
+                    g_free(prefix);
+                    g_free(prefix_dot);
+                    return false;
+                }
+
+                QDECREF(tmp_qdict);
+            }
+
+            g_free(prefix);
+            g_free(prefix_dot);
+
+            /* The QDict has changed, so we need to reiterate. This will not
+             * result in an infinite loop because every time we get here, one
+             * entry whose key contains a '.' has been removed, and in its place
+             * at most one entry whose key does not contain a '.' has been
+             * inserted. Therefore, the number of entries with '.' in their key
+             * decreases and will eventually reach 0, at which point we cannot
+             * get here anymore. */
+            entry = qdict_first(qdict);
+            continue;
+        }
+
+        entry = qdict_next(qdict, entry);
+    }
+
+    /* Second pass: Recurse to nested QDicts and QLists */
+    for (entry = qdict_first(qdict); entry; entry = qdict_next(qdict, entry)) {
+        switch (qobject_type(entry->value)) {
+        case QTYPE_QDICT:
+            if (!qdict_unflatten(qobject_to_qdict(entry->value), errp)) {
+                return false;
+            }
+            break;
+
+        case QTYPE_QLIST:
+            if (!qlist_unflatten(qobject_to_qlist(entry->value), errp)) {
+                return false;
+            }
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    return true;
+}
+
+/**
  * qdict_join(): Absorb the src QDict into the dest QDict, that is, move all
  * elements from src to dest.
  *
-- 
2.7.1

  parent reply	other threads:[~2016-02-29 23:19 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-02-29 23:19 [Qemu-devel] [PATCH v2 00/16] qapi: Allow blockdev-add for NBD Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 01/16] qdict: Add qdict_change_key() Max Reitz
2016-02-29 23:19 ` Max Reitz [this message]
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 03/16] check-qdict: Add a test for qdict_unflatten() Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 04/16] block/nbd: Drop trailing "." in error messages Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 05/16] block/nbd: Reject port parameter without host Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 06/16] block/nbd: Default port in nbd_refresh_filename() Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 07/16] block/nbd: Use qdict_put() Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 08/16] block/nbd: Add nbd_has_filename_options_conflict() Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 09/16] block/nbd: "address" in nbd_refresh_filename() Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 10/16] block/nbd: Accept SocketAddress Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 11/16] block/nbd: Use SocketAddress options Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 12/16] qapi: Allow blockdev-add for NBD Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 13/16] iotests.py: Add qemu_nbd function Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 14/16] iotests.py: Allow concurrent qemu instances Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 15/16] socket_scm_helper: Accept fd directly Max Reitz
2016-02-29 23:19 ` [Qemu-devel] [PATCH v2 16/16] iotests: Add test for NBD's blockdev-add interface Max Reitz
2016-02-29 23:24 ` [Qemu-devel] [PATCH v2 00/16] qapi: Allow blockdev-add for NBD Eric Blake
2016-02-29 23:37   ` Max Reitz
2016-03-01 10:00     ` Daniel P. Berrange
2016-03-01 10:12       ` Kevin Wolf

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=1456787973-19348-3-git-send-email-mreitz@redhat.com \
    --to=mreitz@redhat.com \
    --cc=armbru@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=lcapitulino@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-block@nongnu.org \
    --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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).