All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Daniel P. Berrange" <berrange@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Paolo Bonzini" <pbonzini@redhat.com>,
	"Markus Armbruster" <armbru@redhat.com>,
	"Andreas Färber" <afaerber@suse.de>
Subject: [Qemu-devel] [PATCH v1 01/10] qdict: implement a qdict_crumple method for un-flattening a dict
Date: Fri, 19 Feb 2016 16:47:34 +0000	[thread overview]
Message-ID: <1455900463-16007-2-git-send-email-berrange@redhat.com> (raw)
In-Reply-To: <1455900463-16007-1-git-send-email-berrange@redhat.com>

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(+)

diff --git a/include/qapi/qmp/qdict.h b/include/qapi/qmp/qdict.h
index 6c2a0e5..baf45d5 100644
--- a/include/qapi/qmp/qdict.h
+++ b/include/qapi/qmp/qdict.h
@@ -75,6 +75,7 @@ void qdict_flatten(QDict *qdict);
 void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start);
 void qdict_array_split(QDict *src, QList **dst);
 int qdict_array_entries(QDict *src, const char *subqdict);
+QObject *qdict_crumple(QDict *src, Error **errp);
 
 void qdict_join(QDict *dest, QDict *src, bool overwrite);
 
diff --git a/qobject/qdict.c b/qobject/qdict.c
index 9833bd0..ff4caf8 100644
--- a/qobject/qdict.c
+++ b/qobject/qdict.c
@@ -608,6 +608,7 @@ void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start)
     }
 }
 
+
 static int qdict_count_prefixed_entries(const QDict *src, const char *start)
 {
     const QDictEntry *entry;
@@ -682,6 +683,185 @@ void qdict_array_split(QDict *src, QList **dst)
     }
 }
 
+
+/**
+ * qdict_crumple:
+ *
+ * Reverses the flattening done by qdict_flatten by
+ * crumpling the dicts into a nested structure. Similar
+ * qdict_array_split, but copes with arbitrary nesting
+ * of dicts & arrays, not meely one level of arrays
+ *
+ * { 'foo.0.bar': 'one', 'foo.0.wizz': '1',
+ *   'foo.1.bar': 'two', 'foo.1.wizz': '2' }
+ *
+ * =>
+ *
+ * {
+ *   'foo' => [
+ *      { 'bar': 'one', 'wizz': '1' }
+ *      { 'bar': 'two', 'wizz': '2' }
+ *   ],
+ * }
+ *
+ */
+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;
+    size_t i, j;
+    int64_t val;
+    char *key;
+
+    tmp1 = qdict_new();
+    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;
+        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';
+
+        if (p) {
+            tmp2 = qdict_get_qdict(tmp1, key);
+            p++;
+            if (!tmp2) {
+                tmp2 = qdict_new();
+                qdict_put(tmp1, key, tmp2);
+            }
+            qdict_put_obj(tmp2, p, entry->value);
+        } else {
+            qdict_put_obj(tmp1, key, entry->value);
+        }
+
+        entry = next;
+    }
+
+    /* Step 2: crumple the new dicts we just created */
+    tmp2 = qdict_new();
+    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);
+            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 */
+    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 */
+    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;
+}
+
+
 /**
  * qdict_array_entries(): Returns the number of direct array entries if the
  * sub-QDict of src specified by the prefix in subqdict (or src itself for
diff --git a/tests/check-qdict.c b/tests/check-qdict.c
index a43056c..fb8184c 100644
--- a/tests/check-qdict.c
+++ b/tests/check-qdict.c
@@ -596,6 +596,43 @@ static void qdict_join_test(void)
     QDECREF(dict2);
 }
 
+
+static void qdict_crumple_test(void)
+{
+    QDict *src, *dst, *rule, *eek;
+    QList *rules;
+
+    src = qdict_new();
+    qdict_put(src, "rule.0.match", qstring_from_str("fred"));
+    qdict_put(src, "rule.0.policy", qstring_from_str("allow"));
+    qdict_put(src, "rule.1.match", qstring_from_str("bob"));
+    qdict_put(src, "rule.1.policy", qstring_from_str("deny"));
+    qdict_put(src, "foo..bar", qstring_from_str("wibble"));
+    qdict_put(src, "eek.foo..bar", qstring_from_str("wizz"));
+
+    dst = (QDict *)qdict_crumple(src, &error_abort);
+
+    g_assert_cmpint(qdict_size(dst), ==, 3);
+
+    rules = qdict_get_qlist(dst, "rule");
+
+    g_assert_cmpint(qlist_size(rules), ==, 2);
+
+    rule = qobject_to_qdict(qlist_pop(rules));
+    g_assert_cmpstr("fred", ==, qdict_get_str(rule, "match"));
+    g_assert_cmpstr("allow", ==, qdict_get_str(rule, "policy"));
+
+    rule = qobject_to_qdict(qlist_pop(rules));
+    g_assert_cmpstr("bob", ==, qdict_get_str(rule, "match"));
+    g_assert_cmpstr("deny", ==, qdict_get_str(rule, "policy"));
+
+    g_assert_cmpstr("wibble", ==, qdict_get_str(dst, "foo.bar"));
+
+    eek = qdict_get_qdict(dst, "eek");
+    g_assert_cmpstr("wizz", ==, qdict_get_str(eek, "foo.bar"));
+}
+
+
 /*
  * Errors test-cases
  */
@@ -743,6 +780,8 @@ int main(int argc, char **argv)
     g_test_add_func("/errors/put_exists", qdict_put_exists_test);
     g_test_add_func("/errors/get_not_exists", qdict_get_not_exists_test);
 
+    g_test_add_func("/public/crumple", qdict_crumple_test);
+
     /* The Big one */
     if (g_test_slow()) {
         g_test_add_func("/stress/test", qdict_stress_test);
-- 
2.5.0

  reply	other threads:[~2016-02-19 16:47 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 ` Daniel P. Berrange [this message]
2016-02-19 17:01   ` [Qemu-devel] [PATCH v1 01/10] qdict: implement a qdict_crumple method for un-flattening a dict 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
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=1455900463-16007-2-git-send-email-berrange@redhat.com \
    --to=berrange@redhat.com \
    --cc=afaerber@suse.de \
    --cc=armbru@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.