"Daniel P. Berrange" <berra...@redhat.com> writes:

> If given an option string such as
>
>   size=1024,nodes=10,nodes=4-5,nodes=1-2,policy=bind
>
> the qemu_opts_to_qdict() method will currently overwrite
> the values for repeated option keys, so only the last
> value is in the returned dict:
>
>     size=QString("1024")
>     nodes=QString("1-2")
>     policy=QString("bind")
>
> With this change the caller can optionally ask for all
> the repeated values to be stored in a QList. In the
> above example that would result in 'nodes' being a
> QList, so the returned dict would contain
>
>     size=QString("1024")
>     nodes=QList([QString("10"),
>                  QString("4-5"),
>                  QString("1-2")])
>     policy=QString("bind")
>
> Note that the conversion has no way of knowing whether
> any given key is expected to be a list upfront - it can
> only figure that out when seeing the first duplicated
> key. Thus the caller has to be prepared to deal with the
> fact that if a key 'foo' is a list, then the returned
> qdict may contain either a QString or a QList for the
> key 'foo'.

Actually three cases, not two:

0. qdict does not contain the key means empty list.

1. qdict contains the key with a QString value means list of one
element.

2. qdict contains the key with a QList value means list of more than one
element.

I consider this weird.  However, it's usefully weird with at least your
QObject input visitor.

> In a third mode, it is possible to ask for repeated
> options to be reported as an error, rather than silently
> dropping all but the last one.

Got users for this policy in the pipeline?

> All existing callers are all converted to explicitly
> request the historical behaviour of only reporting the
> last key. Later patches will make use of the new modes.
>
> Signed-off-by: Daniel P. Berrange <berra...@redhat.com>
> ---
>  blockdev.c                   |   7 ++-
>  include/qemu/option.h        |  13 ++++-
>  monitor.c                    |   3 +-
>  qapi/qobject-input-visitor.c |   4 +-
>  qemu-img.c                   |   4 +-
>  qemu-io-cmds.c               |   3 +-
>  qemu-io.c                    |   6 +-
>  qemu-nbd.c                   |   3 +-
>  qom/object_interfaces.c      |   3 +-
>  tests/test-qemu-opts.c       | 132 
> +++++++++++++++++++++++++++++++++++++++++++
>  tests/test-replication.c     |   9 ++-
>  util/qemu-option.c           |  64 ++++++++++++++++++---
>  12 files changed, 229 insertions(+), 22 deletions(-)
>
> diff --git a/blockdev.c b/blockdev.c
> index 814d49f..a999d46 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -914,7 +914,8 @@ DriveInfo *drive_new(QemuOpts *all_opts, 
> BlockInterfaceType block_default_type)
>  
>      /* Get a QDict for processing the options */
>      bs_opts = qdict_new();
> -    qemu_opts_to_qdict(all_opts, bs_opts);
> +    qemu_opts_to_qdict(all_opts, bs_opts, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                       &error_abort);
>  
>      legacy_opts = qemu_opts_create(&qemu_legacy_drive_opts, NULL, 0,
>                                     &error_abort);
> @@ -3804,8 +3805,8 @@ void hmp_drive_add_node(Monitor *mon, const char 
> *optstr)
>          return;
>      }
>  
> -    qdict = qemu_opts_to_qdict(opts, NULL);
> -
> +    qdict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                               &error_abort);
>      if (!qdict_get_try_str(qdict, "node-name")) {
>          QDECREF(qdict);
>          error_report("'node-name' needs to be specified");
> diff --git a/include/qemu/option.h b/include/qemu/option.h
> index 29f3f18..ffd841d 100644
> --- a/include/qemu/option.h
> +++ b/include/qemu/option.h
> @@ -125,7 +125,18 @@ void qemu_opts_set_defaults(QemuOptsList *list, const 
> char *params,
>                              int permit_abbrev);
>  QemuOpts *qemu_opts_from_qdict(QemuOptsList *list, const QDict *qdict,
>                                 Error **errp);
> -QDict *qemu_opts_to_qdict(const QemuOpts *opts, QDict *qdict);

Blank line here, please.

> +typedef enum {
> +    /* Last occurrence of a duplicate option silently replaces earlier */
> +    QEMU_OPTS_REPEAT_POLICY_LAST,
> +    /* Each occurrence of a duplicate option converts value to a QList */
> +    QEMU_OPTS_REPEAT_POLICY_ALL,
> +    /* First occurrence of a duplicate option causes an error */
> +    QEMU_OPTS_REPEAT_POLICY_ERROR,
> +} QemuOptsRepeatPolicy;
> +
> +QDict *qemu_opts_to_qdict(const QemuOpts *opts, QDict *qdict,
> +                          QemuOptsRepeatPolicy repeatPolicy,
> +                          Error **errp);
>  void qemu_opts_absorb_qdict(QemuOpts *opts, QDict *qdict, Error **errp);
>  
>  typedef int (*qemu_opts_loopfunc)(void *opaque, QemuOpts *opts, Error 
> **errp);
> diff --git a/monitor.c b/monitor.c
> index 14089cc..84e79d4 100644
> --- a/monitor.c
> +++ b/monitor.c
> @@ -2642,7 +2642,8 @@ static QDict *monitor_parse_arguments(Monitor *mon,
>                  if (!opts) {
>                      goto fail;
>                  }
> -                qemu_opts_to_qdict(opts, qdict);
> +                qemu_opts_to_qdict(opts, qdict, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                                   &error_abort);
>                  qemu_opts_del(opts);
>              }
>              break;
> diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c
> index 6b3d0f2..2287d11 100644
> --- a/qapi/qobject-input-visitor.c
> +++ b/qapi/qobject-input-visitor.c
> @@ -759,7 +759,9 @@ Visitor *qobject_input_visitor_new_opts(const QemuOpts 
> *opts,
>      QObject *pobj = NULL;
>      Visitor *v = NULL;
>  
> -    pdict = qemu_opts_to_qdict(opts, NULL);
> +    pdict = qemu_opts_to_qdict(opts, NULL,
> +                               QEMU_OPTS_REPEAT_POLICY_LAST,

Join the previous two lines, please.

> +                               errp);

Unlike the other callers, this one doesn't pass &error_abort.  The error
handling is actually dead code.  Let's pass &error_abort and be done
with it.

>      if (!pdict) {
>          goto cleanup;
>      }
> diff --git a/qemu-img.c b/qemu-img.c
> index 851422a..f47ea75 100644
> --- a/qemu-img.c
> +++ b/qemu-img.c
> @@ -273,7 +273,9 @@ static BlockBackend *img_open_opts(const char *optstr,
>      QDict *options;
>      Error *local_err = NULL;
>      BlockBackend *blk;
> -    options = qemu_opts_to_qdict(opts, NULL);
> +    options = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                                 &error_abort);
> +
>      blk = blk_new_open(NULL, NULL, options, flags, &local_err);
>      if (!blk) {
>          error_reportf_err(local_err, "Could not open '%s': ", optstr);
> diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
> index 3a3838a..ce654f4 100644
> --- a/qemu-io-cmds.c
> +++ b/qemu-io-cmds.c
> @@ -1952,7 +1952,8 @@ static int reopen_f(BlockBackend *blk, int argc, char 
> **argv)
>      }
>  
>      qopts = qemu_opts_find(&reopen_opts, NULL);
> -    opts = qopts ? qemu_opts_to_qdict(qopts, NULL) : NULL;
> +    opts = qopts ? qemu_opts_to_qdict(qopts, NULL, 
> QEMU_OPTS_REPEAT_POLICY_LAST,
> +                                      &error_abort) : NULL;

Short ternaries are more legible than if statements, but this one is
pushing the limit now.

>      qemu_opts_reset(&reopen_opts);
>  
>      brq = bdrv_reopen_queue(NULL, bs, opts, flags);
> diff --git a/qemu-io.c b/qemu-io.c
> index db129ea..bb374a6 100644
> --- a/qemu-io.c
> +++ b/qemu-io.c
> @@ -207,7 +207,8 @@ static int open_f(BlockBackend *blk, int argc, char 
> **argv)
>      }
>  
>      qopts = qemu_opts_find(&empty_opts, NULL);
> -    opts = qopts ? qemu_opts_to_qdict(qopts, NULL) : NULL;
> +    opts = qopts ? qemu_opts_to_qdict(qopts, NULL, 
> QEMU_OPTS_REPEAT_POLICY_LAST,
> +                                      &error_abort) : NULL;

Likewise.

>      qemu_opts_reset(&empty_opts);
>  
>      if (optind == argc - 1) {
> @@ -593,7 +594,8 @@ int main(int argc, char **argv)
>              if (!qopts) {
>                  exit(1);
>              }
> -            opts = qemu_opts_to_qdict(qopts, NULL);
> +            opts = qemu_opts_to_qdict(qopts, NULL, 
> QEMU_OPTS_REPEAT_POLICY_LAST,
> +                                      &error_abort);

Even this line's getting ugly.  Can we find a prefix that's less
loquacious than QEMU_OPTS_REPEAT_POLICY_, but still self-explaining?

>              openfile(NULL, flags, writethrough, opts);
>          } else {
>              if (format) {
> diff --git a/qemu-nbd.c b/qemu-nbd.c
> index 99297a5..73b40b1 100644
> --- a/qemu-nbd.c
> +++ b/qemu-nbd.c
> @@ -859,7 +859,8 @@ int main(int argc, char **argv)
>              qemu_opts_reset(&file_opts);
>              exit(EXIT_FAILURE);
>          }
> -        options = qemu_opts_to_qdict(opts, NULL);
> +        options = qemu_opts_to_qdict(opts, NULL, 
> QEMU_OPTS_REPEAT_POLICY_LAST,
> +                                     &error_abort);
>          qemu_opts_reset(&file_opts);
>          blk = blk_new_open(NULL, NULL, options, flags, &local_err);
>      } else {
> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index ded4d84..fdc406b 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -161,7 +161,8 @@ Object *user_creatable_add_opts(QemuOpts *opts, Error 
> **errp)
>      Object *obj = NULL;
>  
>      v = opts_visitor_new(opts);
> -    pdict = qemu_opts_to_qdict(opts, NULL);
> +    pdict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                               &error_abort);
>  
>      obj = user_creatable_add(pdict, v, errp);
>      visit_free(v);
> diff --git a/tests/test-qemu-opts.c b/tests/test-qemu-opts.c
> index a505a3e..3b59978 100644
> --- a/tests/test-qemu-opts.c
> +++ b/tests/test-qemu-opts.c
> @@ -421,6 +421,130 @@ static void test_qemu_opts_set(void)
>      g_assert(opts == NULL);
>  }
>  
> +
> +static void test_qemu_opts_to_qdict_repeat_last(void)
> +{
> +    QemuOpts *opts;
> +    QDict *dict;
> +
> +    /* dynamically initialized (parsed) opts */

What are you trying to say with this comment?  Hmm, you merely copied it
from the existing tests.  I'd drop the existing instances of this
comment instead.

> +    opts = qemu_opts_parse(&opts_list_03,
> +                           
> "size=1024,nodes=10,nodes=4-5,nodes=1-2,policy=bind",
> +                           false, NULL);
> +    g_assert(opts);

You could pass &error_abort.

> +
> +    dict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                              &error_abort);
> +    g_assert(dict);
> +
> +

One blank line, not two, please.

> +    g_assert_cmpstr(qdict_get_str(dict, "size"), ==, "1024");
> +    g_assert_cmpstr(qdict_get_str(dict, "nodes"), ==, "1-2");
> +    g_assert(!qdict_haskey(dict, "nodes.0"));
> +    g_assert(!qdict_haskey(dict, "nodes.1"));
> +    g_assert(!qdict_haskey(dict, "nodes.2"));
> +    g_assert_cmpstr(qdict_get_str(dict, "policy"), ==, "bind");

Checking for absence of "nodes.0"... feels odd.  A better idea is to
check we got exactly the keys we expect, and no more.  Here's a simple
way to do that:

       g_assert(qdict_size(dict) == 3);

> +    QDECREF(dict);
> +
> +    qemu_opts_del(opts);
> +    qemu_opts_reset(&opts_list_03);
> +}
> +
> +
> +static void test_qemu_opts_to_qdict_repeat_all(void)
> +{
> +    QemuOpts *opts;
> +    QDict *dict;
> +    QList *list;
> +    const QListEntry *ent;
> +    QString *str;
> +
> +    /* dynamically initialized (parsed) opts */
> +    opts = qemu_opts_parse(&opts_list_03,
> +                           
> "size=1024,nodes=10,nodes=4-5,nodes=1-2,policy=bind",
> +                           false, NULL);
> +    g_assert(opts);
> +
> +    dict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_ALL,
> +                              &error_abort);
> +    g_assert(dict);
> +
> +    g_assert_cmpstr(qdict_get_str(dict, "size"), ==, "1024");
> +    g_assert(qdict_haskey(dict, "nodes"));
> +    list = qobject_to_qlist(qdict_get(dict, "nodes"));
> +    g_assert(list);

Works, but I'd do

       obj = qdict_get(dict, "nodes");
       g_assert(obj);
       list = qobject_to_qlist(obj);
       g_assert(list);

> +
> +    ent = qlist_first(list);
> +    g_assert(ent);
> +    str = qobject_to_qstring(ent->value);
> +    g_assert(str);
> +    g_assert_cmpstr(qstring_get_str(str), ==, "10");

Since g_assert_cmpstr() does the right thing when @str is null,
g_assert(str) is redundant.  More of the same below.

> +
> +    ent = qlist_next(ent);
> +    g_assert(ent);
> +    str = qobject_to_qstring(ent->value);
> +    g_assert(str);
> +    g_assert_cmpstr(qstring_get_str(str), ==, "4-5");
> +
> +    ent = qlist_next(ent);
> +    g_assert(ent);
> +    str = qobject_to_qstring(ent->value);
> +    g_assert(str);
> +    g_assert_cmpstr(qstring_get_str(str), ==, "1-2");
> +
> +    ent = qlist_next(ent);
> +    g_assert(!ent);
> +
> +    g_assert_cmpstr(qdict_get_str(dict, "policy"), ==, "bind");

       g_assert(qdict_size(dict) == 3);

> +    QDECREF(dict);
> +
> +    qemu_opts_del(opts);
> +    qemu_opts_reset(&opts_list_03);
> +}
> +
> +static void test_qemu_opts_to_qdict_repeat_err_fail(void)
> +{
> +    QemuOpts *opts;
> +    QDict *dict;
> +    Error *err = NULL;
> +
> +    /* dynamically initialized (parsed) opts */
> +    opts = qemu_opts_parse(&opts_list_03,
> +                           
> "size=1024,nodes=10,nodes=4-5,nodes=1-2,policy=bind",
> +                           false, NULL);
> +    g_assert(opts);
> +
> +    dict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_ERROR,
> +                              &err);
> +    error_free_or_abort(&err);
> +    g_assert(!dict);
> +
> +    qemu_opts_del(opts);
> +    qemu_opts_reset(&opts_list_03);
> +}
> +
> +static void test_qemu_opts_to_qdict_repeat_err_ok(void)
> +{
> +    QemuOpts *opts;
> +    QDict *dict;
> +
> +    /* dynamically initialized (parsed) opts */
> +    opts = qemu_opts_parse(&opts_list_03,
> +                           "size=1024,nodes=10,policy=bind",
> +                           false, NULL);
> +    g_assert(opts);
> +
> +    dict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_ERROR,
> +                              &error_abort);
> +    g_assert_cmpstr(qdict_get_str(dict, "size"), ==, "1024");
> +    g_assert_cmpstr(qdict_get_str(dict, "nodes"), ==, "10");
> +    g_assert_cmpstr(qdict_get_str(dict, "policy"), ==, "bind");

       g_assert(qdict_size(dict) == 3);

> +
> +    QDECREF(dict);
> +    qemu_opts_del(opts);
> +    qemu_opts_reset(&opts_list_03);
> +}
> +
>  int main(int argc, char *argv[])
>  {
>      register_opts();
> @@ -435,6 +559,14 @@ int main(int argc, char *argv[])
>      g_test_add_func("/qemu-opts/opt_unset", test_qemu_opt_unset);
>      g_test_add_func("/qemu-opts/opts_reset", test_qemu_opts_reset);
>      g_test_add_func("/qemu-opts/opts_set", test_qemu_opts_set);
> +    g_test_add_func("/qemu-opts/to_qdict/repeat-last",
> +                    test_qemu_opts_to_qdict_repeat_last);
> +    g_test_add_func("/qemu-opts/to_qdict/repeat-all",
> +                    test_qemu_opts_to_qdict_repeat_all);
> +    g_test_add_func("/qemu-opts/to_qdict/repeat-err-fail",
> +                    test_qemu_opts_to_qdict_repeat_err_fail);
> +    g_test_add_func("/qemu-opts/to_qdict/repeat-err-ok",
> +                    test_qemu_opts_to_qdict_repeat_err_ok);
>      g_test_run();
>      return 0;
>  }
> diff --git a/tests/test-replication.c b/tests/test-replication.c
> index 0997bd8..e267f9a 100644
> --- a/tests/test-replication.c
> +++ b/tests/test-replication.c
> @@ -181,7 +181,8 @@ static BlockBackend *start_primary(void)
>      opts = qemu_opts_parse_noisily(&qemu_drive_opts, cmdline, false);
>      g_free(cmdline);
>  
> -    qdict = qemu_opts_to_qdict(opts, NULL);
> +    qdict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                               &error_abort);
>      qdict_set_default_str(qdict, BDRV_OPT_CACHE_DIRECT, "off");
>      qdict_set_default_str(qdict, BDRV_OPT_CACHE_NO_FLUSH, "off");
>  
> @@ -311,7 +312,8 @@ static BlockBackend *start_secondary(void)
>      opts = qemu_opts_parse_noisily(&qemu_drive_opts, cmdline, false);
>      g_free(cmdline);
>  
> -    qdict = qemu_opts_to_qdict(opts, NULL);
> +    qdict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                               &error_abort);
>      qdict_set_default_str(qdict, BDRV_OPT_CACHE_DIRECT, "off");
>      qdict_set_default_str(qdict, BDRV_OPT_CACHE_NO_FLUSH, "off");
>  
> @@ -336,7 +338,8 @@ static BlockBackend *start_secondary(void)
>      opts = qemu_opts_parse_noisily(&qemu_drive_opts, cmdline, false);
>      g_free(cmdline);
>  
> -    qdict = qemu_opts_to_qdict(opts, NULL);
> +    qdict = qemu_opts_to_qdict(opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
> +                               &error_abort);
>      qdict_set_default_str(qdict, BDRV_OPT_CACHE_DIRECT, "off");
>      qdict_set_default_str(qdict, BDRV_OPT_CACHE_NO_FLUSH, "off");
>  
> diff --git a/util/qemu-option.c b/util/qemu-option.c
> index 418cbb6..0129ede 100644
> --- a/util/qemu-option.c
> +++ b/util/qemu-option.c
> @@ -1057,23 +1057,73 @@ void qemu_opts_absorb_qdict(QemuOpts *opts, QDict 
> *qdict, Error **errp)
>   * The QDict values are of type QString.
>   * TODO We'll want to use types appropriate for opt->desc->type, but
>   * this is enough for now.

While there, we could document @obj and @dict properly.  But see below
on @dict.

> + *
> + * If the @opts contains multiple occurrences of the same key,
> + * then the @repeatPolicy parameter determines how they are to
> + * be handled. Traditional behaviour was to only store the
> + * last occurrence, but if @repeatPolicy is set to
> + * QEMU_OPTS_REPEAT_POLICY_ALL, then a QList will be returned
> + * containing all values, for any key with multiple occurrences.
> + * The QEMU_OPTS_REPEAT_POLICY_ERROR value can be used to fail
> + * conversion with an error if multiple occurrences of a key
> + * are seen.

What the traditional behavior was is quite interesting in a commit
message.  Less so in a comment.  Suggest:

    * be handled.  With @repeatPolicy QEMU_OPTS_REPEAT_POLICY_LAST,
    * all values but the last are silently ignored.  With
    * QEMU_OPTS_REPEAT_POLICY_ALL, a QList will be returned
    * containing all values, for any key with multiple occurrences.
    * With QEMU_OPTS_REPEAT_POLICY_ERROR, the conversion fails.

Let's spell out that the function can only fail with
QEMU_OPTS_REPEAT_POLICY_ERROR:

    * This is the only way this function can fail.

>   */
> -QDict *qemu_opts_to_qdict(const QemuOpts *opts, QDict *qdict)
> +QDict *qemu_opts_to_qdict(const QemuOpts *opts, QDict *qdict,
> +                          QemuOptsRepeatPolicy repeatPolicy,
> +                          Error **errp)
>  {
>      QemuOpt *opt;
> -    QObject *val;
> +    QObject *val, *prevval;

I'd prefer prev_val.

> +    QList *list;
> +    QDict *ret;
>  
> -    if (!qdict) {
> -        qdict = qdict_new();
> +    if (qdict) {
> +        ret = qdict;
> +    } else {
> +        ret = qdict_new();
>      }

You need this change to get the reference counting on failure right.
This makes me question the qdict parameter.  I can see two non-null
arguments for it outside tests.  The one in drive_new() is basically
stupid:

    bs_opts = qdict_new();
    qemu_opts_to_qdict(all_opts, bs_opts, QEMU_OPTS_REPEAT_POLICY_LAST,
                       &error_abort);

Easily replaced by

    bs_opts = qemu_opts_to_qdict(all_opts, NULL, QEMU_OPTS_REPEAT_POLICY_LAST,
                                 &error_abort);

The one in monitor_parse_arguments()

                qemu_opts_to_qdict(opts, qdict, QEMU_OPTS_REPEAT_POLICY_LAST,
                                   &error_abort);

could be replaced by

                QDECREF(qdict);
                qdict = qemu_opts_to_qdict(opts, NULL, 
QEMU_OPTS_REPEAT_POLICY_LAST,
                                   &error_abort);

at a negligible loss of efficiency.  We could then drop the parameter,
simplifying the function's contract.

Let's insert a blank line here...

>      if (opts->id) {
> -        qdict_put(qdict, "id", qstring_from_str(opts->id));
> +        qdict_put(ret, "id", qstring_from_str(opts->id));
>      }

... and here.

>      QTAILQ_FOREACH(opt, &opts->head, next) {
>          val = QOBJECT(qstring_from_str(opt->str));
> -        qdict_put_obj(qdict, opt->name, val);
> +
> +        if (qdict_haskey(ret, opt->name)) {
> +            switch (repeatPolicy) {
> +            case QEMU_OPTS_REPEAT_POLICY_ERROR:
> +                error_setg(errp, "Option '%s' appears more than once",
> +                           opt->name);
> +                qobject_decref(val);
> +                if (!qdict) {
> +                    QDECREF(ret);
> +                }
> +                return NULL;
> +
> +            case QEMU_OPTS_REPEAT_POLICY_ALL:
> +                prevval = qdict_get(ret, opt->name);
> +                if (qobject_type(prevval) == QTYPE_QLIST) {
> +                    /* Already identified this key as a list */
> +                    list = qobject_to_qlist(prevval);
> +                } else {
> +                    /* Replace current scalar with a list */
> +                    list = qlist_new();
> +                    qobject_incref(prevval);

Where is this reference decremented?

> +                    qlist_append_obj(list, prevval);
> +                    qdict_put_obj(ret, opt->name, QOBJECT(list));
> +                }
> +                qlist_append_obj(list, val);
> +                break;
> +
> +            case QEMU_OPTS_REPEAT_POLICY_LAST:
> +                /* Just discard previously set value */
> +                qdict_put_obj(ret, opt->name, val);
> +                break;
> +            }
> +        } else {
> +            qdict_put_obj(ret, opt->name, val);
> +        }

A possible alternative:

           val = QOBJECT(qstring_from_str(opt->str));

           if (qdict_haskey(ret, opt->name)) {
               switch (repeatPolicy) {
               case QEMU_OPTS_REPEAT_POLICY_ERROR:
                   error_setg(errp, "Option '%s' appears more than once",
                              opt->name);
                   qobject_decref(val);
                   if (!qdict) {
                       QDECREF(ret);
                   }
                   return NULL;

               case QEMU_OPTS_REPEAT_POLICY_ALL:
                   prevval = qdict_get(ret, opt->name);
                   if (qobject_type(prevval) == QTYPE_QLIST) {
                       /* Already identified this key as a list */
                       list = qobject_to_qlist(prevval);
                   } else {
                       /* Replace current scalar with a list */
                       list = qlist_new();
                       qobject_incref(prevval);
                       qlist_append_obj(list, prevval);
                   }
                   qlist_append_obj(list, val);
                   val = QOBJECT(list);
                   break;

               case QEMU_OPTS_REPEAT_POLICY_LAST:
                   break;
               }
           }
           qdict_put_obj(ret, opt->name, val);

This shows the common part of the behavior more clearly.  Matter of
taste, you get to use your artistic license.

>      }
> -    return qdict;
> +    return ret;
>  }
>  
>  /* Validate parsed opts against descriptions where no

Reply via email to