From: Paolo Bonzini <pbonzini@redhat.com>
To: qemu-devel@nongnu.org
Cc: kwolf@redhat.com, imammedo@redhat.com, armbru@redhat.com
Subject: [PATCH 04/25] keyval: accept escaped commas in implied option
Date: Mon, 18 Jan 2021 11:30:52 -0500 [thread overview]
Message-ID: <20210118163113.780171-5-pbonzini@redhat.com> (raw)
In-Reply-To: <20210118163113.780171-1-pbonzini@redhat.com>
This is used with the weirdly-named device "SUNFD,fdtwo":
$ qemu-system-sparc -device SUNW,,fdtwo,help
SUNW,fdtwo options:
drive=<str> - Node name or ID of a block device to use as a backend
fallback=<FdcDriveType> - FDC drive type, 144/288/120/none/auto (default: "144")
...
Therefore, accepting it is a preparatory step towards keyval-ifying
-device and the device_add monitor command. In general, however, this
unexpected wart of the keyval syntax leads to suboptimal errors compared
to QemuOpts:
$ ./qemu-system-x86_64 -object foo,,bar,id=obj
qemu-system-x86_64: -object foo,,bar,id=obj: invalid object type: foo,bar
$ storage-daemon/qemu-storage-daemon --object foo,,bar,id=obj
qemu-storage-daemon: Invalid parameter ''
To implement this, the flow of the parser is changed to first unescape
everything up to the next comma or equal sign. This is done in a
new function keyval_fetch_string for both the key and value part.
Keys therefore are now parsed in unescaped form, but this makes no
difference in practice because a comma is an invalid character for a
QAPI name. Thus keys with a comma in them are rejected anyway, as
demonstrated by the new testcase.
As a side effect of the new code, parse errors are slightly improved as
well: "Invalid parameter ''" becomes "Expected parameter before '='"
when keyval is fed a string starting with an equal sign.
The slightly baroque interface of keyval_fetch_string lets me keep the
key parsing loop mostly untouched. It is simplified in the next patch,
however.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/qemu/help_option.h | 11 ---
tests/test-keyval.c | 21 ++++--
util/keyval.c | 145 ++++++++++++++++++++-----------------
3 files changed, 92 insertions(+), 85 deletions(-)
diff --git a/include/qemu/help_option.h b/include/qemu/help_option.h
index ca6389a154..328d2a89fd 100644
--- a/include/qemu/help_option.h
+++ b/include/qemu/help_option.h
@@ -19,15 +19,4 @@ 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/tests/test-keyval.c b/tests/test-keyval.c
index ee927fe4e4..19f664f535 100644
--- a/tests/test-keyval.c
+++ b/tests/test-keyval.c
@@ -89,6 +89,11 @@ static void test_keyval_parse(void)
error_free_or_abort(&err);
g_assert(!qdict);
+ /* Keys must be QAPI identifiers */
+ qdict = keyval_parse("weird,,=key", 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, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 2);
@@ -178,15 +183,15 @@ static void test_keyval_parse(void)
error_free_or_abort(&err);
g_assert(!qdict);
- /* Likewise (qemu_opts_parse(): implied key with comma value) */
- qdict = keyval_parse(",,,a=1", "implied", NULL, &err);
- error_free_or_abort(&err);
- g_assert(!qdict);
+ /* Implied key's value can have a comma */
+ qdict = keyval_parse(",,,a=1", "implied", NULL, &error_abort);
+ g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, ",");
+ g_assert_cmpstr(qdict_get_try_str(qdict, "a"), ==, "1");
+ qobject_unref(qdict);
- /* Implied key's value can't have comma (qemu_opts_parse(): it can) */
- qdict = keyval_parse("val,,ue", "implied", NULL, &err);
- error_free_or_abort(&err);
- g_assert(!qdict);
+ qdict = keyval_parse("val,,ue", "implied", NULL, &error_abort);
+ 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", NULL, &err);
diff --git a/util/keyval.c b/util/keyval.c
index be34928813..eb9b9c55ec 100644
--- a/util/keyval.c
+++ b/util/keyval.c
@@ -16,8 +16,8 @@
* key-vals = [ key-val { ',' key-val } [ ',' ] ]
* key-val = key '=' val | help
* key = key-fragment { '.' key-fragment }
- * key-fragment = / [^=,.]+ /
- * val = { / [^,]+ / | ',,' }
+ * key-fragment = { / [^=,.] / | ',,' }
+ * val = { / [^,] / | ',,' }
* help = 'help' | '?'
*
* Semantics defined by reduction to JSON:
@@ -78,13 +78,13 @@
* Alternative syntax for use with an implied key:
*
* key-vals = [ key-val-1st { ',' key-val } [ ',' ] ]
- * key-val-1st = val-no-key | key-val
- * val-no-key = / [^=,]+ / - help
+ * key-val-1st = (val-no-key - help) | key-val
+ * 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 ','.
+ * Note that you can't use the sugared form when the value is empty
+ * or contains '='.
*/
#include "qemu/osdep.h"
@@ -141,7 +141,7 @@ static int key_to_index(const char *key, const char **end)
* On failure, store an error through @errp and return NULL.
*/
static QObject *keyval_parse_put(QDict *cur,
- const char *key_in_cur, QString *value,
+ const char *key_in_cur, const char *value,
const char *key, const char *key_cursor,
Error **errp)
{
@@ -152,20 +152,56 @@ static QObject *keyval_parse_put(QDict *cur,
if (qobject_type(old) != (value ? QTYPE_QSTRING : QTYPE_QDICT)) {
error_setg(errp, "Parameters '%.*s.*' used inconsistently",
(int)(key_cursor - key), key);
- qobject_unref(value);
return NULL;
}
if (!value) {
return old; /* already QDict, do nothing */
}
- new = QOBJECT(value); /* replacement */
- } else {
- new = value ? QOBJECT(value) : QOBJECT(qdict_new());
}
+ new = value ? QOBJECT(qstring_from_str(value)) : QOBJECT(qdict_new());
qdict_put_obj(cur, key_in_cur, new);
return new;
}
+/*
+ * Parse and unescape the string (key or value) pointed to by @start,
+ * stopping at a single comma or if @key is true an equal sign.
+ * The string is unescaped and NUL-terminated in place.
+ *
+ * On return:
+ * - either NUL or the separator (comma or equal sign) is returned.
+ * - the length of the string is stored in @len.
+ * - @start is advanced to either the NUL or the first character past the
+ * separator.
+ */
+static char keyval_fetch_string(char **start, size_t *len, bool key)
+{
+ char sep;
+ char *p, *unescaped;
+ p = unescaped = *start;
+ for (;;) {
+ sep = *p;
+ if (!sep) {
+ break;
+ }
+ if (key && sep == '=') {
+ ++p;
+ break;
+ }
+ if (sep == ',') {
+ if (*++p != ',') {
+ break;
+ }
+ }
+ *unescaped++ = *p++;
+ }
+
+ *unescaped = 0;
+ *len = unescaped - *start;
+ *start = p;
+ return sep;
+}
+
/*
* Parse one parameter from @params.
*
@@ -179,35 +215,42 @@ static QObject *keyval_parse_put(QDict *cur,
* 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, bool *help,
- Error **errp)
+static char *keyval_parse_one(QDict *qdict, char *params,
+ const char *implied_key, bool *help,
+ Error **errp)
{
- const char *key, *key_end, *val_end, *s, *end;
+ const char *key, *key_end, *s, *end;
+ const char *val = NULL;
+ char sep;
size_t len;
char key_in_cur[128];
QDict *cur;
int ret;
QObject *next;
- GString *val;
key = params;
- val_end = NULL;
- len = strcspn(params, "=,");
- if (len && key[len] != '=') {
- if (starts_with_help_option(key) == len) {
+ sep = keyval_fetch_string(¶ms, &len, true);
+ if (!len) {
+ if (sep) {
+ error_setg(errp, "Expected parameter before '%c%s'", sep, params);
+ } else {
+ error_setg(errp, "Expected parameter at end of string");
+ }
+ return NULL;
+ }
+ if (sep != '=') {
+ if (is_help_option(key)) {
*help = true;
- s = key + len;
- if (*s == ',') {
- s++;
- }
- return s;
+ return params;
}
if (implied_key) {
/* Desugar implied key */
+ val = key;
key = implied_key;
- val_end = params + len;
len = strlen(implied_key);
+ } else {
+ error_setg(errp, "No implicit parameter name for value '%s'", key);
+ return NULL;
}
}
key_end = key + len;
@@ -218,7 +261,7 @@ static const char *keyval_parse_one(QDict *qdict, const char *params,
*/
cur = qdict;
s = key;
- for (;;) {
+ do {
/* Want a key index (unless it's first) or a QAPI name */
if (s != key && key_to_index(s, &end) >= 0) {
len = end - s;
@@ -254,47 +297,16 @@ static const char *keyval_parse_one(QDict *qdict, const char *params,
memcpy(key_in_cur, s, len);
key_in_cur[len] = 0;
s += len;
+ } while (*s++ == '.');
- if (*s != '.') {
- break;
- }
- s++;
- }
-
- if (key == implied_key) {
- assert(!*s);
- val = g_string_new_len(params, val_end - params);
- s = val_end;
- if (*s == ',') {
- s++;
- }
- } else {
- if (*s != '=') {
- error_setg(errp, "Expected '=' after parameter '%.*s'",
- (int)(s - key), key);
- return NULL;
- }
- s++;
-
- val = g_string_new(NULL);
- for (;;) {
- if (!*s) {
- break;
- } else if (*s == ',') {
- s++;
- if (*s != ',') {
- break;
- }
- }
- g_string_append_c(val, *s++);
- }
+ if (key != implied_key) {
+ val = params;
+ keyval_fetch_string(¶ms, &len, false);
}
-
- if (!keyval_parse_put(cur, key_in_cur, qstring_from_gstring(val),
- key, key_end, errp)) {
+ if (!keyval_parse_put(cur, key_in_cur, val, key, key_end, errp)) {
return NULL;
}
- return s;
+ return params;
}
static char *reassemble_key(GSList *key)
@@ -439,10 +451,11 @@ QDict *keyval_parse(const char *params, const char *implied_key,
{
QDict *qdict = qdict_new();
QObject *listified;
- const char *s;
+ g_autofree char *dup;
+ char *s;
bool help = false;
- s = params;
+ s = dup = g_strdup(params);
while (*s) {
s = keyval_parse_one(qdict, s, implied_key, &help, errp);
if (!s) {
--
2.26.2
next prev parent reply other threads:[~2021-01-18 16:48 UTC|newest]
Thread overview: 67+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-01-18 16:30 [PATCH 00/25] qemu-option, keyval, vl: switch -object/-M/-accel to keyval parsing Paolo Bonzini
2021-01-18 16:30 ` [PATCH 01/25] qemu-option: clean up id vs. list->merge_lists Paolo Bonzini
2021-01-19 12:33 ` Kevin Wolf
2021-01-19 13:58 ` Markus Armbruster
2021-01-19 14:20 ` Paolo Bonzini
2021-01-20 8:03 ` Markus Armbruster
2021-01-20 12:37 ` Paolo Bonzini
2021-01-20 12:50 ` Markus Armbruster
2021-01-18 16:30 ` [PATCH 02/25] qemu-option: move help handling to get_opt_name_value Paolo Bonzini
2021-01-19 15:10 ` Markus Armbruster
2021-01-18 16:30 ` [PATCH 03/25] qemu-option: warn for short-form boolean options Paolo Bonzini
2021-01-19 15:56 ` Markus Armbruster
2021-01-19 17:04 ` Paolo Bonzini
2021-01-20 8:42 ` Markus Armbruster
2021-01-20 12:40 ` Paolo Bonzini
2021-01-20 12:59 ` Markus Armbruster
2021-01-20 14:05 ` Paolo Bonzini
2021-01-18 16:30 ` Paolo Bonzini [this message]
2021-01-21 12:58 ` [PATCH 04/25] keyval: accept escaped commas in implied option Markus Armbruster
2021-01-22 8:39 ` Markus Armbruster
2021-01-18 16:30 ` [PATCH 05/25] keyval: simplify keyval_parse_one Paolo Bonzini
2021-01-22 13:48 ` Markus Armbruster
2021-01-22 15:00 ` Paolo Bonzini
2021-01-22 15:44 ` Markus Armbruster
2021-01-18 16:30 ` [PATCH 06/25] tests: convert check-qom-proplist to keyval Paolo Bonzini
2021-01-22 14:14 ` Markus Armbruster
2021-01-22 14:38 ` Paolo Bonzini
2021-01-22 14:48 ` Paolo Bonzini
2021-01-18 16:30 ` [PATCH 07/25] keyval: introduce keyval_parse_into Paolo Bonzini
2021-01-22 14:22 ` Markus Armbruster
2021-01-22 14:30 ` Paolo Bonzini
2021-01-18 16:30 ` [PATCH 08/25] hmp: replace "O" parser with keyval Paolo Bonzini
2021-01-25 9:00 ` Markus Armbruster
2021-02-26 11:25 ` Paolo Bonzini
2021-03-01 10:14 ` Markus Armbruster
2021-03-01 10:23 ` Paolo Bonzini
2021-03-01 13:35 ` Markus Armbruster
2021-03-01 10:43 ` Markus Armbruster
2021-03-01 11:54 ` Paolo Bonzini
2021-01-18 16:30 ` [PATCH 09/25] qom: use qemu_printf to print help for user-creatable objects Paolo Bonzini
2021-01-25 12:47 ` Markus Armbruster
2021-01-18 16:30 ` [PATCH 10/25] hmp: special case help options for object_add Paolo Bonzini
2021-01-25 12:48 ` Markus Armbruster
2021-01-25 12:49 ` Paolo Bonzini
2021-01-25 14:02 ` Markus Armbruster
2021-01-18 16:30 ` [PATCH 11/25] remove -writeconfig Paolo Bonzini
2021-01-25 12:53 ` Markus Armbruster
2021-01-25 14:01 ` Paolo Bonzini
2021-01-25 14:12 ` Daniel P. Berrangé
2021-01-18 16:31 ` [PATCH 12/25] qemu-config: add error propagation to qemu_config_parse Paolo Bonzini
2021-01-25 13:54 ` Markus Armbruster
2021-01-18 16:31 ` [PATCH 13/25] qemu-option: support accept-any QemuOptsList in qemu_opts_absorb_qdict Paolo Bonzini
2021-01-18 16:31 ` [PATCH 14/25] qemu-config: parse configuration files to a QDict Paolo Bonzini
2021-01-18 16:31 ` [PATCH 15/25] vl: plumb keyval-based options into -set and -readconfig Paolo Bonzini
2021-01-25 11:48 ` Markus Armbruster
2021-01-25 13:59 ` Paolo Bonzini
2021-01-18 16:31 ` [PATCH 16/25] qom: do not modify QDict argument in user_creatable_add_dict Paolo Bonzini
2021-01-18 16:31 ` [PATCH 17/25] qemu-io: use keyval for -object parsing Paolo Bonzini
2021-01-18 16:31 ` [PATCH 18/25] qemu-nbd: " Paolo Bonzini
2021-01-18 16:31 ` [PATCH 19/25] qemu-img: " Paolo Bonzini
2021-01-18 16:31 ` [PATCH 20/25] qemu: " Paolo Bonzini
2021-01-18 16:31 ` [PATCH 21/25] storage-daemon: do not register the "object" group with QemuOpts Paolo Bonzini
2021-01-18 16:31 ` [PATCH 22/25] qom: export more functions for use with non-UserCreatable objects Paolo Bonzini
2021-01-18 16:31 ` [PATCH 23/25] vl: switch -M parsing to keyval Paolo Bonzini
2021-01-18 16:31 ` [PATCH 24/25] qemu-option: remove now-dead code Paolo Bonzini
2021-01-18 16:31 ` [PATCH 25/25] vl: switch -accel parsing to keyval Paolo Bonzini
2021-01-18 17:18 ` [PATCH 00/25] qemu-option, keyval, vl: switch -object/-M/-accel to keyval parsing no-reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210118163113.780171-5-pbonzini@redhat.com \
--to=pbonzini@redhat.com \
--cc=armbru@redhat.com \
--cc=imammedo@redhat.com \
--cc=kwolf@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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).