@@ -20,8 +20,6 @@ void qdict_join(QDict *dest, QDict *src, bool overwrite);
void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start);
void qdict_array_split(QDict *src, QList **dst);
int qdict_array_entries(QDict *src, const char *subqdict);
-QObject *qdict_crumple(const QDict *src, Error **errp);
-void qdict_flatten(QDict *qdict);
typedef struct QDictRenames {
const char *from;
@@ -66,4 +66,7 @@ const char *qdict_get_try_str(const QDict *qdict, const char *key);
QDict *qdict_clone_shallow(const QDict *src);
+QObject *qdict_crumple(const QDict *src, Error **errp);
+void qdict_flatten(QDict *qdict);
+
#endif /* QDICT_H */
@@ -151,5 +151,6 @@ QDict *keyval_parse_into(QDict *qdict, const char *params, const char *implied_k
bool *p_help, Error **errp);
QDict *keyval_parse(const char *params, const char *implied_key,
bool *help, Error **errp);
+void keyval_merge(QDict *old, const QDict *new, Error **errp);
#endif
@@ -115,6 +115,7 @@
#include "qapi/qapi-commands-migration.h"
#include "qapi/qapi-commands-misc.h"
#include "qapi/qapi-commands-ui.h"
+#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qerror.h"
#include "sysemu/iothread.h"
#include "qemu/guest-random.h"
@@ -2045,13 +2046,94 @@ static int global_init_func(void *opaque, QemuOpts *opts, Error **errp)
return 0;
}
+/*
+ * Return whether configuration group @group is stored in QemuOpts or
+ * in a list of QDicts.
+ */
+static bool is_qemuopts_group(const char *group)
+{
+ return true;
+}
+
+/*
+ * Return a pointer to a list of QDicts, used to store options for
+ * "-set GROUP.*".
+ */
+static GSList **qemu_config_list(const char *group)
+{
+ return NULL;
+}
+
+/*
+ * Return a pointer to a QDict inside the list starting at @head,
+ * used to store options for "-GROUP id=...".
+ */
+static QDict *qemu_find_config(GSList *head, const char *id)
+{
+ assert(id);
+ while (head) {
+ QDict *dict = head->data;
+ if (g_strcmp0(qdict_get_try_str(dict, "id"), id) == 0) {
+ return dict;
+ }
+ head = head->next;
+ }
+ return NULL;
+}
+
+static void qemu_record_config_group(const char *group, QDict *dict, Error **errp)
+{
+ GSList **p_head;
+
+ p_head = qemu_config_list(group);
+ if (p_head) {
+ *p_head = g_slist_prepend(*p_head, dict);
+ } else {
+ abort();
+ }
+}
+
+static void qemu_set_qdict_option(QDict *dict, const char *key, const char *value,
+ Error **errp)
+{
+ QDict *merge_dict;
+
+ merge_dict = qdict_new();
+ qdict_put_str(merge_dict, key, value);
+ keyval_merge(dict, merge_dict, errp);
+ qobject_unref(merge_dict);
+}
+
+/*
+ * Parse non-QemuOpts config file groups, pass the rest to
+ * qemu_config_do_parse.
+ */
+static void qemu_parse_config_group(const char *group, QDict *qdict,
+ void *opaque, Error **errp)
+{
+ if (is_qemuopts_group(group)) {
+ QObject *crumpled = qdict_crumple(qdict, errp);
+ if (!crumpled) {
+ return;
+ }
+ if (qobject_type(crumpled) != QTYPE_QDICT) {
+ assert(qobject_type(crumpled) == QTYPE_QLIST);
+ error_setg(errp, "Lists cannot be at top level of a configuration section");
+ return;
+ }
+ qemu_record_config_group(group, qobject_to(QDict, crumpled), errp);
+ } else {
+ qemu_config_do_parse(group, qdict, opaque, errp);
+ }
+}
+
static void qemu_read_default_config_file(Error **errp)
{
int ret;
Error *local_err = NULL;
g_autofree char *file = get_relocated_path(CONFIG_QEMU_CONFDIR "/qemu.conf");
- ret = qemu_read_config_file(file, qemu_config_do_parse, &local_err);
+ ret = qemu_read_config_file(file, qemu_parse_config_group, &local_err);
if (ret < 0) {
if (ret == -ENOENT) {
error_free(local_err);
@@ -2061,37 +2143,45 @@ static void qemu_read_default_config_file(Error **errp)
}
}
-static int qemu_set_option(const char *str)
+static void qemu_set_option(const char *str, Error **errp)
{
- Error *local_err = NULL;
char group[64], id[64], arg[64];
QemuOptsList *list;
QemuOpts *opts;
+ GSList **p_head;
+ QDict *dict;
int rc, offset;
rc = sscanf(str, "%63[^.].%63[^.].%63[^=]%n", group, id, arg, &offset);
if (rc < 3 || str[offset] != '=') {
- error_report("can't parse: \"%s\"", str);
- return -1;
+ error_setg(errp, "can't parse: \"%s\"", str);
+ return;
}
- list = qemu_find_opts(group);
- if (list == NULL) {
- return -1;
+ p_head = qemu_config_list(group);
+ if (p_head) {
+ dict = qemu_find_config(*p_head, id);
+ if (!dict) {
+ goto not_found;
+ }
+ qemu_set_qdict_option(dict, arg, str + offset + 1, errp);
+ return;
}
- opts = qemu_opts_find(list, id);
- if (!opts) {
- error_report("there is no %s \"%s\" defined",
- list->name, id);
- return -1;
+ list = qemu_find_opts_err(group, errp);
+ if (list) {
+ opts = qemu_opts_find(list, id);
+ if (!opts) {
+ goto not_found;
+ }
+ qemu_opt_set(opts, arg, str + offset + 1, errp);
+ return;
}
- if (!qemu_opt_set(opts, arg, str + offset + 1, &local_err)) {
- error_report_err(local_err);
- return -1;
- }
- return 0;
+ return;
+
+not_found:
+ error_setg(errp, "there is no %s \"%s\" defined", group, id);
}
static void user_register_global_props(void)
@@ -2642,8 +2732,7 @@ void qemu_init(int argc, char **argv, char **envp)
}
break;
case QEMU_OPTION_set:
- if (qemu_set_option(optarg) != 0)
- exit(1);
+ qemu_set_option(optarg, &error_fatal);
break;
case QEMU_OPTION_global:
if (qemu_global_option(optarg) != 0)
@@ -3285,7 +3374,7 @@ void qemu_init(int argc, char **argv, char **envp)
qemu_plugin_opt_parse(optarg, &plugin_list);
break;
case QEMU_OPTION_readconfig:
- qemu_read_config_file(optarg, qemu_config_do_parse, &error_fatal);
+ qemu_read_config_file(optarg, qemu_parse_config_group, &error_fatal);
break;
case QEMU_OPTION_spice:
olist = qemu_find_opts_err("spice", NULL);
@@ -737,6 +737,36 @@ static void test_keyval_visit_any(void)
visit_free(v);
}
+static void test_keyval_merge_success(void)
+{
+ QDict *old = keyval_parse("opt1=abc,opt2.sub1=def,opt2.sub2=ghi,opt3=xyz",
+ NULL, NULL, &error_abort);
+ QDict *new = keyval_parse("opt1=ABC,opt2.sub2=GHI,opt2.sub3=JKL",
+ NULL, NULL, &error_abort);
+ QDict *combined = keyval_parse("opt1=ABC,opt2.sub1=def,opt2.sub2=GHI,opt2.sub3=JKL,opt3=xyz",
+ NULL, NULL, &error_abort);
+ Error *err = NULL;
+
+ keyval_merge(old, new, &err);
+ g_assert(!err);
+ g_assert(qdict_is_equal(QOBJECT(combined), QOBJECT(old)));
+}
+
+static void test_keyval_merge_conflict(void)
+{
+ QDict *old = keyval_parse("opt2.sub1=def,opt2.sub2=ghi",
+ NULL, NULL, &error_abort);
+ QDict *new = keyval_parse("opt2=ABC",
+ NULL, NULL, &error_abort);
+ Error *err = NULL;
+
+ keyval_merge(new, old, &err);
+ error_free_or_abort(&err);
+
+ keyval_merge(old, new, &err);
+ error_free_or_abort(&err);
+}
+
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
@@ -750,6 +780,8 @@ int main(int argc, char *argv[])
g_test_add_func("/keyval/visit/optional", test_keyval_visit_optional);
g_test_add_func("/keyval/visit/alternate", test_keyval_visit_alternate);
g_test_add_func("/keyval/visit/any", test_keyval_visit_any);
+ g_test_add_func("/keyval/merge/success", test_keyval_merge_success);
+ g_test_add_func("/keyval/merge/conflict", test_keyval_merge_conflict);
g_test_run();
return 0;
}
@@ -315,6 +315,42 @@ static char *reassemble_key(GSList *key)
return g_string_free(s, FALSE);
}
+/* Merge two dictionaries. */
+static void keyval_do_merge(QDict *old, const QDict *new, GString *str, Error **errp)
+{
+ size_t save_len = str->len;
+ const QDictEntry *ent;
+ QObject *old_value;
+
+ for (ent = qdict_first(new); ent; ent = qdict_next(new, ent)) {
+ old_value = qdict_get(old, ent->key);
+ if (old_value && qobject_type(old_value) != qobject_type(ent->value)) {
+ error_setg(errp, "Parameter '%s%s' used inconsistently", str->str, ent->key);
+ return;
+ }
+ if (!old_value || qobject_type(ent->value) != QTYPE_QDICT) {
+ qobject_ref(ent->value);
+ qdict_put_obj(old, ent->key, ent->value);
+ continue;
+ }
+
+ /* Merge sub-dictionaries. */
+ g_string_append(str, ent->key);
+ g_string_append_c(str, '.');
+ keyval_do_merge(qobject_to(QDict, old_value),
+ qobject_to(QDict, ent->value),
+ str, errp);
+ g_string_truncate(str, save_len);
+ }
+}
+
+void keyval_merge(QDict *old, const QDict *new, Error **errp)
+{
+ GString *str = g_string_new("");
+ keyval_do_merge(old, new, str, errp);
+ g_string_free(str, TRUE);
+}
+
/*
* Listify @cur recursively.
* Replace QDicts whose keys are all valid list indexes by QLists.
Add generic machinery to support parsing command line options with keyval in -set and -readconfig, choosing between QDict and QemuOpts as the underlying data structure. The keyval_merge function is slightly heavyweight as a way to do qemu_set_option for QDict-based options, but it will be put to further use later to merge entire -readconfig sections together with their command-line equivalents. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- include/block/qdict.h | 2 - include/qapi/qmp/qdict.h | 3 + include/qemu/option.h | 1 + softmmu/vl.c | 131 ++++++++++++++++++++++++++++++++------- tests/test-keyval.c | 32 ++++++++++ util/keyval.c | 36 +++++++++++ 6 files changed, 182 insertions(+), 23 deletions(-)