26ba25
From 44fe5f3579f8b5dfa08ce9f750306df44f026013 Mon Sep 17 00:00:00 2001
26ba25
From: Markus Armbruster <armbru@redhat.com>
26ba25
Date: Mon, 18 Jun 2018 08:43:28 +0200
26ba25
Subject: [PATCH 030/268] block: Fix -blockdev / blockdev-add for empty objects
26ba25
 and arrays
26ba25
26ba25
RH-Author: Markus Armbruster <armbru@redhat.com>
26ba25
Message-id: <20180618084330.30009-22-armbru@redhat.com>
26ba25
Patchwork-id: 80726
26ba25
O-Subject: [RHEL-7.6 qemu-kvm-rhev PATCH 21/23] block: Fix -blockdev / blockdev-add for empty objects and arrays
26ba25
Bugzilla: 1557995
26ba25
RH-Acked-by: Max Reitz <mreitz@redhat.com>
26ba25
RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
26ba25
RH-Acked-by: Kevin Wolf <kwolf@redhat.com>
26ba25
26ba25
-blockdev and blockdev-add silently ignore empty objects and arrays in
26ba25
their argument.  That's because qmp_blockdev_add() converts the
26ba25
argument to a flat QDict, and qdict_flatten() eats empty QDict and
26ba25
QList members.  For instance, we ignore an empty BlockdevOptions
26ba25
member @cache.  No real harm, as absent means the same as empty there.
26ba25
26ba25
Thus, the flaw puts an artificial restriction on the QAPI schema: we
26ba25
can't have potentially empty objects and arrays within
26ba25
BlockdevOptions, except when they're optional and "empty" has the same
26ba25
meaning as "absent".
26ba25
26ba25
Our QAPI schema satisfies this restriction (I checked), but it's a
26ba25
trap for the unwary, and a temptation to employ awkward workarounds
26ba25
for the wary.  Let's get rid of it.
26ba25
26ba25
Change qdict_flatten() and qdict_crumple() to treat empty dictionaries
26ba25
and lists exactly like scalars.
26ba25
26ba25
Signed-off-by: Markus Armbruster <armbru@redhat.com>
26ba25
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
26ba25
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
26ba25
(cherry picked from commit 2860b2b2cb883969c8f6464bd9f8bc88742c5c73)
26ba25
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
26ba25
---
26ba25
 qobject/block-qdict.c     | 54 +++++++++++++++++++++++++++++------------------
26ba25
 tests/check-block-qdict.c | 38 ++++++++++++++++++++++++++-------
26ba25
 2 files changed, 63 insertions(+), 29 deletions(-)
26ba25
26ba25
diff --git a/qobject/block-qdict.c b/qobject/block-qdict.c
26ba25
index e51a3d2..df83308 100644
26ba25
--- a/qobject/block-qdict.c
26ba25
+++ b/qobject/block-qdict.c
26ba25
@@ -56,6 +56,8 @@ static void qdict_flatten_qlist(QList *qlist, QDict *target, const char *prefix)
26ba25
 {
26ba25
     QObject *value;
26ba25
     const QListEntry *entry;
26ba25
+    QDict *dict_val;
26ba25
+    QList *list_val;
26ba25
     char *new_key;
26ba25
     int i;
26ba25
 
26ba25
@@ -69,16 +71,18 @@ static void qdict_flatten_qlist(QList *qlist, QDict *target, const char *prefix)
26ba25
 
26ba25
     for (i = 0; entry; entry = qlist_next(entry), i++) {
26ba25
         value = qlist_entry_obj(entry);
26ba25
+        dict_val = qobject_to(QDict, value);
26ba25
+        list_val = qobject_to(QList, value);
26ba25
         new_key = g_strdup_printf("%s.%i", prefix, i);
26ba25
 
26ba25
         /*
26ba25
          * Flatten non-empty QDict and QList recursively into @target,
26ba25
          * copy other objects to @target
26ba25
          */
26ba25
-        if (qobject_type(value) == QTYPE_QDICT) {
26ba25
-            qdict_flatten_qdict(qobject_to(QDict, value), target, new_key);
26ba25
-        } else if (qobject_type(value) == QTYPE_QLIST) {
26ba25
-            qdict_flatten_qlist(qobject_to(QList, value), target, new_key);
26ba25
+        if (dict_val && qdict_size(dict_val)) {
26ba25
+            qdict_flatten_qdict(dict_val, target, new_key);
26ba25
+        } else if (list_val && !qlist_empty(list_val)) {
26ba25
+            qdict_flatten_qlist(list_val, target, new_key);
26ba25
         } else {
26ba25
             qdict_put_obj(target, new_key, qobject_ref(value));
26ba25
         }
26ba25
@@ -91,6 +95,8 @@ static void qdict_flatten_qdict(QDict *qdict, QDict *target, const char *prefix)
26ba25
 {
26ba25
     QObject *value;
26ba25
     const QDictEntry *entry, *next;
26ba25
+    QDict *dict_val;
26ba25
+    QList *list_val;
26ba25
     char *new_key;
26ba25
 
26ba25
     entry = qdict_first(qdict);
26ba25
@@ -98,6 +104,8 @@ static void qdict_flatten_qdict(QDict *qdict, QDict *target, const char *prefix)
26ba25
     while (entry != NULL) {
26ba25
         next = qdict_next(qdict, entry);
26ba25
         value = qdict_entry_value(entry);
26ba25
+        dict_val = qobject_to(QDict, value);
26ba25
+        list_val = qobject_to(QList, value);
26ba25
         new_key = NULL;
26ba25
 
26ba25
         if (prefix) {
26ba25
@@ -108,12 +116,12 @@ static void qdict_flatten_qdict(QDict *qdict, QDict *target, const char *prefix)
26ba25
          * Flatten non-empty QDict and QList recursively into @target,
26ba25
          * copy other objects to @target
26ba25
          */
26ba25
-        if (qobject_type(value) == QTYPE_QDICT) {
26ba25
-            qdict_flatten_qdict(qobject_to(QDict, value), target,
26ba25
+        if (dict_val && qdict_size(dict_val)) {
26ba25
+            qdict_flatten_qdict(dict_val, target,
26ba25
                                 new_key ? new_key : entry->key);
26ba25
             qdict_del(qdict, entry->key);
26ba25
-        } else if (qobject_type(value) == QTYPE_QLIST) {
26ba25
-            qdict_flatten_qlist(qobject_to(QList, value), target,
26ba25
+        } else if (list_val && !qlist_empty(list_val)) {
26ba25
+            qdict_flatten_qlist(list_val, target,
26ba25
                                 new_key ? new_key : entry->key);
26ba25
             qdict_del(qdict, entry->key);
26ba25
         } else if (target != qdict) {
26ba25
@@ -127,10 +135,11 @@ static void qdict_flatten_qdict(QDict *qdict, QDict *target, const char *prefix)
26ba25
 }
26ba25
 
26ba25
 /**
26ba25
- * qdict_flatten(): For each nested QDict with key x, all fields with key y
26ba25
- * are moved to this QDict and their key is renamed to "x.y". For each nested
26ba25
- * QList with key x, the field at index y is moved to this QDict with the key
26ba25
- * "x.y" (i.e., the reverse of what qdict_array_split() does).
26ba25
+ * qdict_flatten(): For each nested non-empty QDict with key x, all
26ba25
+ * fields with key y are moved to this QDict and their key is renamed
26ba25
+ * to "x.y". For each nested non-empty QList with key x, the field at
26ba25
+ * index y is moved to this QDict with the key "x.y" (i.e., the
26ba25
+ * reverse of what qdict_array_split() does).
26ba25
  * This operation is applied recursively for nested QDicts and QLists.
26ba25
  */
26ba25
 void qdict_flatten(QDict *qdict)
26ba25
@@ -361,8 +370,8 @@ static int qdict_is_list(QDict *maybe_list, Error **errp)
26ba25
  * @src: the original flat dictionary (only scalar values) to crumple
26ba25
  *
26ba25
  * Takes a flat dictionary whose keys use '.' separator to indicate
26ba25
- * nesting, and values are scalars, and crumples it into a nested
26ba25
- * structure.
26ba25
+ * nesting, and values are scalars, empty dictionaries or empty lists,
26ba25
+ * and crumples it into a nested structure.
26ba25
  *
26ba25
  * To include a literal '.' in a key name, it must be escaped as '..'
26ba25
  *
26ba25
@@ -399,6 +408,8 @@ QObject *qdict_crumple(const QDict *src, Error **errp)
26ba25
 {
26ba25
     const QDictEntry *ent;
26ba25
     QDict *two_level, *multi_level = NULL, *child_dict;
26ba25
+    QDict *dict_val;
26ba25
+    QList *list_val;
26ba25
     QObject *dst = NULL, *child;
26ba25
     size_t i;
26ba25
     char *prefix = NULL;
26ba25
@@ -409,10 +420,11 @@ QObject *qdict_crumple(const QDict *src, Error **errp)
26ba25
 
26ba25
     /* Step 1: split our totally flat dict into a two level dict */
26ba25
     for (ent = qdict_first(src); ent != NULL; ent = qdict_next(src, ent)) {
26ba25
-        if (qobject_type(ent->value) == QTYPE_QDICT ||
26ba25
-            qobject_type(ent->value) == QTYPE_QLIST) {
26ba25
-            error_setg(errp, "Value %s is not a scalar",
26ba25
-                       ent->key);
26ba25
+        dict_val = qobject_to(QDict, ent->value);
26ba25
+        list_val = qobject_to(QList, ent->value);
26ba25
+        if ((dict_val && qdict_size(dict_val))
26ba25
+            || (list_val && !qlist_empty(list_val))) {
26ba25
+            error_setg(errp, "Value %s is not flat", ent->key);
26ba25
             goto error;
26ba25
         }
26ba25
 
26ba25
@@ -451,9 +463,9 @@ QObject *qdict_crumple(const QDict *src, Error **errp)
26ba25
     multi_level = qdict_new();
26ba25
     for (ent = qdict_first(two_level); ent != NULL;
26ba25
          ent = qdict_next(two_level, ent)) {
26ba25
-        QDict *dict = qobject_to(QDict, ent->value);
26ba25
-        if (dict) {
26ba25
-            child = qdict_crumple(dict, errp);
26ba25
+        dict_val = qobject_to(QDict, ent->value);
26ba25
+        if (dict_val && qdict_size(dict_val)) {
26ba25
+            child = qdict_crumple(dict_val, errp);
26ba25
             if (!child) {
26ba25
                 goto error;
26ba25
             }
26ba25
diff --git a/tests/check-block-qdict.c b/tests/check-block-qdict.c
26ba25
index 2da16f0..1d20fcc 100644
26ba25
--- a/tests/check-block-qdict.c
26ba25
+++ b/tests/check-block-qdict.c
26ba25
@@ -79,10 +79,10 @@ static void qdict_flatten_test(void)
26ba25
      *     "e.1.2.b": 1,
26ba25
      *     "f.c": 2,
26ba25
      *     "f.d": 3,
26ba25
-     *     "g": 4
26ba25
+     *     "g": 4,
26ba25
+     *     "y.0": {},
26ba25
+     *     "z.a": []
26ba25
      * }
26ba25
-     *
26ba25
-     * Note that "y" and "z" get eaten.
26ba25
      */
26ba25
 
26ba25
     qdict_put_int(e_1_2, "a", 0);
26ba25
@@ -117,8 +117,10 @@ static void qdict_flatten_test(void)
26ba25
     g_assert(qdict_get_int(root, "f.c") == 2);
26ba25
     g_assert(qdict_get_int(root, "f.d") == 3);
26ba25
     g_assert(qdict_get_int(root, "g") == 4);
26ba25
+    g_assert(!qdict_size(qdict_get_qdict(root, "y.0")));
26ba25
+    g_assert(qlist_empty(qdict_get_qlist(root, "z.a")));
26ba25
 
26ba25
-    g_assert(qdict_size(root) == 8);
26ba25
+    g_assert(qdict_size(root) == 10);
26ba25
 
26ba25
     qobject_unref(root);
26ba25
 }
26ba25
@@ -387,7 +389,8 @@ static void qdict_join_test(void)
26ba25
 static void qdict_crumple_test_recursive(void)
26ba25
 {
26ba25
     QDict *src, *dst, *rule, *vnc, *acl, *listen;
26ba25
-    QList *rules;
26ba25
+    QDict *empty, *empty_dict, *empty_list_0;
26ba25
+    QList *rules, *empty_list, *empty_dict_a;
26ba25
 
26ba25
     src = qdict_new();
26ba25
     qdict_put_str(src, "vnc.listen.addr", "127.0.0.1");
26ba25
@@ -399,10 +402,12 @@ static void qdict_crumple_test_recursive(void)
26ba25
     qdict_put_str(src, "vnc.acl.default", "deny");
26ba25
     qdict_put_str(src, "vnc.acl..name", "acl0");
26ba25
     qdict_put_str(src, "vnc.acl.rule..name", "acl0");
26ba25
+    qdict_put(src, "empty.dict.a", qlist_new());
26ba25
+    qdict_put(src, "empty.list.0", qdict_new());
26ba25
 
26ba25
     dst = qobject_to(QDict, qdict_crumple(src, &error_abort));
26ba25
     g_assert(dst);
26ba25
-    g_assert_cmpint(qdict_size(dst), ==, 1);
26ba25
+    g_assert_cmpint(qdict_size(dst), ==, 2);
26ba25
 
26ba25
     vnc = qdict_get_qdict(dst, "vnc");
26ba25
     g_assert(vnc);
26ba25
@@ -440,6 +445,21 @@ static void qdict_crumple_test_recursive(void)
26ba25
     g_assert_cmpstr("acl0", ==, qdict_get_str(vnc, "acl.name"));
26ba25
     g_assert_cmpstr("acl0", ==, qdict_get_str(acl, "rule.name"));
26ba25
 
26ba25
+    empty = qdict_get_qdict(dst, "empty");
26ba25
+    g_assert(empty);
26ba25
+    g_assert_cmpint(qdict_size(empty), ==, 2);
26ba25
+    empty_dict = qdict_get_qdict(empty, "dict");
26ba25
+    g_assert(empty_dict);
26ba25
+    g_assert_cmpint(qdict_size(empty_dict), ==, 1);
26ba25
+    empty_dict_a = qdict_get_qlist(empty_dict, "a");
26ba25
+    g_assert(empty_dict_a && qlist_empty(empty_dict_a));
26ba25
+    empty_list = qdict_get_qlist(empty, "list");
26ba25
+    g_assert(empty_list);
26ba25
+    g_assert_cmpint(qlist_size(empty_list), ==, 1);
26ba25
+    empty_list_0 = qobject_to(QDict, qlist_pop(empty_list));
26ba25
+    g_assert(empty_list_0);
26ba25
+    g_assert_cmpint(qdict_size(empty_list_0), ==, 0);
26ba25
+
26ba25
     qobject_unref(src);
26ba25
     qobject_unref(dst);
26ba25
 }
26ba25
@@ -587,7 +607,7 @@ static void qdict_rename_keys_test(void)
26ba25
 
26ba25
 static void qdict_crumple_test_bad_inputs(void)
26ba25
 {
26ba25
-    QDict *src;
26ba25
+    QDict *src, *nested;
26ba25
     Error *error = NULL;
26ba25
 
26ba25
     src = qdict_new();
26ba25
@@ -614,7 +634,9 @@ static void qdict_crumple_test_bad_inputs(void)
26ba25
 
26ba25
     src = qdict_new();
26ba25
     /* The input should be flat, ie no dicts or lists */
26ba25
-    qdict_put(src, "rule.a", qdict_new());
26ba25
+    nested = qdict_new();
26ba25
+    qdict_put(nested, "x", qdict_new());
26ba25
+    qdict_put(src, "rule.a", nested);
26ba25
     qdict_put_str(src, "rule.b", "allow");
26ba25
 
26ba25
     g_assert(qdict_crumple(src, &error) == NULL);
26ba25
-- 
26ba25
1.8.3.1
26ba25