[email protected] writes:

>> Reject invalid values such as help= and help=foo.
>> 
>> Reject valid, but not applicable values such has help=on and help=off.
>> 
>> To achieve the above:
>> 
>> Add an 'is_help' member to QemuOpt so that information can be stored
>> while creating the option. This allows moving the help handling out of
>> get_opt_name_value(), which is necessary because that function doesn't
>> take an Error.
>> 
>> At the caller, opts_do_parse(), let the QemuOpt be created with the
>> name and value fields as they come out of get_opt_name_value().
>> 
>> With a new routine, validate the new QemuOpt against the 'help'
>> parameter rules (to fix the bug), then let the existing opt_validate()
>> reject any NULL values. The two validations need to be separate
>> because in between there needs to be a setting of opt->str to "on"
>> whenever the special short-form parameters require it.
>> 
>> The "on" value is required because of the conversions of QemuOpts
>> from/to QDict since QDict cannot take a NULL value. The conversions
>> also cause opt_validate() to be reentrant via qemu_opt_set(), so the
>> initial validation of 'help' needs to be out of that function,
>> otherwise there's no way to distinguish an user-set help=on vs. the
>> help=on set for compatibility with QDict (the "in between" part from
>> the previous paragraph).
>> 
>> The passing around of help_wanted becomes redundant since opt->is_help
>> can be checked throughout. Remove that argument everywhere.
>> 
>> Stop returning false from opts_do_parse() when is_help=true. Only
>> return false when there's an error and let the callers decide what to
>> do when is_help=true. However, still break the loop when is_help=true
>> to keep the behavior of stopping parsing once the 'help' parameter is
>> seen.
>> 
>> At qemu_opts_parse_noisily(), due to the change of return value
>> mentioned above, it becomes clear when to report the error and when to
>> print the help text. Use qemu_opt_has_help_opt() to obtain the
>> is_help information from QemuOpt.
>> 
>> Since get_opt_name_value() can now return while value=NULL, the other
>> two callsites need to be updated. At opts_parse_id(), assert value !=
>> NULL. At has_help_option(), use the NULL value to keep the existing
>> behavior of rejecting anything but 'help'.
>> 
>> The unit tests are updated accordingly.
>> 
>> Signed-off-by: Fabiano Rosas <[email protected]>
>> Message-ID: <[email protected]>
>>
>> diff --git a/include/qemu/option_int.h b/include/qemu/option_int.h
>> index 5dd9a5162db9..c31bd6e3aba1 100644
>> --- a/include/qemu/option_int.h
>> +++ b/include/qemu/option_int.h
>> @@ -32,6 +32,7 @@
>>  struct QemuOpt {
>>      char *name;
>>      char *str;
>> +    bool is_help;
>>  
>>      const QemuOptDesc *desc;
>>      union {
>> diff --git a/tests/unit/test-qemu-opts.c b/tests/unit/test-qemu-opts.c
>> index 07ede509204a..7cedfba33278 100644
>> --- a/tests/unit/test-qemu-opts.c
>> +++ b/tests/unit/test-qemu-opts.c
>> @@ -777,16 +777,15 @@ static void test_has_help_option_with_value(void)
>>      };
>>      int i;
>>      QemuOpts *opts;
>> +    Error *err = NULL;
>>  
>>      for (i = 0; i < ARRAY_SIZE(test); i++) {
>>          /* reject all */
>>          g_assert_cmpint(has_help_option(test[i].params), ==, false);
>>  
>> -        /* accept all */
>> -        opts = qemu_opts_parse(&opts_list_03, test[i].params, false,
>> -                               &error_abort);
>> -        g_assert_cmpint(qemu_opt_has_help_opt(opts), ==, true);
>> -        qemu_opts_del(opts);
>> +        opts = qemu_opts_parse(&opts_list_03, test[i].params, false, &err);
>> +        error_free_or_abort(&err);
>> +        g_assert(!opts);
>>      }
>>  }
>>  
>> diff --git a/util/qemu-option.c b/util/qemu-option.c
>> index 8ac848bbc06f..483e6cbf70a1 100644
>> --- a/util/qemu-option.c
>> +++ b/util/qemu-option.c
>> @@ -331,7 +331,7 @@ bool qemu_opt_has_help_opt(QemuOpts *opts)
>>      QemuOpt *opt;
>>  
>>      QTAILQ_FOREACH_REVERSE(opt, &opts->head, next) {
>> -        if (is_help_option(opt->name)) {
>> +        if (opt->is_help) {
>>              return true;
>>          }
>>      }
>> @@ -499,6 +499,7 @@ static QemuOpt *opt_create(QemuOpts *opts, const char 
>> *name, char *value)
>>      opt->name = g_strdup(name);
>>      opt->str = value;
>>      opt->opts = opts;
>> +    opt->is_help = is_help_option(name);
>>      QTAILQ_INSERT_TAIL(&opts->head, opt, next);
>>  
>>      return opt;
>> @@ -760,12 +761,10 @@ void qemu_opts_print(QemuOpts *opts, const char 
>> *separator)
>>  
>>  static const char *get_opt_name_value(const char *params,
>>                                        const char *firstname,
>> -                                      bool *help_wanted,
>>                                        char **name, char **value)
>>  {
>>      const char *p;
>>      size_t len;
>> -    bool is_help = false;
>>  
>>      len = strcspn(params, "=,");
>>      if (params[len] != '=') {
>> @@ -776,22 +775,6 @@ static const char *get_opt_name_value(const char 
>> *params,
>>              p = get_opt_value(params, value);
>>          } else {
>>              p = get_opt_name(params, name, len);
>> -
>> -            /*
>> -             * Short-form flags (i.e. without any value) are not
>> -             * supported, except for two cases:
>> -             */
>> -
>> -            /* the 'help' or '?' flag */
>> -            is_help = is_help_option(*name);
>> -            if (is_help) {
>> -                *value = g_strdup("on");
>> -            }
>> -
>> -            /* a missing, non-implicit key, i.e. a single comma ',' */
>> -            if (g_str_equal(*name, "") && *p == ',') {
>> -                *value = g_strdup("on");
>> -            }
>>          }
>>      } else {
>>          /* found "foo=bar,more" */
>> @@ -802,18 +785,46 @@ static const char *get_opt_name_value(const char 
>> *params,
>>      }
>>  
>>      assert(!*p || *p == ',');
>> -    if (help_wanted && is_help) {
>> -        *help_wanted = true;
>> -    }
>>      if (*p == ',') {
>>          p++;
>>      }
>>      return p;
>>  }
>>  
>> +/*
>> + * Short-form parameters (i.e. without any value) are not
>> + * supported, except for two cases:
>> + *
>> + * - the 'help' or '?' flag
>> + * - the empty, non-implicit key combined with an empty
>> + *   non-implicit value, i.e. a single comma ','
>> + *
>> + * The rest of the code is not prepared to deal with empty values, set
>> + * the special cases to "on".
>> + */
>> +static bool opt_short_form_compat(QemuOpt *opt, Error **errp)
>> +{
>> +    if (opt->str) {
>> +        /*
>> +         * Validate the 'help' parameter at this point because the
>> +         * opt_validate() routine can be reentrant and this very check
>> +         * would fail once the "on" value is set below.
>> +         */
>> +        if (opt->is_help) {
>> +            error_setg(errp, "Parameter '%s' doesn't take any values",
>> +                       opt->name);
>> +            return false;
>> +        }
>> +    } else {
>> +        if (opt->is_help || g_str_equal(opt->name, "")) {
>> +            opt->str = g_strdup("on");
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>>  static bool opts_do_parse(QemuOpts *opts, const char *params,
>> -                          const char *firstname,
>> -                          bool *help_wanted, Error **errp)
>> +                          const char *firstname, Error **errp)
>>  {
>>      const char *p;
>>      QemuOpt *opt;
>> @@ -822,10 +833,7 @@ static bool opts_do_parse(QemuOpts *opts, const char 
>> *params,
>>          g_autofree char *option = NULL;
>>          g_autofree char *value = NULL;
>>  
>> -        p = get_opt_name_value(p, firstname, help_wanted, &option, &value);
>> -        if (help_wanted && *help_wanted) {
>> -            return false;
>> -        }
>> +        p = get_opt_name_value(p, firstname, &option, &value);
>>          firstname = NULL;
>>  
>>          if (!strcmp(option, "id")) {
>> @@ -833,10 +841,15 @@ static bool opts_do_parse(QemuOpts *opts, const char 
>> *params,
>>          }
>>  
>>          opt = opt_create(opts, option, g_steal_pointer(&value));
>> -        if (!opt_validate(opt, errp)) {
>> +        if (!opt_short_form_compat(opt, errp) ||
>> +            !opt_validate(opt, errp)) {
>
> opt_validate() will reject "help" as an invalid parameter beofre the is_help 
> check below. Change it to?:
>

It does already happen before the series, I'll add a test case for that.

Is it something we want to fix? The situation is that 'help' was present
but the QemuOptsList declares a set of parameters of which 'help' is a
part of. I'm not sure if this is an oversight in that it wants to reject
unlisted parameters, but 'help' should have been special-cased or if the
API is that options that list a set of parameters must also list the
'help'.

>     opt = opt_create(opts, option, g_steal_pointer(&value));
>     if (!opt_short_form_compat(opt, errp)) {
>         qemu_opt_del(opt);
>         return false;
>     }
>     if (opt->is_help) {
>         return true;
>     }
>     if (!opt_validate(opt, errp)) {
>         qemu_opt_del(opt);
>         return false;
>     }
>
>>              qemu_opt_del(opt);
>>              return false;
>>          }
>> +
>> +        if (opt->is_help) {
>> +            return true;
>> +        }
>>      }
>>  
>>      return true;
>> @@ -850,8 +863,9 @@ static char *opts_parse_id(const char *params)
>>          g_autofree char *name = NULL;
>>          g_autofree char *value = NULL;
>>  
>> -        p = get_opt_name_value(p, NULL, NULL, &name, &value);
>> +        p = get_opt_name_value(p, NULL, &name, &value);
>>          if (!strcmp(name, "id")) {
>> +            assert(value);
>>              return g_steal_pointer(&value);
>>          }
>>      }
>> @@ -862,14 +876,13 @@ static char *opts_parse_id(const char *params)
>>  bool has_help_option(const char *params)
>>  {
>>      const char *p;
>> -    bool ret = false;
>>  
>>      for (p = params; *p;) {
>>          g_autofree char *name = NULL;
>>          g_autofree char *value = NULL;
>>  
>> -        p = get_opt_name_value(p, NULL, &ret, &name, &value);
>> -        if (ret) {
>> +        p = get_opt_name_value(p, NULL, &name, &value);
>> +        if (is_help_option(name) && !value) {
>>              return true;
>>          }
>>      }
>> @@ -886,11 +899,11 @@ 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, NULL, errp);
>> +    return opts_do_parse(opts, params, firstname, errp);
>>  }
>>  
>>  static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
>> -                            bool permit_abbrev, bool *help_wanted, Error 
>> **errp)
>> +                            bool permit_abbrev, Error **errp)
>>  {
>>      const char *firstname;
>>      char *id = opts_parse_id(params);
>> @@ -905,7 +918,7 @@ static QemuOpts *opts_parse(QemuOptsList *list, const 
>> char *params,
>>          return NULL;
>>      }
>>  
>> -    if (!opts_do_parse(opts, params, firstname, help_wanted, errp)) {
>> +    if (!opts_do_parse(opts, params, firstname, errp)) {
>>          qemu_opts_del(opts);
>>          return NULL;
>>      }
>> @@ -923,7 +936,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, NULL, errp);
>> +    return opts_parse(list, params, permit_abbrev, errp);
>>  }
>>  
>>  /**
>> @@ -939,17 +952,16 @@ QemuOpts *qemu_opts_parse_noisily(QemuOptsList *list, 
>> const char *params,
>>  {
>>      Error *err = NULL;
>>      QemuOpts *opts;
>> -    bool help_wanted = false;
>>  
>> -    opts = opts_parse(list, params, permit_abbrev,
>> -                      opts_accepts_any(list) ? NULL : &help_wanted, &err);
>> +    opts = opts_parse(list, params, permit_abbrev, &err);
>>      if (!opts) {
>> -        assert(!!err + !!help_wanted == 1);
>> -        if (help_wanted) {
>> -            qemu_opts_print_help(list, true);
>> -        } else {
>> -            error_report_err(err);
>> -        }
>> +        error_report_err(err);
>> +        return NULL;
>> +    }
>> +
>> +    if (qemu_opt_has_help_opt(opts) && !opts_accepts_any(list)) {
>> +        qemu_opts_print_help(list, true);
>
> qemu_opts_del(opts) missing

Indeed. Thanks!

Reply via email to