qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object
@ 2020-10-11  7:34 Markus Armbruster
  2020-10-11  7:34 ` [PATCH v4 1/7] keyval: Fix and clarify grammar Markus Armbruster
                   ` (7 more replies)
  0 siblings, 8 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

This replaces the QemuOpts-based help code for --object in the storage
daemon with code based on the keyval parser.

Review of v3 led me to preexisting issues.  Instead of posting my
fixes separately, then working with Kevin to rebase his work on top of
mine, I did the rebase myself and am posting it together with my fixes
as v4.  Hope that's okay.

Note: to test qemu-storage-daemon --object help, you need Philippe's
"[PATCH v3] hw/nvram: Always register FW_CFG_DATA_GENERATOR_INTERFACE"
and its prerequisites (first 10 patches of Paolo's "[PULL 00/39] SCSI,
qdev, qtest, meson patches for 2020-10-10"), so it doesn't crash with
"missing interface 'fw_cfg-data-generator' for object 'tls-creds'".

v4:
- PATCH 1-3: New
- PATCH 4:
  * Rebased
  * Commit message typos [Eric]
  * Replace flawed is_help_option_n() by starts_with_help_option()
    [me]
  * Update grammar and accompanying prose [me]
  * Update keyval_parse_one()'s contract, tweak keyval_parse()'s [me]
  * Revert the keyval_parse_one() change to a rebased version of v1,
    because it's simpler and the edge case that led to the more
    complicated version no longer exists [me].
  * Rearrange tests so the simple cases come first
- PATCH 5-7: Unchanged

v3:
- Always parse help options, no matter if the caller implements help or
  not. If it doesn't, return an error. [Markus]
- Document changes to the keyval parser grammar [Markus]
- Support both 'help' and '?' [Eric]
- Test case fixes [Eric]
- Improved documentation of user_creatable_print_help(_from_qdict)
  [Markus]

v2:
- Fixed double comma by reusing the existing key and value parsers [Eric]
- More tests to cover the additional cases

Kevin Wolf (4):
  keyval: Parse help options
  qom: Factor out helpers from user_creatable_print_help()
  qom: Add user_creatable_print_help_from_qdict()
  qemu-storage-daemon: Remove QemuOpts from --object parser

Markus Armbruster (3):
  keyval: Fix and clarify grammar
  test-keyval: Demonstrate misparse of ',' with implied key
  keyval: Fix parsing of ',' in value of implied key

 include/qemu/help_option.h           |  11 ++
 include/qemu/option.h                |   2 +-
 include/qom/object_interfaces.h      |  21 ++-
 qapi/qobject-input-visitor.c         |   2 +-
 qom/object_interfaces.c              |  99 ++++++++------
 storage-daemon/qemu-storage-daemon.c |  15 +--
 tests/test-keyval.c                  | 187 ++++++++++++++++++---------
 util/keyval.c                        | 103 +++++++++++----
 8 files changed, 297 insertions(+), 143 deletions(-)

-- 
2.26.2



^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v4 1/7] keyval: Fix and clarify grammar
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
@ 2020-10-11  7:34 ` Markus Armbruster
  2020-10-12 11:43   ` Eric Blake
  2020-10-11  7:35 ` [PATCH v4 2/7] test-keyval: Demonstrate misparse of ', ' with implied key Markus Armbruster
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

The grammar has a few issues:

* key-fragment = / [^=,.]* /

  Prose restricts key fragments: they "must be valid QAPI names or
  consist only of decimal digits".  Technically, '' consists only of
  decimal digits.  The code rejects that.  Fix the grammar.

* val          = { / [^,]* / | ',,' }

  Use + instead of *.  Accepts the same language.

* val-no-key   = / [^=,]* /

  The code rejects an empty value.  Fix the grammar.

* Section "Additional syntax for use with an implied key" is
  confusing.  Rewrite it.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 util/keyval.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/util/keyval.c b/util/keyval.c
index 13def4af54..82d8497c71 100644
--- a/util/keyval.c
+++ b/util/keyval.c
@@ -16,8 +16,8 @@
  *   key-vals     = [ key-val { ',' key-val } [ ',' ] ]
  *   key-val      = key '=' val
  *   key          = key-fragment { '.' key-fragment }
- *   key-fragment = / [^=,.]* /
- *   val          = { / [^,]* / | ',,' }
+ *   key-fragment = / [^=,.]+ /
+ *   val          = { / [^,]+ / | ',,' }
  *
  * Semantics defined by reduction to JSON:
  *
@@ -71,12 +71,16 @@
  * Awkward.  Note that we carefully restrict alternate types to avoid
  * similar ambiguity.
  *
- * Additional syntax for use with an implied key:
+ * Alternative syntax for use with an implied key:
  *
- *   key-vals-ik  = val-no-key [ ',' key-vals ]
- *   val-no-key   = / [^=,]* /
+ *   key-vals     = [ key-val-1st { ',' key-val } [ ',' ] ]
+ *   key-val-1st  = val-no-key | key-val
+ *   val-no-key   = / [^=,]+ /
  *
- * where no-key is syntactic sugar for implied-key=val-no-key.
+ * where val-no-key is syntactic sugar for implied-key=val-no-key.
+ *
+ * Note that you can't use the sugared form when the value contains
+ * '=' or ','.
  */
 
 #include "qemu/osdep.h"
-- 
2.26.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v4 2/7] test-keyval: Demonstrate misparse of ', ' with implied key
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
  2020-10-11  7:34 ` [PATCH v4 1/7] keyval: Fix and clarify grammar Markus Armbruster
@ 2020-10-11  7:35 ` Markus Armbruster
  2020-10-12 11:44   ` [PATCH v4 2/7] test-keyval: Demonstrate misparse of ',' " Eric Blake
  2020-10-11  7:35 ` [PATCH v4 3/7] keyval: Fix parsing of ',' in value of " Markus Armbruster
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

Add a test for "val,,ue" with implied key.  Documentation says this
should parse as implied key with value "val", then fail.  The code
parses it as implied key with value "val,ue", then succeeds.  The next
commit will fix it.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 tests/test-keyval.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/tests/test-keyval.c b/tests/test-keyval.c
index e331a84149..f02bdf7029 100644
--- a/tests/test-keyval.c
+++ b/tests/test-keyval.c
@@ -182,6 +182,13 @@ static void test_keyval_parse(void)
     error_free_or_abort(&err);
     g_assert(!qdict);
 
+    /* Implied key's value can't have comma (qemu_opts_parse(): it can) */
+    /* BUG: it can */
+    qdict = keyval_parse("val,,ue", "implied", &error_abort);
+    g_assert_cmpuint(qdict_size(qdict), ==, 1);
+    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "val,ue");
+    qobject_unref(qdict);
+
     /* Empty key is not an implied key */
     qdict = keyval_parse("=val", "implied", &err);
     error_free_or_abort(&err);
-- 
2.26.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v4 3/7] keyval: Fix parsing of ',' in value of implied key
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
  2020-10-11  7:34 ` [PATCH v4 1/7] keyval: Fix and clarify grammar Markus Armbruster
  2020-10-11  7:35 ` [PATCH v4 2/7] test-keyval: Demonstrate misparse of ', ' with implied key Markus Armbruster
@ 2020-10-11  7:35 ` Markus Armbruster
  2020-10-12 11:46   ` [PATCH v4 3/7] keyval: Fix parsing of ', ' " Eric Blake
  2020-10-11  7:35 ` [PATCH v4 4/7] keyval: Parse help options Markus Armbruster
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

The previous commit demonstrated documentation and code disagree on
parsing of ',' in the value of an implied key.  Fix the code to match
the documentation.

This breaks uses of keyval_parse() that pass an implied key and accept
a value containing ','.  None of the existing uses does:

* audiodev: implied key "driver" is enum AudiodevDriver, none of the
  values contains ','

* display: implied key "type" is enum DisplayType, none of the values
  contains ','

* blockdev: implied key "driver is enum BlockdevDriver, none of the
  values contains ','

* export: implied key "type" is enum BlockExportType, none of the
  values contains ','

* monitor: implied key "mode" is enum MonitorMode, none of the values
  contains ','

* nbd-server: no implied key.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 tests/test-keyval.c |  8 +++-----
 util/keyval.c       | 28 +++++++++++++++++-----------
 2 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/tests/test-keyval.c b/tests/test-keyval.c
index f02bdf7029..04c62cf045 100644
--- a/tests/test-keyval.c
+++ b/tests/test-keyval.c
@@ -183,11 +183,9 @@ static void test_keyval_parse(void)
     g_assert(!qdict);
 
     /* Implied key's value can't have comma (qemu_opts_parse(): it can) */
-    /* BUG: it can */
-    qdict = keyval_parse("val,,ue", "implied", &error_abort);
-    g_assert_cmpuint(qdict_size(qdict), ==, 1);
-    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "val,ue");
-    qobject_unref(qdict);
+    qdict = keyval_parse("val,,ue", "implied", &err);
+    error_free_or_abort(&err);
+    g_assert(!qdict);
 
     /* Empty key is not an implied key */
     qdict = keyval_parse("=val", "implied", &err);
diff --git a/util/keyval.c b/util/keyval.c
index 82d8497c71..8f33a36a7c 100644
--- a/util/keyval.c
+++ b/util/keyval.c
@@ -173,7 +173,7 @@ static const char *keyval_parse_one(QDict *qdict, const char *params,
                                     const char *implied_key,
                                     Error **errp)
 {
-    const char *key, *key_end, *s, *end;
+    const char *key, *key_end, *val_end, *s, *end;
     size_t len;
     char key_in_cur[128];
     QDict *cur;
@@ -182,10 +182,12 @@ static const char *keyval_parse_one(QDict *qdict, const char *params,
     QString *val;
 
     key = params;
+    val_end = NULL;
     len = strcspn(params, "=,");
     if (implied_key && len && key[len] != '=') {
         /* Desugar implied key */
         key = implied_key;
+        val_end = params + len;
         len = strlen(implied_key);
     }
     key_end = key + len;
@@ -241,7 +243,11 @@ static const char *keyval_parse_one(QDict *qdict, const char *params,
 
     if (key == implied_key) {
         assert(!*s);
-        s = params;
+        val = qstring_from_substr(params, 0, val_end - params);
+        s = val_end;
+        if (*s == ',') {
+            s++;
+        }
     } else {
         if (*s != '=') {
             error_setg(errp, "Expected '=' after parameter '%.*s'",
@@ -249,19 +255,19 @@ static const char *keyval_parse_one(QDict *qdict, const char *params,
             return NULL;
         }
         s++;
-    }
 
-    val = qstring_new();
-    for (;;) {
-        if (!*s) {
-            break;
-        } else if (*s == ',') {
-            s++;
-            if (*s != ',') {
+        val = qstring_new();
+        for (;;) {
+            if (!*s) {
                 break;
+            } else if (*s == ',') {
+                s++;
+                if (*s != ',') {
+                    break;
+                }
             }
+            qstring_append_chr(val, *s++);
         }
-        qstring_append_chr(val, *s++);
     }
 
     if (!keyval_parse_put(cur, key_in_cur, val, key, key_end, errp)) {
-- 
2.26.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v4 4/7] keyval: Parse help options
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
                   ` (2 preceding siblings ...)
  2020-10-11  7:35 ` [PATCH v4 3/7] keyval: Fix parsing of ',' in value of " Markus Armbruster
@ 2020-10-11  7:35 ` Markus Armbruster
  2020-10-12 11:51   ` Eric Blake
  2020-10-12 14:35   ` Kevin Wolf
  2020-10-11  7:35 ` [PATCH v4 5/7] qom: Factor out helpers from user_creatable_print_help() Markus Armbruster
                   ` (3 subsequent siblings)
  7 siblings, 2 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

From: Kevin Wolf <kwolf@redhat.com>

This adds a special meaning for 'help' and '?' as options to the keyval
parser. Instead of being an error (because of a missing value) or a
value for an implied key, they now request help, which is a new boolean
output of the parser in addition to the QDict.

A new parameter 'p_help' is added to keyval_parse() that contains on
return whether help was requested. If NULL is passed, requesting help
results in an error and all other cases work like before.

Turning previous error cases into help is a compatible extension. The
behaviour potentially changes for implied keys: They could previously
get 'help' as their value, which is now interpreted as requesting help.

This is not a problem in practice because 'help' and '?' are not a valid
values for the implied key of any option parsed with keyval_parse():

* audiodev: union Audiodev, implied key "driver" is enum AudiodevDriver,
  "help" and "?" are not among its values

* display: union DisplayOptions, implied key "type" is enum
  DisplayType, "help" and "?" are not among its values

* blockdev: union BlockdevOptions, implied key "driver is enum
  BlockdevDriver, "help" and "?" are not among its values

* export: union BlockExport, implied key "type" is enum BlockExportType,
  "help" and "?" are not among its values

* monitor: struct MonitorOptions, implied key "mode" is enum MonitorMode,
  "help" and "?" are not among its values

* nbd-server: struct NbdServerOptions, no implied key.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 include/qemu/help_option.h           |  11 ++
 include/qemu/option.h                |   2 +-
 qapi/qobject-input-visitor.c         |   2 +-
 storage-daemon/qemu-storage-daemon.c |   2 +-
 tests/test-keyval.c                  | 184 ++++++++++++++++++---------
 util/keyval.c                        |  63 +++++++--
 6 files changed, 186 insertions(+), 78 deletions(-)

diff --git a/include/qemu/help_option.h b/include/qemu/help_option.h
index 328d2a89fd..ca6389a154 100644
--- a/include/qemu/help_option.h
+++ b/include/qemu/help_option.h
@@ -19,4 +19,15 @@ static inline bool is_help_option(const char *s)
     return !strcmp(s, "?") || !strcmp(s, "help");
 }
 
+static inline int starts_with_help_option(const char *s)
+{
+    if (*s == '?') {
+        return 1;
+    }
+    if (g_str_has_prefix(s, "help")) {
+        return 4;
+    }
+    return 0;
+}
+
 #endif
diff --git a/include/qemu/option.h b/include/qemu/option.h
index 05e8a15c73..ac69352e0e 100644
--- a/include/qemu/option.h
+++ b/include/qemu/option.h
@@ -149,6 +149,6 @@ void qemu_opts_free(QemuOptsList *list);
 QemuOptsList *qemu_opts_append(QemuOptsList *dst, QemuOptsList *list);
 
 QDict *keyval_parse(const char *params, const char *implied_key,
-                    Error **errp);
+                    bool *help, Error **errp);
 
 #endif
diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c
index f918a05e5f..7b184b50a7 100644
--- a/qapi/qobject-input-visitor.c
+++ b/qapi/qobject-input-visitor.c
@@ -757,7 +757,7 @@ Visitor *qobject_input_visitor_new_str(const char *str,
         assert(args);
         v = qobject_input_visitor_new(QOBJECT(args));
     } else {
-        args = keyval_parse(str, implied_key, errp);
+        args = keyval_parse(str, implied_key, NULL, errp);
         if (!args) {
             return NULL;
         }
diff --git a/storage-daemon/qemu-storage-daemon.c b/storage-daemon/qemu-storage-daemon.c
index 1ae1cda481..6f0e0cfb36 100644
--- a/storage-daemon/qemu-storage-daemon.c
+++ b/storage-daemon/qemu-storage-daemon.c
@@ -278,7 +278,7 @@ static void process_options(int argc, char *argv[])
                 }
                 qemu_opts_del(opts);
 
-                args = keyval_parse(optarg, "qom-type", &error_fatal);
+                args = keyval_parse(optarg, "qom-type", NULL, &error_fatal);
                 user_creatable_add_dict(args, true, &error_fatal);
                 qobject_unref(args);
                 break;
diff --git a/tests/test-keyval.c b/tests/test-keyval.c
index 04c62cf045..7b459900af 100644
--- a/tests/test-keyval.c
+++ b/tests/test-keyval.c
@@ -1,3 +1,4 @@
+
 /*
  * Unit tests for parsing of KEY=VALUE,... strings
  *
@@ -27,27 +28,28 @@ static void test_keyval_parse(void)
     QDict *qdict, *sub_qdict;
     char long_key[129];
     char *params;
+    bool help;
 
     /* Nothing */
-    qdict = keyval_parse("", NULL, &error_abort);
+    qdict = keyval_parse("", NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 0);
     qobject_unref(qdict);
 
     /* Empty key (qemu_opts_parse() accepts this) */
-    qdict = keyval_parse("=val", NULL, &err);
+    qdict = keyval_parse("=val", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Empty key fragment */
-    qdict = keyval_parse(".", NULL, &err);
+    qdict = keyval_parse(".", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
-    qdict = keyval_parse("key.", NULL, &err);
+    qdict = keyval_parse("key.", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Invalid non-empty key (qemu_opts_parse() doesn't care) */
-    qdict = keyval_parse("7up=val", NULL, &err);
+    qdict = keyval_parse("7up=val", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
@@ -56,25 +58,25 @@ static void test_keyval_parse(void)
     long_key[127] = 'z';
     long_key[128] = 0;
     params = g_strdup_printf("k.%s=v", long_key);
-    qdict = keyval_parse(params + 2, NULL, &err);
+    qdict = keyval_parse(params + 2, NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Overlong key fragment */
-    qdict = keyval_parse(params, NULL, &err);
+    qdict = keyval_parse(params, NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
     g_free(params);
 
     /* Long key (qemu_opts_parse() accepts and truncates silently) */
     params = g_strdup_printf("k.%s=v", long_key + 1);
-    qdict = keyval_parse(params + 2, NULL, &error_abort);
+    qdict = keyval_parse(params + 2, NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 1);
     g_assert_cmpstr(qdict_get_try_str(qdict, long_key + 1), ==, "v");
     qobject_unref(qdict);
 
     /* Long key fragment */
-    qdict = keyval_parse(params, NULL, &error_abort);
+    qdict = keyval_parse(params, NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 1);
     sub_qdict = qdict_get_qdict(qdict, "k");
     g_assert(sub_qdict);
@@ -84,25 +86,25 @@ static void test_keyval_parse(void)
     g_free(params);
 
     /* Crap after valid key */
-    qdict = keyval_parse("key[0]=val", NULL, &err);
+    qdict = keyval_parse("key[0]=val", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Multiple keys, last one wins */
-    qdict = keyval_parse("a=1,b=2,,x,a=3", NULL, &error_abort);
+    qdict = keyval_parse("a=1,b=2,,x,a=3", NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 2);
     g_assert_cmpstr(qdict_get_try_str(qdict, "a"), ==, "3");
     g_assert_cmpstr(qdict_get_try_str(qdict, "b"), ==, "2,x");
     qobject_unref(qdict);
 
     /* Even when it doesn't in qemu_opts_parse() */
-    qdict = keyval_parse("id=foo,id=bar", NULL, &error_abort);
+    qdict = keyval_parse("id=foo,id=bar", NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 1);
     g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "bar");
     qobject_unref(qdict);
 
     /* Dotted keys */
-    qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, &error_abort);
+    qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 2);
     sub_qdict = qdict_get_qdict(qdict, "a");
     g_assert(sub_qdict);
@@ -115,48 +117,48 @@ static void test_keyval_parse(void)
     qobject_unref(qdict);
 
     /* Inconsistent dotted keys */
-    qdict = keyval_parse("a.b=1,a=2", NULL, &err);
+    qdict = keyval_parse("a.b=1,a=2", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
-    qdict = keyval_parse("a.b=1,a.b.c=2", NULL, &err);
+    qdict = keyval_parse("a.b=1,a.b.c=2", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Trailing comma is ignored */
-    qdict = keyval_parse("x=y,", NULL, &error_abort);
+    qdict = keyval_parse("x=y,", NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 1);
     g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, "y");
     qobject_unref(qdict);
 
     /* Except when it isn't */
-    qdict = keyval_parse(",", NULL, &err);
+    qdict = keyval_parse(",", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Value containing ,id= not misinterpreted as qemu_opts_parse() does */
-    qdict = keyval_parse("x=,,id=bar", NULL, &error_abort);
+    qdict = keyval_parse("x=,,id=bar", NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 1);
     g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, ",id=bar");
     qobject_unref(qdict);
 
     /* Anti-social ID is left to caller (qemu_opts_parse() rejects it) */
-    qdict = keyval_parse("id=666", NULL, &error_abort);
+    qdict = keyval_parse("id=666", NULL, NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 1);
     g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "666");
     qobject_unref(qdict);
 
     /* Implied value not supported (unlike qemu_opts_parse()) */
-    qdict = keyval_parse("an,noaus,noaus=", NULL, &err);
+    qdict = keyval_parse("an,noaus,noaus=", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Implied value, key "no" (qemu_opts_parse(): negated empty key) */
-    qdict = keyval_parse("no", NULL, &err);
+    qdict = keyval_parse("no", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Implied key */
-    qdict = keyval_parse("an,aus=off,noaus=", "implied", &error_abort);
+    qdict = keyval_parse("an,aus=off,noaus=", "implied", NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 3);
     g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "an");
     g_assert_cmpstr(qdict_get_try_str(qdict, "aus"), ==, "off");
@@ -164,7 +166,7 @@ static void test_keyval_parse(void)
     qobject_unref(qdict);
 
     /* Implied dotted key */
-    qdict = keyval_parse("val", "eins.zwei", &error_abort);
+    qdict = keyval_parse("val", "eins.zwei", NULL, &error_abort);
     g_assert_cmpuint(qdict_size(qdict), ==, 1);
     sub_qdict = qdict_get_qdict(qdict, "eins");
     g_assert(sub_qdict);
@@ -173,24 +175,81 @@ static void test_keyval_parse(void)
     qobject_unref(qdict);
 
     /* Implied key with empty value (qemu_opts_parse() accepts this) */
-    qdict = keyval_parse(",", "implied", &err);
+    qdict = keyval_parse(",", "implied", NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Likewise (qemu_opts_parse(): implied key with comma value) */
-    qdict = keyval_parse(",,,a=1", "implied", &err);
+    qdict = keyval_parse(",,,a=1", "implied", NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Implied key's value can't have comma (qemu_opts_parse(): it can) */
-    qdict = keyval_parse("val,,ue", "implied", &err);
+    qdict = keyval_parse("val,,ue", "implied", NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Empty key is not an implied key */
-    qdict = keyval_parse("=val", "implied", &err);
+    qdict = keyval_parse("=val", "implied", NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
+
+    /* "help" by itself, without implied key */
+    qdict = keyval_parse("help", NULL, &help, &error_abort);
+    g_assert_cmpuint(qdict_size(qdict), ==, 0);
+    g_assert(help);
+    qobject_unref(qdict);
+
+    /* "help" by itself, with implied key */
+    qdict = keyval_parse("help", "implied", &help, &error_abort);
+    g_assert_cmpuint(qdict_size(qdict), ==, 0);
+    g_assert(help);
+    qobject_unref(qdict);
+
+    /* "help" when no help is available, without implied key */
+    qdict = keyval_parse("help", NULL, NULL, &err);
+    error_free_or_abort(&err);
+    g_assert(!qdict);
+
+    /* "help" when no help is available, with implied key */
+    qdict = keyval_parse("help", "implied", NULL, &err);
+    error_free_or_abort(&err);
+    g_assert(!qdict);
+
+    /* Key "help" */
+    qdict = keyval_parse("help=on", NULL, &help, &error_abort);
+    g_assert_cmpuint(qdict_size(qdict), ==, 1);
+    g_assert_cmpstr(qdict_get_try_str(qdict, "help"), ==, "on");
+    g_assert(!help);
+    qobject_unref(qdict);
+
+    /* "help" followed by crap, without implied key */
+    qdict = keyval_parse("help.abc", NULL, &help, &err);
+    error_free_or_abort(&err);
+    g_assert(!qdict);
+
+    /* "help" followed by crap, with implied key */
+    qdict = keyval_parse("help.abc", "implied", &help, &err);
+    g_assert_cmpuint(qdict_size(qdict), ==, 1);
+    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "help.abc");
+    g_assert(!help);
+    qobject_unref(qdict);
+
+    /* "help" with other stuff, without implied key */
+    qdict = keyval_parse("number=42,help,foo=bar", NULL, &help, &error_abort);
+    g_assert_cmpuint(qdict_size(qdict), ==, 2);
+    g_assert_cmpstr(qdict_get_try_str(qdict, "number"), ==, "42");
+    g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
+    g_assert(help);
+    qobject_unref(qdict);
+
+    /* "help" with other stuff, with implied key */
+    qdict = keyval_parse("val,help,foo=bar", "implied", &help, &error_abort);
+    g_assert_cmpuint(qdict_size(qdict), ==, 2);
+    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "val");
+    g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
+    g_assert(help);
+    qobject_unref(qdict);
 }
 
 static void check_list012(QList *qlist)
@@ -215,26 +274,26 @@ static void test_keyval_parse_list(void)
     QDict *qdict, *sub_qdict;
 
     /* Root can't be a list */
-    qdict = keyval_parse("0=1", NULL, &err);
+    qdict = keyval_parse("0=1", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* List elements need not be in order */
-    qdict = keyval_parse("list.0=null,list.2=zwei,list.1=eins",
-                         NULL, &error_abort);
+    qdict = keyval_parse("list.0=null,list.2=zwei,list.1=eins", NULL, NULL,
+                         &error_abort);
     g_assert_cmpint(qdict_size(qdict), ==, 1);
     check_list012(qdict_get_qlist(qdict, "list"));
     qobject_unref(qdict);
 
     /* Multiple indexes, last one wins */
     qdict = keyval_parse("list.1=goner,list.0=null,list.01=eins,list.2=zwei",
-                         NULL, &error_abort);
+                         NULL, NULL, &error_abort);
     g_assert_cmpint(qdict_size(qdict), ==, 1);
     check_list012(qdict_get_qlist(qdict, "list"));
     qobject_unref(qdict);
 
     /* List at deeper nesting */
-    qdict = keyval_parse("a.list.1=eins,a.list.00=null,a.list.2=zwei",
+    qdict = keyval_parse("a.list.1=eins,a.list.00=null,a.list.2=zwei", NULL,
                          NULL, &error_abort);
     g_assert_cmpint(qdict_size(qdict), ==, 1);
     sub_qdict = qdict_get_qdict(qdict, "a");
@@ -243,18 +302,19 @@ static void test_keyval_parse_list(void)
     qobject_unref(qdict);
 
     /* Inconsistent dotted keys: both list and dictionary */
-    qdict = keyval_parse("a.b.c=1,a.b.0=2", NULL, &err);
+    qdict = keyval_parse("a.b.c=1,a.b.0=2", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
-    qdict = keyval_parse("a.0.c=1,a.b.c=2", NULL, &err);
+    qdict = keyval_parse("a.0.c=1,a.b.c=2", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 
     /* Missing list indexes */
-    qdict = keyval_parse("list.1=lonely", NULL, &err);
+    qdict = keyval_parse("list.1=lonely", NULL, NULL, &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
-    qdict = keyval_parse("list.0=null,list.2=eins,list.02=zwei", NULL, &err);
+    qdict = keyval_parse("list.0=null,list.2=eins,list.02=zwei", NULL, NULL,
+                         &err);
     error_free_or_abort(&err);
     g_assert(!qdict);
 }
@@ -266,7 +326,7 @@ static void test_keyval_visit_bool(void)
     QDict *qdict;
     bool b;
 
-    qdict = keyval_parse("bool1=on,bool2=off", NULL, &error_abort);
+    qdict = keyval_parse("bool1=on,bool2=off", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -278,7 +338,7 @@ static void test_keyval_visit_bool(void)
     visit_end_struct(v, NULL);
     visit_free(v);
 
-    qdict = keyval_parse("bool1=offer", NULL, &error_abort);
+    qdict = keyval_parse("bool1=offer", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -296,7 +356,7 @@ static void test_keyval_visit_number(void)
     uint64_t u;
 
     /* Lower limit zero */
-    qdict = keyval_parse("number1=0", NULL, &error_abort);
+    qdict = keyval_parse("number1=0", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -307,7 +367,7 @@ static void test_keyval_visit_number(void)
     visit_free(v);
 
     /* Upper limit 2^64-1 */
-    qdict = keyval_parse("number1=18446744073709551615,number2=-1",
+    qdict = keyval_parse("number1=18446744073709551615,number2=-1", NULL,
                          NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
@@ -321,8 +381,8 @@ static void test_keyval_visit_number(void)
     visit_free(v);
 
     /* Above upper limit */
-    qdict = keyval_parse("number1=18446744073709551616",
-                         NULL, &error_abort);
+    qdict = keyval_parse("number1=18446744073709551616", NULL, NULL,
+                         &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -332,8 +392,8 @@ static void test_keyval_visit_number(void)
     visit_free(v);
 
     /* Below lower limit */
-    qdict = keyval_parse("number1=-18446744073709551616",
-                         NULL, &error_abort);
+    qdict = keyval_parse("number1=-18446744073709551616", NULL, NULL,
+                         &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -343,8 +403,7 @@ static void test_keyval_visit_number(void)
     visit_free(v);
 
     /* Hex and octal */
-    qdict = keyval_parse("number1=0x2a,number2=052",
-                         NULL, &error_abort);
+    qdict = keyval_parse("number1=0x2a,number2=052", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -357,8 +416,7 @@ static void test_keyval_visit_number(void)
     visit_free(v);
 
     /* Trailing crap */
-    qdict = keyval_parse("number1=3.14,number2=08",
-                         NULL, &error_abort);
+    qdict = keyval_parse("number1=3.14,number2=08", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -378,7 +436,7 @@ static void test_keyval_visit_size(void)
     uint64_t sz;
 
     /* Lower limit zero */
-    qdict = keyval_parse("sz1=0", NULL, &error_abort);
+    qdict = keyval_parse("sz1=0", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -394,7 +452,7 @@ static void test_keyval_visit_size(void)
     qdict = keyval_parse("sz1=9007199254740991,"
                          "sz2=9007199254740992,"
                          "sz3=9007199254740993",
-                         NULL, &error_abort);
+                         NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -411,7 +469,7 @@ static void test_keyval_visit_size(void)
     /* Close to signed upper limit 0x7ffffffffffffc00 (53 msbs set) */
     qdict = keyval_parse("sz1=9223372036854774784," /* 7ffffffffffffc00 */
                          "sz2=9223372036854775295", /* 7ffffffffffffdff */
-                         NULL, &error_abort);
+                         NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -426,7 +484,7 @@ static void test_keyval_visit_size(void)
     /* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */
     qdict = keyval_parse("sz1=18446744073709549568," /* fffffffffffff800 */
                          "sz2=18446744073709550591", /* fffffffffffffbff */
-                         NULL, &error_abort);
+                         NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -441,7 +499,7 @@ static void test_keyval_visit_size(void)
     /* Beyond limits */
     qdict = keyval_parse("sz1=-1,"
                          "sz2=18446744073709550592", /* fffffffffffffc00 */
-                         NULL, &error_abort);
+                         NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -454,7 +512,7 @@ static void test_keyval_visit_size(void)
 
     /* Suffixes */
     qdict = keyval_parse("sz1=8b,sz2=1.5k,sz3=2M,sz4=0.1G,sz5=16777215T",
-                         NULL, &error_abort);
+                         NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -473,7 +531,7 @@ static void test_keyval_visit_size(void)
     visit_free(v);
 
     /* Beyond limit with suffix */
-    qdict = keyval_parse("sz1=16777216T", NULL, &error_abort);
+    qdict = keyval_parse("sz1=16777216T", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -483,7 +541,7 @@ static void test_keyval_visit_size(void)
     visit_free(v);
 
     /* Trailing crap */
-    qdict = keyval_parse("sz1=0Z,sz2=16Gi", NULL, &error_abort);
+    qdict = keyval_parse("sz1=0Z,sz2=16Gi", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -502,7 +560,7 @@ static void test_keyval_visit_dict(void)
     QDict *qdict;
     int64_t i;
 
-    qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, &error_abort);
+    qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -520,7 +578,7 @@ static void test_keyval_visit_dict(void)
     visit_end_struct(v, NULL);
     visit_free(v);
 
-    qdict = keyval_parse("a.b=", NULL, &error_abort);
+    qdict = keyval_parse("a.b=", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -542,7 +600,7 @@ static void test_keyval_visit_list(void)
     QDict *qdict;
     char *s;
 
-    qdict = keyval_parse("a.0=,a.1=I,a.2.0=II", NULL, &error_abort);
+    qdict = keyval_parse("a.0=,a.1=I,a.2.0=II", NULL, NULL, &error_abort);
     /* TODO empty list */
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
@@ -566,7 +624,7 @@ static void test_keyval_visit_list(void)
     visit_end_struct(v, NULL);
     visit_free(v);
 
-    qdict = keyval_parse("a.0=,b.0.0=head", NULL, &error_abort);
+    qdict = keyval_parse("a.0=,b.0.0=head", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -595,7 +653,7 @@ static void test_keyval_visit_optional(void)
     bool present;
     int64_t i;
 
-    qdict = keyval_parse("a.b=1", NULL, &error_abort);
+    qdict = keyval_parse("a.b=1", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -631,7 +689,7 @@ static void test_keyval_visit_alternate(void)
      * the string variant if there is one, else an error.
      * TODO make it work for unambiguous cases like AltEnumBool below
      */
-    qdict = keyval_parse("a=1,b=2,c=on", NULL, &error_abort);
+    qdict = keyval_parse("a=1,b=2,c=on", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
@@ -655,7 +713,7 @@ static void test_keyval_visit_any(void)
     QList *qlist;
     QString *qstr;
 
-    qdict = keyval_parse("a.0=null,a.1=1", NULL, &error_abort);
+    qdict = keyval_parse("a.0=null,a.1=1", NULL, NULL, &error_abort);
     v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
     qobject_unref(qdict);
     visit_start_struct(v, NULL, NULL, 0, &error_abort);
diff --git a/util/keyval.c b/util/keyval.c
index 8f33a36a7c..14fdd364fa 100644
--- a/util/keyval.c
+++ b/util/keyval.c
@@ -14,10 +14,11 @@
  * KEY=VALUE,... syntax:
  *
  *   key-vals     = [ key-val { ',' key-val } [ ',' ] ]
- *   key-val      = key '=' val
+ *   key-val      = key '=' val | help
  *   key          = key-fragment { '.' key-fragment }
  *   key-fragment = / [^=,.]+ /
  *   val          = { / [^,]+ / | ',,' }
+ *   help         = 'help | '?'
  *
  * Semantics defined by reduction to JSON:
  *
@@ -54,6 +55,9 @@
  *
  * The length of any key-fragment must be between 1 and 127.
  *
+ * If any key-val is help, the object is to be treated as a help
+ * request.
+ *
  * Design flaw: there is no way to denote an empty array or non-root
  * object.  While interpreting "key absent" as empty seems natural
  * (removing a key-val from the input string removes the member when
@@ -75,7 +79,7 @@
  *
  *   key-vals     = [ key-val-1st { ',' key-val } [ ',' ] ]
  *   key-val-1st  = val-no-key | key-val
- *   val-no-key   = / [^=,]+ /
+ *   val-no-key   = / [^=,]+ / - help
  *
  * where val-no-key is syntactic sugar for implied-key=val-no-key.
  *
@@ -89,6 +93,7 @@
 #include "qapi/qmp/qlist.h"
 #include "qapi/qmp/qstring.h"
 #include "qemu/cutils.h"
+#include "qemu/help_option.h"
 #include "qemu/option.h"
 
 /*
@@ -162,15 +167,20 @@ static QObject *keyval_parse_put(QDict *cur,
 }
 
 /*
- * Parse one KEY=VALUE from @params, store result in @qdict.
+ * Parse one parameter from @params.
+ *
+ * If we're looking at KEY=VALUE, store result in @qdict.
  * The first fragment of KEY applies to @qdict.  Subsequent fragments
  * apply to nested QDicts, which are created on demand.  @implied_key
  * is as in keyval_parse().
- * On success, return a pointer to the next KEY=VALUE, or else to '\0'.
+ *
+ * If we're looking at "help" or "?", set *help to true.
+ *
+ * On success, return a pointer to the next parameter, or else to '\0'.
  * On failure, return NULL.
  */
 static const char *keyval_parse_one(QDict *qdict, const char *params,
-                                    const char *implied_key,
+                                    const char *implied_key, bool *help,
                                     Error **errp)
 {
     const char *key, *key_end, *val_end, *s, *end;
@@ -184,11 +194,21 @@ static const char *keyval_parse_one(QDict *qdict, const char *params,
     key = params;
     val_end = NULL;
     len = strcspn(params, "=,");
-    if (implied_key && len && key[len] != '=') {
-        /* Desugar implied key */
-        key = implied_key;
-        val_end = params + len;
-        len = strlen(implied_key);
+    if (len && key[len] != '=') {
+        if (starts_with_help_option(key) == len) {
+            *help = true;
+            s = key + len;
+            if (*s == ',') {
+                s++;
+            }
+            return s;
+        }
+        if (implied_key) {
+            /* Desugar implied key */
+            key = implied_key;
+            val_end = params + len;
+            len = strlen(implied_key);
+        }
     }
     key_end = key + len;
 
@@ -398,21 +418,32 @@ static QObject *keyval_listify(QDict *cur, GSList *key_of_cur, Error **errp)
 
 /*
  * Parse @params in QEMU's traditional KEY=VALUE,... syntax.
+ *
  * If @implied_key, the first KEY= can be omitted.  @implied_key is
  * implied then, and VALUE can't be empty or contain ',' or '='.
+ *
+ * A parameter "help" or "?" without a value isn't added to the
+ * resulting dictionary, but instead is interpreted as help request.
+ * All other options are parsed and returned normally so that context
+ * specific help can be printed.
+ *
+ * If @p_help is not NULL, store whether help is requested there.
+ * If @p_help is NULL and help is requested, fail.
+ *
  * On success, return a dictionary of the parsed keys and values.
  * On failure, store an error through @errp and return NULL.
  */
 QDict *keyval_parse(const char *params, const char *implied_key,
-                    Error **errp)
+                    bool *p_help, Error **errp)
 {
     QDict *qdict = qdict_new();
     QObject *listified;
     const char *s;
+    bool help = false;
 
     s = params;
     while (*s) {
-        s = keyval_parse_one(qdict, s, implied_key, errp);
+        s = keyval_parse_one(qdict, s, implied_key, &help, errp);
         if (!s) {
             qobject_unref(qdict);
             return NULL;
@@ -420,6 +451,14 @@ QDict *keyval_parse(const char *params, const char *implied_key,
         implied_key = NULL;
     }
 
+    if (p_help) {
+        *p_help = help;
+    } else if (help) {
+        error_setg(errp, "Help is not available for this option");
+        qobject_unref(qdict);
+        return NULL;
+    }
+
     listified = keyval_listify(qdict, NULL, errp);
     if (!listified) {
         qobject_unref(qdict);
-- 
2.26.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v4 5/7] qom: Factor out helpers from user_creatable_print_help()
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
                   ` (3 preceding siblings ...)
  2020-10-11  7:35 ` [PATCH v4 4/7] keyval: Parse help options Markus Armbruster
@ 2020-10-11  7:35 ` Markus Armbruster
  2020-10-11  7:35 ` [PATCH v4 6/7] qom: Add user_creatable_print_help_from_qdict() Markus Armbruster
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

From: Kevin Wolf <kwolf@redhat.com>

This creates separate helper functions for printing a list of user
creatable object types and for printing a list of properties of a given
type. This will allow using these parts without having a QemuOpts.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 qom/object_interfaces.c | 90 ++++++++++++++++++++++++-----------------
 1 file changed, 52 insertions(+), 38 deletions(-)

diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index e8e1523960..3fd1da157e 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -214,52 +214,66 @@ char *object_property_help(const char *name, const char *type,
     return g_string_free(str, false);
 }
 
-bool user_creatable_print_help(const char *type, QemuOpts *opts)
+static void user_creatable_print_types(void)
+{
+    GSList *l, *list;
+
+    printf("List of user creatable objects:\n");
+    list = object_class_get_list_sorted(TYPE_USER_CREATABLE, false);
+    for (l = list; l != NULL; l = l->next) {
+        ObjectClass *oc = OBJECT_CLASS(l->data);
+        printf("  %s\n", object_class_get_name(oc));
+    }
+    g_slist_free(list);
+}
+
+static bool user_creatable_print_type_properites(const char *type)
 {
     ObjectClass *klass;
+    ObjectPropertyIterator iter;
+    ObjectProperty *prop;
+    GPtrArray *array;
+    int i;
 
-    if (is_help_option(type)) {
-        GSList *l, *list;
+    klass = object_class_by_name(type);
+    if (!klass) {
+        return false;
+    }
 
-        printf("List of user creatable objects:\n");
-        list = object_class_get_list_sorted(TYPE_USER_CREATABLE, false);
-        for (l = list; l != NULL; l = l->next) {
-            ObjectClass *oc = OBJECT_CLASS(l->data);
-            printf("  %s\n", object_class_get_name(oc));
+    array = g_ptr_array_new();
+    object_class_property_iter_init(&iter, klass);
+    while ((prop = object_property_iter_next(&iter))) {
+        if (!prop->set) {
+            continue;
         }
-        g_slist_free(list);
+
+        g_ptr_array_add(array,
+                        object_property_help(prop->name, prop->type,
+                                             prop->defval, prop->description));
+    }
+    g_ptr_array_sort(array, (GCompareFunc)qemu_pstrcmp0);
+    if (array->len > 0) {
+        printf("%s options:\n", type);
+    } else {
+        printf("There are no options for %s.\n", type);
+    }
+    for (i = 0; i < array->len; i++) {
+        printf("%s\n", (char *)array->pdata[i]);
+    }
+    g_ptr_array_set_free_func(array, g_free);
+    g_ptr_array_free(array, true);
+    return true;
+}
+
+bool user_creatable_print_help(const char *type, QemuOpts *opts)
+{
+    if (is_help_option(type)) {
+        user_creatable_print_types();
         return true;
     }
 
-    klass = object_class_by_name(type);
-    if (klass && qemu_opt_has_help_opt(opts)) {
-        ObjectPropertyIterator iter;
-        ObjectProperty *prop;
-        GPtrArray *array = g_ptr_array_new();
-        int i;
-
-        object_class_property_iter_init(&iter, klass);
-        while ((prop = object_property_iter_next(&iter))) {
-            if (!prop->set) {
-                continue;
-            }
-
-            g_ptr_array_add(array,
-                            object_property_help(prop->name, prop->type,
-                                                 prop->defval, prop->description));
-        }
-        g_ptr_array_sort(array, (GCompareFunc)qemu_pstrcmp0);
-        if (array->len > 0) {
-            printf("%s options:\n", type);
-        } else {
-            printf("There are no options for %s.\n", type);
-        }
-        for (i = 0; i < array->len; i++) {
-            printf("%s\n", (char *)array->pdata[i]);
-        }
-        g_ptr_array_set_free_func(array, g_free);
-        g_ptr_array_free(array, true);
-        return true;
+    if (qemu_opt_has_help_opt(opts)) {
+        return user_creatable_print_type_properites(type);
     }
 
     return false;
-- 
2.26.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v4 6/7] qom: Add user_creatable_print_help_from_qdict()
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
                   ` (4 preceding siblings ...)
  2020-10-11  7:35 ` [PATCH v4 5/7] qom: Factor out helpers from user_creatable_print_help() Markus Armbruster
@ 2020-10-11  7:35 ` Markus Armbruster
  2020-10-11  7:35 ` [PATCH v4 7/7] qemu-storage-daemon: Remove QemuOpts from --object parser Markus Armbruster
  2020-10-12 14:39 ` [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Kevin Wolf
  7 siblings, 0 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

From: Kevin Wolf <kwolf@redhat.com>

This adds a function that, given a QDict of non-help options, prints
help for user creatable objects.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 include/qom/object_interfaces.h | 21 ++++++++++++++++++---
 qom/object_interfaces.c         |  9 +++++++++
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
index f118fb516b..07d5cc8832 100644
--- a/include/qom/object_interfaces.h
+++ b/include/qom/object_interfaces.h
@@ -154,13 +154,28 @@ int user_creatable_add_opts_foreach(void *opaque,
  * @type: the QOM type to be added
  * @opts: options to create
  *
- * Prints help if requested in @opts.
+ * Prints help if requested in @type or @opts. Note that if @type is neither
+ * "help"/"?" nor a valid user creatable type, no help will be printed
+ * regardless of @opts.
  *
- * Returns: true if @opts contained a help option and help was printed, false
- * if no help option was found.
+ * Returns: true if a help option was found and help was printed, false
+ * otherwise.
  */
 bool user_creatable_print_help(const char *type, QemuOpts *opts);
 
+/**
+ * user_creatable_print_help_from_qdict:
+ * @args: options to create
+ *
+ * Prints help considering the other options given in @args (if "qom-type" is
+ * given and valid, print properties for the type, otherwise print valid types)
+ *
+ * In contrast to user_creatable_print_help(), this function can't return that
+ * no help was requested. It should only be called if we know that help is
+ * requested and it will always print some help.
+ */
+void user_creatable_print_help_from_qdict(QDict *args);
+
 /**
  * user_creatable_del:
  * @id: the unique ID for the object
diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index 3fd1da157e..ed896fe764 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -279,6 +279,15 @@ bool user_creatable_print_help(const char *type, QemuOpts *opts)
     return false;
 }
 
+void user_creatable_print_help_from_qdict(QDict *args)
+{
+    const char *type = qdict_get_try_str(args, "qom-type");
+
+    if (!type || !user_creatable_print_type_properites(type)) {
+        user_creatable_print_types();
+    }
+}
+
 bool user_creatable_del(const char *id, Error **errp)
 {
     Object *container;
-- 
2.26.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH v4 7/7] qemu-storage-daemon: Remove QemuOpts from --object parser
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
                   ` (5 preceding siblings ...)
  2020-10-11  7:35 ` [PATCH v4 6/7] qom: Add user_creatable_print_help_from_qdict() Markus Armbruster
@ 2020-10-11  7:35 ` Markus Armbruster
  2020-10-12 14:39 ` [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Kevin Wolf
  7 siblings, 0 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-11  7:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, qemu-block

From: Kevin Wolf <kwolf@redhat.com>

The command line parser for --object parses the input twice: Once into
QemuOpts just for detecting help options, and then again into a QDict
using the keyval parser for actually creating the object.

Now that the keyval parser can also detect help options, we can simplify
this and remove the QemuOpts part.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 storage-daemon/qemu-storage-daemon.c | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/storage-daemon/qemu-storage-daemon.c b/storage-daemon/qemu-storage-daemon.c
index 6f0e0cfb36..e419ba9f19 100644
--- a/storage-daemon/qemu-storage-daemon.c
+++ b/storage-daemon/qemu-storage-daemon.c
@@ -264,21 +264,14 @@ static void process_options(int argc, char *argv[])
             }
         case OPTION_OBJECT:
             {
-                QemuOpts *opts;
-                const char *type;
                 QDict *args;
+                bool help;
 
-                /* FIXME The keyval parser rejects 'help' arguments, so we must
-                 * unconditionall try QemuOpts first. */
-                opts = qemu_opts_parse(&qemu_object_opts,
-                                       optarg, true, &error_fatal);
-                type = qemu_opt_get(opts, "qom-type");
-                if (type && user_creatable_print_help(type, opts)) {
+                args = keyval_parse(optarg, "qom-type", &help, &error_fatal);
+                if (help) {
+                    user_creatable_print_help_from_qdict(args);
                     exit(EXIT_SUCCESS);
                 }
-                qemu_opts_del(opts);
-
-                args = keyval_parse(optarg, "qom-type", NULL, &error_fatal);
                 user_creatable_add_dict(args, true, &error_fatal);
                 qobject_unref(args);
                 break;
-- 
2.26.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 1/7] keyval: Fix and clarify grammar
  2020-10-11  7:34 ` [PATCH v4 1/7] keyval: Fix and clarify grammar Markus Armbruster
@ 2020-10-12 11:43   ` Eric Blake
  2020-10-19  9:03     ` Markus Armbruster
  0 siblings, 1 reply; 18+ messages in thread
From: Eric Blake @ 2020-10-12 11:43 UTC (permalink / raw)
  To: Markus Armbruster, qemu-devel; +Cc: kwolf, qemu-block

On 10/11/20 2:34 AM, Markus Armbruster wrote:
> The grammar has a few issues:
> 
> * key-fragment = / [^=,.]* /
> 
>    Prose restricts key fragments: they "must be valid QAPI names or
>    consist only of decimal digits".  Technically, '' consists only of
>    decimal digits.  The code rejects that.  Fix the grammar.
> 
> * val          = { / [^,]* / | ',,' }
> 
>    Use + instead of *.  Accepts the same language.
> 
> * val-no-key   = / [^=,]* /
> 
>    The code rejects an empty value.  Fix the grammar.
> 
> * Section "Additional syntax for use with an implied key" is
>    confusing.  Rewrite it.
> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>   util/keyval.c | 16 ++++++++++------
>   1 file changed, 10 insertions(+), 6 deletions(-)
> 
> diff --git a/util/keyval.c b/util/keyval.c
> index 13def4af54..82d8497c71 100644
> --- a/util/keyval.c
> +++ b/util/keyval.c
> @@ -16,8 +16,8 @@
>    *   key-vals     = [ key-val { ',' key-val } [ ',' ] ]
>    *   key-val      = key '=' val
>    *   key          = key-fragment { '.' key-fragment }
> - *   key-fragment = / [^=,.]* /
> - *   val          = { / [^,]* / | ',,' }
> + *   key-fragment = / [^=,.]+ /

This requires a non-empty string.  Good (we don't allow an empty key).

> + *   val          = { / [^,]+ / | ',,' }

I agree that this has no real change.  Previously, you allowed zero or 
more repetitions of a regex that could produce zero characters; now, 
each outer repetition must make progress.

>    *
>    * Semantics defined by reduction to JSON:
>    *
> @@ -71,12 +71,16 @@
>    * Awkward.  Note that we carefully restrict alternate types to avoid
>    * similar ambiguity.
>    *
> - * Additional syntax for use with an implied key:
> + * Alternative syntax for use with an implied key:
>    *
> - *   key-vals-ik  = val-no-key [ ',' key-vals ]
> - *   val-no-key   = / [^=,]* /
> + *   key-vals     = [ key-val-1st { ',' key-val } [ ',' ] ]
> + *   key-val-1st  = val-no-key | key-val
> + *   val-no-key   = / [^=,]+ /
>    *
> - * where no-key is syntactic sugar for implied-key=val-no-key.
> + * where val-no-key is syntactic sugar for implied-key=val-no-key.
> + *
> + * Note that you can't use the sugared form when the value contains
> + * '=' or ','.

Nor can you use the sugared form when the value is intended to be empty 
(although this may be academic, as your other patches enumerate the list 
of clients, and none of them seem to allow an empty value even when 
desugared).

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 2/7] test-keyval: Demonstrate misparse of ',' with implied key
  2020-10-11  7:35 ` [PATCH v4 2/7] test-keyval: Demonstrate misparse of ', ' with implied key Markus Armbruster
@ 2020-10-12 11:44   ` Eric Blake
  0 siblings, 0 replies; 18+ messages in thread
From: Eric Blake @ 2020-10-12 11:44 UTC (permalink / raw)
  To: Markus Armbruster, qemu-devel; +Cc: kwolf, qemu-block

On 10/11/20 2:35 AM, Markus Armbruster wrote:
> Add a test for "val,,ue" with implied key.  Documentation says this
> should parse as implied key with value "val", then fail.  The code
> parses it as implied key with value "val,ue", then succeeds.  The next
> commit will fix it.
> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>   tests/test-keyval.c | 7 +++++++
>   1 file changed, 7 insertions(+)

Reviewed-by: Eric Blake <eblake@redhat.com>

> 
> diff --git a/tests/test-keyval.c b/tests/test-keyval.c
> index e331a84149..f02bdf7029 100644
> --- a/tests/test-keyval.c
> +++ b/tests/test-keyval.c
> @@ -182,6 +182,13 @@ static void test_keyval_parse(void)
>       error_free_or_abort(&err);
>       g_assert(!qdict);
>   
> +    /* Implied key's value can't have comma (qemu_opts_parse(): it can) */
> +    /* BUG: it can */
> +    qdict = keyval_parse("val,,ue", "implied", &error_abort);
> +    g_assert_cmpuint(qdict_size(qdict), ==, 1);
> +    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "val,ue");
> +    qobject_unref(qdict);
> +
>       /* Empty key is not an implied key */
>       qdict = keyval_parse("=val", "implied", &err);
>       error_free_or_abort(&err);
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 3/7] keyval: Fix parsing of ', ' in value of implied key
  2020-10-11  7:35 ` [PATCH v4 3/7] keyval: Fix parsing of ',' in value of " Markus Armbruster
@ 2020-10-12 11:46   ` Eric Blake
  0 siblings, 0 replies; 18+ messages in thread
From: Eric Blake @ 2020-10-12 11:46 UTC (permalink / raw)
  To: Markus Armbruster, qemu-devel; +Cc: kwolf, qemu-block

On 10/11/20 2:35 AM, Markus Armbruster wrote:
> The previous commit demonstrated documentation and code disagree on
> parsing of ',' in the value of an implied key.  Fix the code to match
> the documentation.
> 
> This breaks uses of keyval_parse() that pass an implied key and accept
> a value containing ','.  None of the existing uses does:
> 
> * audiodev: implied key "driver" is enum AudiodevDriver, none of the
>    values contains ','
> 
> * display: implied key "type" is enum DisplayType, none of the values
>    contains ','
> 
> * blockdev: implied key "driver is enum BlockdevDriver, none of the
>    values contains ','
> 
> * export: implied key "type" is enum BlockExportType, none of the
>    values contains ','
> 
> * monitor: implied key "mode" is enum MonitorMode, none of the values
>    contains ','
> 
> * nbd-server: no implied key.
> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>   tests/test-keyval.c |  8 +++-----
>   util/keyval.c       | 28 +++++++++++++++++-----------
>   2 files changed, 20 insertions(+), 16 deletions(-)
> 

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 4/7] keyval: Parse help options
  2020-10-11  7:35 ` [PATCH v4 4/7] keyval: Parse help options Markus Armbruster
@ 2020-10-12 11:51   ` Eric Blake
  2020-10-19  9:06     ` Markus Armbruster
  2020-10-12 14:35   ` Kevin Wolf
  1 sibling, 1 reply; 18+ messages in thread
From: Eric Blake @ 2020-10-12 11:51 UTC (permalink / raw)
  To: Markus Armbruster, qemu-devel; +Cc: kwolf, qemu-block

On 10/11/20 2:35 AM, Markus Armbruster wrote:
> From: Kevin Wolf <kwolf@redhat.com>
> 
> This adds a special meaning for 'help' and '?' as options to the keyval
> parser. Instead of being an error (because of a missing value) or a
> value for an implied key, they now request help, which is a new boolean
> output of the parser in addition to the QDict.
> 
> A new parameter 'p_help' is added to keyval_parse() that contains on
> return whether help was requested. If NULL is passed, requesting help
> results in an error and all other cases work like before.
> 

> +
> +    /* "help" by itself, without implied key */
> +    qdict = keyval_parse("help", NULL, &help, &error_abort);
> +    g_assert_cmpuint(qdict_size(qdict), ==, 0);
> +    g_assert(help);
> +    qobject_unref(qdict);
> +
> +    /* "help" by itself, with implied key */
> +    qdict = keyval_parse("help", "implied", &help, &error_abort);
> +    g_assert_cmpuint(qdict_size(qdict), ==, 0);
> +    g_assert(help);
> +    qobject_unref(qdict);
> +
> +    /* "help" when no help is available, without implied key */
> +    qdict = keyval_parse("help", NULL, NULL, &err);
> +    error_free_or_abort(&err);
> +    g_assert(!qdict);
> +
> +    /* "help" when no help is available, with implied key */
> +    qdict = keyval_parse("help", "implied", NULL, &err);
> +    error_free_or_abort(&err);
> +    g_assert(!qdict);
> +
> +    /* Key "help" */
> +    qdict = keyval_parse("help=on", NULL, &help, &error_abort);
> +    g_assert_cmpuint(qdict_size(qdict), ==, 1);
> +    g_assert_cmpstr(qdict_get_try_str(qdict, "help"), ==, "on");
> +    g_assert(!help);
> +    qobject_unref(qdict);
> +
> +    /* "help" followed by crap, without implied key */
> +    qdict = keyval_parse("help.abc", NULL, &help, &err);
> +    error_free_or_abort(&err);
> +    g_assert(!qdict);
> +
> +    /* "help" followed by crap, with implied key */
> +    qdict = keyval_parse("help.abc", "implied", &help, &err);
> +    g_assert_cmpuint(qdict_size(qdict), ==, 1);
> +    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "help.abc");
> +    g_assert(!help);
> +    qobject_unref(qdict);
> +
> +    /* "help" with other stuff, without implied key */
> +    qdict = keyval_parse("number=42,help,foo=bar", NULL, &help, &error_abort);
> +    g_assert_cmpuint(qdict_size(qdict), ==, 2);
> +    g_assert_cmpstr(qdict_get_try_str(qdict, "number"), ==, "42");
> +    g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
> +    g_assert(help);
> +    qobject_unref(qdict);
> +
> +    /* "help" with other stuff, with implied key */
> +    qdict = keyval_parse("val,help,foo=bar", "implied", &help, &error_abort);
> +    g_assert_cmpuint(qdict_size(qdict), ==, 2);
> +    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "val");
> +    g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
> +    g_assert(help);
> +    qobject_unref(qdict);

Is it worth checking that "helper" with implied key is a value, not help?

> +++ b/util/keyval.c
> @@ -14,10 +14,11 @@
>    * KEY=VALUE,... syntax:
>    *
>    *   key-vals     = [ key-val { ',' key-val } [ ',' ] ]
> - *   key-val      = key '=' val
> + *   key-val      = key '=' val | help
>    *   key          = key-fragment { '.' key-fragment }
>    *   key-fragment = / [^=,.]+ /
>    *   val          = { / [^,]+ / | ',,' }
> + *   help         = 'help | '?'

Missing '

Otherwise
Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 4/7] keyval: Parse help options
  2020-10-11  7:35 ` [PATCH v4 4/7] keyval: Parse help options Markus Armbruster
  2020-10-12 11:51   ` Eric Blake
@ 2020-10-12 14:35   ` Kevin Wolf
  2020-10-19  9:04     ` Markus Armbruster
  1 sibling, 1 reply; 18+ messages in thread
From: Kevin Wolf @ 2020-10-12 14:35 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, qemu-block

Am 11.10.2020 um 09:35 hat Markus Armbruster geschrieben:
> From: Kevin Wolf <kwolf@redhat.com>
> 
> This adds a special meaning for 'help' and '?' as options to the keyval
> parser. Instead of being an error (because of a missing value) or a
> value for an implied key, they now request help, which is a new boolean
> output of the parser in addition to the QDict.
> 
> A new parameter 'p_help' is added to keyval_parse() that contains on
> return whether help was requested. If NULL is passed, requesting help
> results in an error and all other cases work like before.
> 
> Turning previous error cases into help is a compatible extension. The
> behaviour potentially changes for implied keys: They could previously
> get 'help' as their value, which is now interpreted as requesting help.
> 
> This is not a problem in practice because 'help' and '?' are not a valid
> values for the implied key of any option parsed with keyval_parse():
> 
> * audiodev: union Audiodev, implied key "driver" is enum AudiodevDriver,
>   "help" and "?" are not among its values
> 
> * display: union DisplayOptions, implied key "type" is enum
>   DisplayType, "help" and "?" are not among its values
> 
> * blockdev: union BlockdevOptions, implied key "driver is enum
>   BlockdevDriver, "help" and "?" are not among its values
> 
> * export: union BlockExport, implied key "type" is enum BlockExportType,
>   "help" and "?" are not among its values
> 
> * monitor: struct MonitorOptions, implied key "mode" is enum MonitorMode,
>   "help" and "?" are not among its values
> 
> * nbd-server: struct NbdServerOptions, no implied key.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Signed-off-by: Markus Armbruster <armbru@redhat.com>

> diff --git a/tests/test-keyval.c b/tests/test-keyval.c
> index 04c62cf045..7b459900af 100644
> --- a/tests/test-keyval.c
> +++ b/tests/test-keyval.c
> @@ -1,3 +1,4 @@
> +
>  /*
>   * Unit tests for parsing of KEY=VALUE,... strings
>   *

This hunk looks unintentional.

Kevin



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object
  2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
                   ` (6 preceding siblings ...)
  2020-10-11  7:35 ` [PATCH v4 7/7] qemu-storage-daemon: Remove QemuOpts from --object parser Markus Armbruster
@ 2020-10-12 14:39 ` Kevin Wolf
  2020-10-19  9:08   ` Markus Armbruster
  7 siblings, 1 reply; 18+ messages in thread
From: Kevin Wolf @ 2020-10-12 14:39 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, qemu-block

Am 11.10.2020 um 09:34 hat Markus Armbruster geschrieben:
> This replaces the QemuOpts-based help code for --object in the storage
> daemon with code based on the keyval parser.
> 
> Review of v3 led me to preexisting issues.  Instead of posting my
> fixes separately, then working with Kevin to rebase his work on top of
> mine, I did the rebase myself and am posting it together with my fixes
> as v4.  Hope that's okay.
> 
> Note: to test qemu-storage-daemon --object help, you need Philippe's
> "[PATCH v3] hw/nvram: Always register FW_CFG_DATA_GENERATOR_INTERFACE"
> and its prerequisites (first 10 patches of Paolo's "[PULL 00/39] SCSI,
> qdev, qtest, meson patches for 2020-10-10"), so it doesn't crash with
> "missing interface 'fw_cfg-data-generator' for object 'tls-creds'".

Thanks, fixed up the typos in patch 4 and applied patches 1-4. I'm
taking patches 5-7 from my original series because you didn't add your
S-o-b (they are unchanged anyway, so no practical difference).

Kevin



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 1/7] keyval: Fix and clarify grammar
  2020-10-12 11:43   ` Eric Blake
@ 2020-10-19  9:03     ` Markus Armbruster
  0 siblings, 0 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-19  9:03 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, qemu-block

Eric Blake <eblake@redhat.com> writes:

> On 10/11/20 2:34 AM, Markus Armbruster wrote:
>> The grammar has a few issues:
>> * key-fragment = / [^=,.]* /
>>    Prose restricts key fragments: they "must be valid QAPI names or
>>    consist only of decimal digits".  Technically, '' consists only of
>>    decimal digits.  The code rejects that.  Fix the grammar.
>> * val          = { / [^,]* / | ',,' }
>>    Use + instead of *.  Accepts the same language.
>> * val-no-key   = / [^=,]* /
>>    The code rejects an empty value.  Fix the grammar.
>> * Section "Additional syntax for use with an implied key" is
>>    confusing.  Rewrite it.
>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>> ---
>>   util/keyval.c | 16 ++++++++++------
>>   1 file changed, 10 insertions(+), 6 deletions(-)
>> diff --git a/util/keyval.c b/util/keyval.c
>> index 13def4af54..82d8497c71 100644
>> --- a/util/keyval.c
>> +++ b/util/keyval.c
>> @@ -16,8 +16,8 @@
>>    *   key-vals     = [ key-val { ',' key-val } [ ',' ] ]
>>    *   key-val      = key '=' val
>>    *   key          = key-fragment { '.' key-fragment }
>> - *   key-fragment = / [^=,.]* /
>> - *   val          = { / [^,]* / | ',,' }
>> + *   key-fragment = / [^=,.]+ /
>
> This requires a non-empty string.  Good (we don't allow an empty key).
>
>> + *   val          = { / [^,]+ / | ',,' }
>
> I agree that this has no real change.  Previously, you allowed zero or
> more repetitions of a regex that could produce zero characters; now, 
> each outer repetition must make progress.
>
>>    *
>>    * Semantics defined by reduction to JSON:
>>    *
>> @@ -71,12 +71,16 @@
>>    * Awkward.  Note that we carefully restrict alternate types to avoid
>>    * similar ambiguity.
>>    *
>> - * Additional syntax for use with an implied key:
>> + * Alternative syntax for use with an implied key:
>>    *
>> - *   key-vals-ik  = val-no-key [ ',' key-vals ]
>> - *   val-no-key   = / [^=,]* /
>> + *   key-vals     = [ key-val-1st { ',' key-val } [ ',' ] ]
>> + *   key-val-1st  = val-no-key | key-val
>> + *   val-no-key   = / [^=,]+ /
>>    *
>> - * where no-key is syntactic sugar for implied-key=val-no-key.
>> + * where val-no-key is syntactic sugar for implied-key=val-no-key.
>> + *
>> + * Note that you can't use the sugared form when the value contains
>> + * '=' or ','.
>
> Nor can you use the sugared form when the value is intended to be
> empty

True.  Spelling it out wouldn't hurt.  Takes a follow-up patch; this one
is already in master.

>       (although this may be academic, as your other patches enumerate
> the list of clients, and none of them seem to allow an empty value
> even when desugared).
>
> Reviewed-by: Eric Blake <eblake@redhat.com>

Thanks!



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 4/7] keyval: Parse help options
  2020-10-12 14:35   ` Kevin Wolf
@ 2020-10-19  9:04     ` Markus Armbruster
  0 siblings, 0 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-19  9:04 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-devel, qemu-block

Kevin Wolf <kwolf@redhat.com> writes:

> Am 11.10.2020 um 09:35 hat Markus Armbruster geschrieben:
>> From: Kevin Wolf <kwolf@redhat.com>
>> 
>> This adds a special meaning for 'help' and '?' as options to the keyval
>> parser. Instead of being an error (because of a missing value) or a
>> value for an implied key, they now request help, which is a new boolean
>> output of the parser in addition to the QDict.
>> 
>> A new parameter 'p_help' is added to keyval_parse() that contains on
>> return whether help was requested. If NULL is passed, requesting help
>> results in an error and all other cases work like before.
>> 
>> Turning previous error cases into help is a compatible extension. The
>> behaviour potentially changes for implied keys: They could previously
>> get 'help' as their value, which is now interpreted as requesting help.
>> 
>> This is not a problem in practice because 'help' and '?' are not a valid
>> values for the implied key of any option parsed with keyval_parse():
>> 
>> * audiodev: union Audiodev, implied key "driver" is enum AudiodevDriver,
>>   "help" and "?" are not among its values
>> 
>> * display: union DisplayOptions, implied key "type" is enum
>>   DisplayType, "help" and "?" are not among its values
>> 
>> * blockdev: union BlockdevOptions, implied key "driver is enum
>>   BlockdevDriver, "help" and "?" are not among its values
>> 
>> * export: union BlockExport, implied key "type" is enum BlockExportType,
>>   "help" and "?" are not among its values
>> 
>> * monitor: struct MonitorOptions, implied key "mode" is enum MonitorMode,
>>   "help" and "?" are not among its values
>> 
>> * nbd-server: struct NbdServerOptions, no implied key.
>> 
>> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>
>> diff --git a/tests/test-keyval.c b/tests/test-keyval.c
>> index 04c62cf045..7b459900af 100644
>> --- a/tests/test-keyval.c
>> +++ b/tests/test-keyval.c
>> @@ -1,3 +1,4 @@
>> +
>>  /*
>>   * Unit tests for parsing of KEY=VALUE,... strings
>>   *
>
> This hunk looks unintentional.

It is.  Thanks for fixing it up!



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 4/7] keyval: Parse help options
  2020-10-12 11:51   ` Eric Blake
@ 2020-10-19  9:06     ` Markus Armbruster
  0 siblings, 0 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-19  9:06 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, qemu-block

Eric Blake <eblake@redhat.com> writes:

> On 10/11/20 2:35 AM, Markus Armbruster wrote:
>> From: Kevin Wolf <kwolf@redhat.com>
>> This adds a special meaning for 'help' and '?' as options to the
>> keyval
>> parser. Instead of being an error (because of a missing value) or a
>> value for an implied key, they now request help, which is a new boolean
>> output of the parser in addition to the QDict.
>> A new parameter 'p_help' is added to keyval_parse() that contains on
>> return whether help was requested. If NULL is passed, requesting help
>> results in an error and all other cases work like before.
>> 
>
>> +
>> +    /* "help" by itself, without implied key */
>> +    qdict = keyval_parse("help", NULL, &help, &error_abort);
>> +    g_assert_cmpuint(qdict_size(qdict), ==, 0);
>> +    g_assert(help);
>> +    qobject_unref(qdict);
>> +
>> +    /* "help" by itself, with implied key */
>> +    qdict = keyval_parse("help", "implied", &help, &error_abort);
>> +    g_assert_cmpuint(qdict_size(qdict), ==, 0);
>> +    g_assert(help);
>> +    qobject_unref(qdict);
>> +
>> +    /* "help" when no help is available, without implied key */
>> +    qdict = keyval_parse("help", NULL, NULL, &err);
>> +    error_free_or_abort(&err);
>> +    g_assert(!qdict);
>> +
>> +    /* "help" when no help is available, with implied key */
>> +    qdict = keyval_parse("help", "implied", NULL, &err);
>> +    error_free_or_abort(&err);
>> +    g_assert(!qdict);
>> +
>> +    /* Key "help" */
>> +    qdict = keyval_parse("help=on", NULL, &help, &error_abort);
>> +    g_assert_cmpuint(qdict_size(qdict), ==, 1);
>> +    g_assert_cmpstr(qdict_get_try_str(qdict, "help"), ==, "on");
>> +    g_assert(!help);
>> +    qobject_unref(qdict);
>> +
>> +    /* "help" followed by crap, without implied key */
>> +    qdict = keyval_parse("help.abc", NULL, &help, &err);
>> +    error_free_or_abort(&err);
>> +    g_assert(!qdict);
>> +
>> +    /* "help" followed by crap, with implied key */
>> +    qdict = keyval_parse("help.abc", "implied", &help, &err);
>> +    g_assert_cmpuint(qdict_size(qdict), ==, 1);
>> +    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "help.abc");
>> +    g_assert(!help);
>> +    qobject_unref(qdict);
>> +
>> +    /* "help" with other stuff, without implied key */
>> +    qdict = keyval_parse("number=42,help,foo=bar", NULL, &help, &error_abort);
>> +    g_assert_cmpuint(qdict_size(qdict), ==, 2);
>> +    g_assert_cmpstr(qdict_get_try_str(qdict, "number"), ==, "42");
>> +    g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
>> +    g_assert(help);
>> +    qobject_unref(qdict);
>> +
>> +    /* "help" with other stuff, with implied key */
>> +    qdict = keyval_parse("val,help,foo=bar", "implied", &help, &error_abort);
>> +    g_assert_cmpuint(qdict_size(qdict), ==, 2);
>> +    g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "val");
>> +    g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
>> +    g_assert(help);
>> +    qobject_unref(qdict);
>
> Is it worth checking that "helper" with implied key is a value, not help?

Case /* "help" followed by crap, with implied key */ covers this,
doesn't it?

>> +++ b/util/keyval.c
>> @@ -14,10 +14,11 @@
>>    * KEY=VALUE,... syntax:
>>    *
>>    *   key-vals     = [ key-val { ',' key-val } [ ',' ] ]
>> - *   key-val      = key '=' val
>> + *   key-val      = key '=' val | help
>>    *   key          = key-fragment { '.' key-fragment }
>>    *   key-fragment = / [^=,.]+ /
>>    *   val          = { / [^,]+ / | ',,' }
>> + *   help         = 'help | '?'
>
> Missing '

Kevin fixed it up.

> Otherwise
> Reviewed-by: Eric Blake <eblake@redhat.com>

Thanks!



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object
  2020-10-12 14:39 ` [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Kevin Wolf
@ 2020-10-19  9:08   ` Markus Armbruster
  0 siblings, 0 replies; 18+ messages in thread
From: Markus Armbruster @ 2020-10-19  9:08 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: qemu-devel, qemu-block

Kevin Wolf <kwolf@redhat.com> writes:

> Am 11.10.2020 um 09:34 hat Markus Armbruster geschrieben:
>> This replaces the QemuOpts-based help code for --object in the storage
>> daemon with code based on the keyval parser.
>> 
>> Review of v3 led me to preexisting issues.  Instead of posting my
>> fixes separately, then working with Kevin to rebase his work on top of
>> mine, I did the rebase myself and am posting it together with my fixes
>> as v4.  Hope that's okay.
>> 
>> Note: to test qemu-storage-daemon --object help, you need Philippe's
>> "[PATCH v3] hw/nvram: Always register FW_CFG_DATA_GENERATOR_INTERFACE"
>> and its prerequisites (first 10 patches of Paolo's "[PULL 00/39] SCSI,
>> qdev, qtest, meson patches for 2020-10-10"), so it doesn't crash with
>> "missing interface 'fw_cfg-data-generator' for object 'tls-creds'".
>
> Thanks, fixed up the typos in patch 4 and applied patches 1-4. I'm
> taking patches 5-7 from my original series because you didn't add your
> S-o-b (they are unchanged anyway, so no practical difference).

Makes sense.  I didn't feel like slapping my S-o-b on patches I reposted
unchanged.

Thanks!



^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2020-10-19  9:10 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-11  7:34 [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Markus Armbruster
2020-10-11  7:34 ` [PATCH v4 1/7] keyval: Fix and clarify grammar Markus Armbruster
2020-10-12 11:43   ` Eric Blake
2020-10-19  9:03     ` Markus Armbruster
2020-10-11  7:35 ` [PATCH v4 2/7] test-keyval: Demonstrate misparse of ', ' with implied key Markus Armbruster
2020-10-12 11:44   ` [PATCH v4 2/7] test-keyval: Demonstrate misparse of ',' " Eric Blake
2020-10-11  7:35 ` [PATCH v4 3/7] keyval: Fix parsing of ',' in value of " Markus Armbruster
2020-10-12 11:46   ` [PATCH v4 3/7] keyval: Fix parsing of ', ' " Eric Blake
2020-10-11  7:35 ` [PATCH v4 4/7] keyval: Parse help options Markus Armbruster
2020-10-12 11:51   ` Eric Blake
2020-10-19  9:06     ` Markus Armbruster
2020-10-12 14:35   ` Kevin Wolf
2020-10-19  9:04     ` Markus Armbruster
2020-10-11  7:35 ` [PATCH v4 5/7] qom: Factor out helpers from user_creatable_print_help() Markus Armbruster
2020-10-11  7:35 ` [PATCH v4 6/7] qom: Add user_creatable_print_help_from_qdict() Markus Armbruster
2020-10-11  7:35 ` [PATCH v4 7/7] qemu-storage-daemon: Remove QemuOpts from --object parser Markus Armbruster
2020-10-12 14:39 ` [PATCH v4 0/7] qemu-storage-daemon: Remove QemuOpts from --object Kevin Wolf
2020-10-19  9:08   ` Markus Armbruster

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).