[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v2 02/16] qdict: Add qdict_unflatten()
From: |
Max Reitz |
Subject: |
[Qemu-devel] [PATCH v2 02/16] qdict: Add qdict_unflatten() |
Date: |
Tue, 1 Mar 2016 00:19:19 +0100 |
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 <address@hidden>
---
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
- [Qemu-devel] [PATCH v2 00/16] qapi: Allow blockdev-add for NBD, Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 01/16] qdict: Add qdict_change_key(), Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 02/16] qdict: Add qdict_unflatten(),
Max Reitz <=
- [Qemu-devel] [PATCH v2 03/16] check-qdict: Add a test for qdict_unflatten(), Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 04/16] block/nbd: Drop trailing "." in error messages, Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 06/16] block/nbd: Default port in nbd_refresh_filename(), Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 05/16] block/nbd: Reject port parameter without host, Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 07/16] block/nbd: Use qdict_put(), Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 08/16] block/nbd: Add nbd_has_filename_options_conflict(), Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 10/16] block/nbd: Accept SocketAddress, Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 09/16] block/nbd: "address" in nbd_refresh_filename(), Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 11/16] block/nbd: Use SocketAddress options, Max Reitz, 2016/02/29
- [Qemu-devel] [PATCH v2 15/16] socket_scm_helper: Accept fd directly, Max Reitz, 2016/02/29