keyval: Parse help options
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>
Message-Id: <20201011073505.1185335-5-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
diff --git a/include/qemu/help_option.h b/include/qemu/help_option.h
index 328d2a8..ca6389a 100644
--- a/include/qemu/help_option.h
+++ b/include/qemu/help_option.h
@@ -19,4 +19,15 @@
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 05e8a15..ac69352 100644
--- a/include/qemu/option.h
+++ b/include/qemu/option.h
@@ -149,6 +149,6 @@
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 f918a05..7b184b5 100644
--- a/qapi/qobject-input-visitor.c
+++ b/qapi/qobject-input-visitor.c
@@ -757,7 +757,7 @@
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 1ae1cda..6f0e0cf 100644
--- a/storage-daemon/qemu-storage-daemon.c
+++ b/storage-daemon/qemu-storage-daemon.c
@@ -278,7 +278,7 @@
}
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 04c62cf..ee927fe 100644
--- a/tests/test-keyval.c
+++ b/tests/test-keyval.c
@@ -27,27 +27,28 @@
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 +57,25 @@
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 +85,25 @@
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 +116,48 @@
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 +165,7 @@
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 +174,81 @@
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 +273,26 @@
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 +301,19 @@
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 +325,7 @@
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 +337,7 @@
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 +355,7 @@
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 +366,7 @@
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 +380,8 @@
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 +391,8 @@
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 +402,7 @@
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 +415,7 @@
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 +435,7 @@
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 +451,7 @@
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 +468,7 @@
/* 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 +483,7 @@
/* 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 +498,7 @@
/* 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 +511,7 @@
/* 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 +530,7 @@
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 +540,7 @@
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 +559,7 @@
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 +577,7 @@
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 +599,7 @@
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 +623,7 @@
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 +652,7 @@
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 +688,7 @@
* 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 +712,7 @@
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 8f33a36..7f625ad 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 @@
}
/*
- * 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 @@
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 @@
/*
* 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 @@
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);