mbox series

[v2,for-5.2,0/6] Deprecate or forbid crazy QemuOpts cases

Message ID 20201109133931.979563-1-pbonzini@redhat.com
Headers show
Series Deprecate or forbid crazy QemuOpts cases | expand

Message

Paolo Bonzini Nov. 9, 2020, 1:39 p.m. UTC
It's very hard to make QemuOpts fail.  It's also very easy
to write command lines that QemuOpts accept but make no sense.

This series deals with three cases:

- QemuOpts accepts ids even for options that are meant to be singletons.
As a result, a command line option like "-M q35,id=ff" is ignored silently.

- QemuOpts simply matches "help" or "?" against the option name to
determine whether the user asked for help.  Something like "nohelp" or
"?=please" will print the help message.

- QemuOpts lets you write boolean options in "short form" where "abc"
means "abc=on" and "noabc" means "abc=off".  This is confusing, since it
is not done for the first key=value pair (but only if there is an implied
key); it can also be grossly misused, as in the previous example, because
it is not type safe.  In case you need confirmation, "-device e1000,noid"
will create a device with id equal to "off".

Unfortunately, this last idiom has found wide use with -chardev (think
"server,nowait") and to a lesser extent -spice, so it can only be
deprecated.  The other two are removed.

Patches 1-3 are cleanups.  Patches 4-6 deal with the above issues one
by one.  I have a seventh patch to remove the third argument to
qemu_opts_create, but it touches a few dozen files.

Paolo

Supersedes: <20201105142731.623428-1-pbonzini@redhat.com>

Paolo Bonzini (6):
  qemu-option: simplify search for end of key
  qemu-option: pass QemuOptsList to opts_accepts_any
  qemu-option: restrict qemu_opts_set to merge-lists QemuOpts
  qemu-option: clean up id vs. list->merge_lists
  qemu-option: move help handling to get_opt_name_value
  qemu-option: warn for short-form boolean options

 docs/system/deprecated.rst |   6 ++
 include/qemu/option.h      |   3 +-
 softmmu/vl.c               |  19 ++---
 tests/test-qemu-opts.c     |  26 ++++++-
 util/qemu-option.c         | 149 +++++++++++++++++++------------------
 5 files changed, 113 insertions(+), 90 deletions(-)

Comments

no-reply@patchew.org Nov. 9, 2020, 1:54 p.m. UTC | #1
Patchew URL: https://patchew.org/QEMU/20201109133931.979563-1-pbonzini@redhat.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Message-id: 20201109133931.979563-1-pbonzini@redhat.com
Subject: [PATCH v2 for-5.2 0/6] Deprecate or forbid crazy QemuOpts cases
Type: series

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 - [tag update]      patchew/20201104172512.2381656-1-ehabkost@redhat.com -> patchew/20201104172512.2381656-1-ehabkost@redhat.com
 - [tag update]      patchew/20201105171126.88014-1-richard.henderson@linaro.org -> patchew/20201105171126.88014-1-richard.henderson@linaro.org
 - [tag update]      patchew/20201105221905.1350-1-dbuono@linux.vnet.ibm.com -> patchew/20201105221905.1350-1-dbuono@linux.vnet.ibm.com
 - [tag update]      patchew/20201106124241.16950-1-vsementsov@virtuozzo.com -> patchew/20201106124241.16950-1-vsementsov@virtuozzo.com
 * [new tag]         patchew/20201109133931.979563-1-pbonzini@redhat.com -> patchew/20201109133931.979563-1-pbonzini@redhat.com
 - [tag update]      patchew/5FA41448.4040404@huawei.com -> patchew/5FA41448.4040404@huawei.com
Switched to a new branch 'test'
50c88b8 qemu-option: warn for short-form boolean options
5ab2220 qemu-option: move help handling to get_opt_name_value
fc2619d qemu-option: clean up id vs. list->merge_lists
e17617f qemu-option: restrict qemu_opts_set to merge-lists QemuOpts
7739f06 qemu-option: pass QemuOptsList to opts_accepts_any
c1676e5 qemu-option: simplify search for end of key

=== OUTPUT BEGIN ===
1/6 Checking commit c1676e514286 (qemu-option: simplify search for end of key)
2/6 Checking commit 7739f060c567 (qemu-option: pass QemuOptsList to opts_accepts_any)
3/6 Checking commit e17617fd5081 (qemu-option: restrict qemu_opts_set to merge-lists QemuOpts)
WARNING: line over 80 characters
#32: FILE: include/qemu/option.h:122:
+bool qemu_opts_set(QemuOptsList *list, const char *name, const char *value, Error **errp);

WARNING: line over 80 characters
#46: FILE: softmmu/vl.c:3110:
+                qemu_opts_set(qemu_find_opts("machine"), "kernel", optarg, &error_abort);

WARNING: line over 80 characters
#51: FILE: softmmu/vl.c:3113:
+                qemu_opts_set(qemu_find_opts("machine"), "initrd", optarg, &error_abort);

WARNING: line over 80 characters
#56: FILE: softmmu/vl.c:3116:
+                qemu_opts_set(qemu_find_opts("machine"), "append", optarg, &error_abort);

WARNING: line over 80 characters
#61: FILE: softmmu/vl.c:3119:
+                qemu_opts_set(qemu_find_opts("machine"), "dtb", optarg, &error_abort);

ERROR: line over 90 characters
#71: FILE: softmmu/vl.c:3229:
+                qemu_opts_set(qemu_find_opts("machine"), "firmware", optarg, &error_abort);

WARNING: line over 80 characters
#152: FILE: util/qemu-option.c:684:
+bool qemu_opts_set(QemuOptsList *list, const char *name, const char *value, Error **errp)

total: 1 errors, 6 warnings, 120 lines checked

Patch 3/6 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

4/6 Checking commit fc2619d6e44c (qemu-option: clean up id vs. list->merge_lists)
5/6 Checking commit 5ab2220302ba (qemu-option: move help handling to get_opt_name_value)
6/6 Checking commit 50c88b8f7929 (qemu-option: warn for short-form boolean options)
WARNING: line over 80 characters
#81: FILE: util/qemu-option.c:803:
+                warn_report("short-form boolean option '%s%s' deprecated", prefix, *name);

WARNING: line over 80 characters
#100: FILE: util/qemu-option.c:834:
+        p = get_opt_name_value(p, firstname, warn_on_flag, help_wanted, &option, &value);

total: 0 errors, 2 warnings, 127 lines checked

Patch 6/6 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20201109133931.979563-1-pbonzini@redhat.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com
Paolo Bonzini Nov. 9, 2020, 4:21 p.m. UTC | #2
On 09/11/20 16:55, Markus Armbruster wrote:
>>           QemuOptsList *net = qemu_find_opts("net");
>> -        qemu_opts_set(net, NULL, "type", "nic", &error_abort);
>> +        qemu_opts_parse(net, "nic", true, &error_abort);
>>   #ifdef CONFIG_SLIRP
>> -        qemu_opts_set(net, NULL, "type", "user", &error_abort);
>> +        qemu_opts_parse(net, "user", true, &error_abort);
>>   #endif
>>       }
>>   
> Looks safe to me, but I don't quite get why you switch to
> qemu_opts_parse().  The commit message explains it is "so that
> qemu_opts_set is now only used on merge-lists QemuOptsList (for which it
> makes the most sense indeed)..."  Is there anything wrong with using ot
> on non-merge-lists QemuOptsList?

I would *expect* a function named qemu_opts_set to do two things:

1. setting an option in a merge-lists QemuOptsList, such as -kernel.

This is indeed what we mostly use qemu_opts_set for.


2. setting an option in a non-merge-lists QemuOptsList with non-NULL id, 
similar to -set.

QEMU does not use qemu_opts_set for the latter (see qemu_set_option) 
because it wants to use qemu_opts_find rather than qemu_opts_create.  In 
fact it wouldn't *work* to use qemu_opts_set for the latter because 
qemu_opts_set uses fail_if_exists==1. So:

    -> For non-merge-lists QemuOptsList and non-NULL id, it is
       debatable that qemu_opts_set fails if the (QemuOptsList, id)
       pair already exists


On the other hand, I would not *expect* qemu_opts_set to create a 
non-merge-lists QemuOpts with a single option; which it does, though. 
This leads us directly to:

    -> For non-merge-lists QemuOptsList and NULL id, qemu_opts_set
       hardly adds value over qemu_opts_parse.  It does skip some
       parsing and unescaping, but its two call sites don't really care.

So qemu_opts_set has warty behavior for non-merge-lists QemuOptsList if 
id is non-NULL, and it's mostly pointless if id is NULL.  My solution to 
keeping the API as simple as possible is to limit qemu_opts_set to 
merge-lists QemuOptsList.  For them, it's useful (we don't want 
comma-unescaping for -kernel) *and* has sane semantics.

>> +    g_assert(!QTAILQ_EMPTY(&list->head));
>> +
>> +    /* set it again */
>> +    qemu_opts_set(list, "str3", "value", &error_abort);
>>      g_assert(!QTAILQ_EMPTY(&list->head));
> 
> This one not.
> 
> What are you trying to accomplish?

Improve the testcase, though I should have mentioned it in the commit 
message.  Basically emulating "-kernel bc -kernel def".

Paolo
Markus Armbruster Nov. 9, 2020, 9:19 p.m. UTC | #3
Paolo Bonzini <pbonzini@redhat.com> writes:

> Options such as "server" or "nowait", that are commonly found in -chardev,
> are sugar for "server=on" and "wait=off".  This is quite surprising and
> also does not have any notion of typing attached.  It is even possible to
> do "-device e1000,noid" and get a device with "id=off".
>
> Deprecate it and print a warning when it is encountered.  In general,
> this short form for boolean options only seems to be in wide use for
> -chardev and -spice.
>
> The extra boolean argument is not my favorite.  In 6.0 I plan to remove
> qemu_opts_set_defaults by switching -M to keyval, and therefore quite
> a bit of QemuOpts code will go away.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  docs/system/deprecated.rst |  6 ++++++
>  tests/test-qemu-opts.c     |  2 +-
>  util/qemu-option.c         | 29 ++++++++++++++++++-----------
>  3 files changed, 25 insertions(+), 12 deletions(-)
>
> diff --git a/docs/system/deprecated.rst b/docs/system/deprecated.rst
> index 8c1dc7645d..f45938a5ff 100644
> --- a/docs/system/deprecated.rst
> +++ b/docs/system/deprecated.rst
> @@ -146,6 +146,12 @@ Drives with interface types other than ``if=none`` are for onboard
>  devices.  It is possible to use drives the board doesn't pick up with
>  -device.  This usage is now deprecated.  Use ``if=none`` instead.
>  
> +Short-form boolean options (since 5.2)
> +''''''''''''''''''''''''''''''''''''''
> +
> +Boolean options such as ``share=on``/``share=off`` can be written
> +in short form as ``share`` and ``noshare``.  This is deprecated
> +and will cause a warning.
>  
>  QEMU Machine Protocol (QMP) commands
>  ------------------------------------
> diff --git a/tests/test-qemu-opts.c b/tests/test-qemu-opts.c
> index 322b32871b..e12fb51032 100644
> --- a/tests/test-qemu-opts.c
> +++ b/tests/test-qemu-opts.c
> @@ -519,7 +519,7 @@ static void test_opts_parse(void)
>      error_free_or_abort(&err);
>      g_assert(!opts);
>  
> -    /* Implied value */
> +    /* Implied value (qemu_opts_parse does not warn) */
>      opts = qemu_opts_parse(&opts_list_03, "an,noaus,noaus=",
>                             false, &error_abort);
>      g_assert_cmpuint(opts_count(opts), ==, 3);
> diff --git a/util/qemu-option.c b/util/qemu-option.c
> index 0ddf1f7b45..23238f00ea 100644
> --- a/util/qemu-option.c
> +++ b/util/qemu-option.c
> @@ -756,10 +756,12 @@ void qemu_opts_print(QemuOpts *opts, const char *separator)
>  
>  static const char *get_opt_name_value(const char *params,
>                                        const char *firstname,
> +                                      bool warn_on_flag,
>                                        bool *help_wanted,
>                                        char **name, char **value)
>  {
>      const char *p;
> +    const char *prefix = "";
>      size_t len;
>      bool is_help = false;
>  
> @@ -776,10 +778,15 @@ static const char *get_opt_name_value(const char *params,
>              if (strncmp(*name, "no", 2) == 0) {
>                  memmove(*name, *name + 2, strlen(*name + 2) + 1);
>                  *value = g_strdup("off");
> +                prefix = "no";
>              } else {
>                  *value = g_strdup("on");
>                  is_help = is_help_option(*name);
>              }
> +            if (!is_help && warn_on_flag) {
> +                warn_report("short-form boolean option '%s%s' deprecated", prefix, *name);
> +                error_printf("Please use %s=%s instead\n", *name, *value);
> +            }

If @warn_on_flag, we warn except for "help" and "?".  The exception
applies regardless of @help_wanted.

>          }
>      } else {
>          /* found "foo=bar,more" */
> @@ -801,14 +808,14 @@ static const char *get_opt_name_value(const char *params,
>  
>  static bool opts_do_parse(QemuOpts *opts, const char *params,
>                            const char *firstname, bool prepend,
> -                          bool *help_wanted, Error **errp)
> +                          bool warn_on_flag, bool *help_wanted, Error **errp)
>  {
>      char *option, *value;
>      const char *p;
>      QemuOpt *opt;
>  
>      for (p = params; *p;) {
> -        p = get_opt_name_value(p, firstname, help_wanted, &option, &value);
> +        p = get_opt_name_value(p, firstname, warn_on_flag, help_wanted, &option, &value);

Long line.  Break it before the three output arguments.

>          if (help_wanted && *help_wanted) {
>              return false;
>          }
> @@ -837,7 +844,7 @@ static char *opts_parse_id(const char *params)
>      char *name, *value;
>  
>      for (p = params; *p;) {
> -        p = get_opt_name_value(p, NULL, NULL, &name, &value);
> +        p = get_opt_name_value(p, NULL, false, NULL, &name, &value);

No change (we pass false to warn_on_flag).

>          if (!strcmp(name, "id")) {
>              g_free(name);
>              return value;
> @@ -856,7 +863,7 @@ bool has_help_option(const char *params)
>      bool ret;
>  
>      for (p = params; *p;) {
> -        p = get_opt_name_value(p, NULL, &ret, &name, &value);
> +        p = get_opt_name_value(p, NULL, false, &ret, &name, &value);

Likewise.

>          g_free(name);
>          g_free(value);
>          if (ret) {
> @@ -876,12 +883,12 @@ bool has_help_option(const char *params)
>  bool qemu_opts_do_parse(QemuOpts *opts, const char *params,
>                         const char *firstname, Error **errp)
>  {
> -    return opts_do_parse(opts, params, firstname, false, NULL, errp);
> +    return opts_do_parse(opts, params, firstname, false, false, NULL, errp);

Likewise.

>  }
>  
>  static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
>                              bool permit_abbrev, bool defaults,
> -                            bool *help_wanted, Error **errp)
> +                            bool warn_on_flag, bool *help_wanted, Error **errp)
>  {
>      const char *firstname;
>      char *id = opts_parse_id(params);
> @@ -904,8 +911,8 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
>          return NULL;
>      }
>  
> -    if (!opts_do_parse(opts, params, firstname, defaults, help_wanted,
> -                       errp)) {
> +    if (!opts_do_parse(opts, params, firstname, defaults,
> +                       warn_on_flag, help_wanted, errp)) {
>          qemu_opts_del(opts);
>          return NULL;
>      }
> @@ -923,7 +930,7 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
>  QemuOpts *qemu_opts_parse(QemuOptsList *list, const char *params,
>                            bool permit_abbrev, Error **errp)
>  {
> -    return opts_parse(list, params, permit_abbrev, false, NULL, errp);
> +    return opts_parse(list, params, permit_abbrev, false, false, NULL, errp);

Likewise.

>  }
>  
>  /**
> @@ -941,7 +948,7 @@ QemuOpts *qemu_opts_parse_noisily(QemuOptsList *list, const char *params,
>      QemuOpts *opts;
>      bool help_wanted = false;
>  
> -    opts = opts_parse(list, params, permit_abbrev, false,
> +    opts = opts_parse(list, params, permit_abbrev, false, true,
>                        opts_accepts_any(list) ? NULL : &help_wanted,
>                        &err);
>      if (!opts) {

This function now warns, except for "help" and "?".  The exception
applies even when we treat "help" and "?" as sugar for "help=on" and
"?=on" because opts_accepts_any().

> @@ -960,7 +967,7 @@ void qemu_opts_set_defaults(QemuOptsList *list, const char *params,
>  {
>      QemuOpts *opts;
>  
> -    opts = opts_parse(list, params, permit_abbrev, true, NULL, NULL);
> +    opts = opts_parse(list, params, permit_abbrev, true, false, NULL, NULL);
>      assert(opts);
>  }

No change (we pass false to warn_on_flag).

Summary: only qemu_opts_parse_noisily() warns.  This is airtight only if
all user input flows through qemu_opts_parse_noisily().  It's too late
in my day for me to check.