First of all, next time please add Xen-devel ML to recipients too...

On Wed, Apr 23, 2025 at 09:47:48PM -0500, Aaron Rainbolt wrote:
> Enable GRUB to parse the Xen command line for parameters, and expose
> certain of those parameters to the GRUB config file (or rescue shell)
> as environment variables.

Please make the commit message more verbose, i.e., explain why this
feature it is needed, how it works, etc.

> ---
>  grub-core/Makefile.core.def   |   2 +
>  grub-core/kern/i386/xen/pvh.c |  16 ++
>  grub-core/kern/main.c         |  12 ++
>  grub-core/kern/xen/cmdline.c  | 270 ++++++++++++++++++++++++++++++++++
>  include/grub/xen.h            |   2 +

I think this feature should have a description in the GRUB docs.

>  5 files changed, 302 insertions(+)
>  create mode 100644 grub-core/kern/xen/cmdline.c
>
> diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
> index f70e02e69..79e681a7b 100644
> --- a/grub-core/Makefile.core.def
> +++ b/grub-core/Makefile.core.def
> @@ -248,6 +248,7 @@ kernel = {
>    xen = term/xen/console.c;
>    xen = disk/xen/xendisk.c;
>    xen = commands/boot.c;
> +  xen = kern/xen/cmdline.c;
>
>    i386_xen_pvh = commands/boot.c;
>    i386_xen_pvh = disk/xen/xendisk.c;
> @@ -255,6 +256,7 @@ kernel = {
>    i386_xen_pvh = kern/i386/xen/tsc.c;
>    i386_xen_pvh = kern/i386/xen/pvh.c;
>    i386_xen_pvh = kern/xen/init.c;
> +  i386_xen_pvh = kern/xen/cmdline.c;
>    i386_xen_pvh = term/xen/console.c;
>
>    ia64_efi = kern/ia64/efi/startup.S;
> diff --git a/grub-core/kern/i386/xen/pvh.c b/grub-core/kern/i386/xen/pvh.c
> index 91fbca859..9fb048082 100644
> --- a/grub-core/kern/i386/xen/pvh.c
> +++ b/grub-core/kern/i386/xen/pvh.c
> @@ -352,6 +352,22 @@ grub_xen_setup_pvh (void)
>    grub_xen_mm_init_regions ();
>
>    grub_rsdp_addr = pvh_start_info->rsdp_paddr;
> +  int xen_cmdline_fits = 0;

We have bool type in the GRUB.

> +  char *xen_cmdline = (char *) pvh_start_info->cmdline_paddr;

const char?

And please add an empty line here.

> +  for (int i = 0; i < 1024; i++)

1024? Where this number come from? And I suggest using constants where
possible.

Additionally, please do not mix code with variable definitions. Define
all variables at the beginning of a function or block.

> +    {
> +      if (xen_cmdline[i] == '\0')
> +        {
> +          xen_cmdline_fits = 1;
> +          break;
> +        }
> +    }
> +  if (xen_cmdline_fits == 1)

Probably this and xen_cmdline_fits variable is not needed if you merge
the code below with the code above.

> +    {
> +      grub_strncpy ((char *) grub_xen_start_page_addr->cmd_line,
> +                 (char *) pvh_start_info->cmdline_paddr,
> +                 MAX_GUEST_CMDLINE);
> +    }
>  }
>
>  grub_err_t
> diff --git a/grub-core/kern/main.c b/grub-core/kern/main.c
> index 143a232b8..f96b16f73 100644
> --- a/grub-core/kern/main.c
> +++ b/grub-core/kern/main.c
> @@ -40,6 +40,10 @@
>  static bool cli_disabled = false;
>  static bool cli_need_auth = false;
>
> +#if defined (GRUB_MACHINE_XEN) || defined (GRUB_MACHINE_XEN_PVH)
> +#include <grub/xen.h>
> +#endif
> +
>  grub_addr_t
>  grub_modules_get_end (void)
>  {
> @@ -351,6 +355,14 @@ grub_main (void)
>    grub_env_export ("root");
>    grub_env_export ("prefix");
>
> +  /*
> +   * Parse command line parameters from Xen and export them as environment
> +   * variables
> +   */
> +#if defined (GRUB_MACHINE_XEN) || defined (GRUB_MACHINE_XEN_PVH)
> +  grub_parse_xen_cmdline ();
> +#endif
> +
>    /* Reclaim space used for modules.  */
>    reclaim_module_space ();
>
> diff --git a/grub-core/kern/xen/cmdline.c b/grub-core/kern/xen/cmdline.c
> new file mode 100644
> index 000000000..0084f3e77
> --- /dev/null
> +++ b/grub-core/kern/xen/cmdline.c
> @@ -0,0 +1,270 @@
> +/*
> + *  GRUB  --  GRand Unified Bootloader
> + *  Copyright (C) 2025  Free Software Foundation, Inc.
> + *
> + *  GRUB is free software: you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation, either version 3 of the License, or
> + *  (at your option) any later version.
> + *
> + *  GRUB is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <grub/env.h>
> +#include <grub/misc.h>
> +#include <grub/mm.h>
> +#include <grub/xen.h>
> +
> +#define PARSER_HIT_BACKSLASH 0x1
> +#define PARSER_IN_SINGLE_QUOTES 0x2
> +#define PARSER_IN_DOUBLE_QUOTES 0x4
> +#define PARSER_BASE_WORD_LEN 16

I think it could be enum type.

> +grub_size_t word_list_len;
> +char **word_list;
> +grub_size_t current_word_len;
> +grub_size_t current_word_pos;
> +char *current_word;
> +char current_char;
> +grub_size_t param_dict_len;
> +char **param_keys;
> +char **param_vals;

Probably all of this should be static.

> +static bool
> +append_char_to_word (bool allow_null)
> +{
> +  if (!(current_char >= 0x20) || !(current_char <= 0x7E))

I know what you mean but please comment why you accept only limited set
of characters.

> +    {
> +      if (current_char != '\0' || !allow_null)

allow_null == false please here and in other places too...

> +        {
> +          return false;
> +        }

Please drop unneeded curly brackets...

> +    }
> +  if (current_word_pos == current_word_len)
> +    {
> +      current_word_len *= 2;
> +      current_word = grub_realloc (current_word, current_word_len);

grub_realloc() may fail...

> +    }
> +
> +  current_word[current_word_pos] = current_char;
> +  current_word_pos++;
> +  return true;
> +}
> +
> +static void
> +append_word_to_list (void)
> +{
> +  for (grub_size_t i = 0; i < current_word_pos; i++)

Again, please do not mix variable definitions with the code.

> +    {
> +      if (current_word[i] <= 0x1F || current_word[i] >= 0x7F)
> +        {
> +          grub_free (current_word);
> +          goto reset_word;
> +        }
> +    }
> +  current_char = '\0';
> +  if (!append_char_to_word (true))

append_char_to_word (true) == false

> +    {
> +      grub_error (GRUB_ERR_BUG, N_("couldn't append null terminator to word 
> during Xen cmdline parsing"));

Please drop N_() from here...

> +      grub_print_error ();
> +      grub_exit ();
> +    }
> +  current_word_len = grub_strlen (current_word) + 1;
> +  current_word = grub_realloc (current_word, current_word_len);

Please check for errors when you alloc/realloc memory.

> +  word_list_len++;
> +  word_list = grub_realloc (word_list, word_list_len * sizeof (char *));

Ditto.

> +  word_list[word_list_len - 1] = current_word;
> +
> +reset_word:

Missing space before label.

> +  current_word_len = PARSER_BASE_WORD_LEN;
> +  current_word_pos = 0;
> +  current_word = grub_malloc (current_word_len);

Again, missing error checks are unacceptable.

> +}
> +
> +int

s/int/bool/

> +check_key_is_safe (char *key, grub_size_t len)
> +{
> +  /*
> +   * Ensure only a-z, A-Z, and _ are allowed.
> +   */
> +  for (grub_size_t i = 0; i < len; i++)
> +  {
> +    if (! ((key[i] >= 'A' && key[i] <= 'Z')
> +          || (key[i] >= 'a' && key[i] <= 'z')
> +          || (key[i] == '_') ) )

This "if" begs for comment.

> +      {
> +        return 0;
> +      }

Redundant curly braces.

> +  }
> +  return 1;
> +}
> +
> +void
> +grub_parse_xen_cmdline (void)
> +{
> +  word_list = grub_malloc (0);

word_list = NULL;

> +  current_word_len = PARSER_BASE_WORD_LEN;
> +  current_word = grub_malloc (current_word_len);
> +  param_keys = grub_malloc (0);

Ditto.

> +  param_vals = grub_malloc (0);

Ditto.

And why these variables have to be global?

> +  grub_uint8_t parse_flags = 0;

Please use enum type instead of grub_uint8_t.

> +  char *cmdline = (char *)grub_xen_start_page_addr->cmd_line;

const char?

And missing space after ")".

> +  for (grub_size_t i = 0; i < grub_strlen (cmdline); i++)

Is cmdline always NUL terminated? Should not we impose a (sensible)
command line length limit in the GRUB code?

> +    {
> +      current_char = cmdline[i];
> +
> +      if (parse_flags & PARSER_HIT_BACKSLASH)
> +        {
> +          parse_flags ^= PARSER_HIT_BACKSLASH;
> +          if (!append_char_to_word (false))
> +            {
> +              goto cleanup;
> +            }

Please reduce curly braces to the minimum required...

> +          continue;
> +        }
> +
> +      if (current_char == '\\')

I think parser code should have comments with explanations why we treat
characters in a given way...

> +        {
> +          parse_flags ^= PARSER_HIT_BACKSLASH;
> +          continue;
> +        }
> +
> +      if (current_char == '\'')

Would not switch/case be better here?

> +        {
> +          if (parse_flags & PARSER_IN_DOUBLE_QUOTES)
> +            {
> +              if (!append_char_to_word (false))
> +                {
> +                  goto cleanup;
> +                }
> +              continue;
> +            }
> +
> +          parse_flags ^= PARSER_IN_SINGLE_QUOTES;
> +          continue;
> +        }
> +
> +      if (current_char == '"')

Ditto and below/everywhere here...

> +        {
> +          if (parse_flags & PARSER_IN_SINGLE_QUOTES)
> +            {
> +              if (!append_char_to_word (false))
> +                {
> +                  goto cleanup;
> +                }
> +              continue;
> +            }
> +
> +          parse_flags ^= PARSER_IN_DOUBLE_QUOTES;
> +          continue;
> +        }
> +
> +      if (current_char == ' ')
> +        {
> +          if (parse_flags & PARSER_IN_SINGLE_QUOTES
> +              || parse_flags & PARSER_IN_DOUBLE_QUOTES)
> +            {
> +              if (!append_char_to_word (false))
> +                {
> +                  goto cleanup;
> +                }
> +              continue;
> +            }
> +
> +          append_word_to_list ();
> +          continue;
> +        }
> +
> +      if (!append_char_to_word (false))
> +        {
> +          goto cleanup;
> +        }
> +    }
> +
> +  if (current_word_pos > 0)
> +    {
> +      append_word_to_list ();
> +    }
> +
> +  param_keys = grub_realloc (param_keys, word_list_len * sizeof (char *));
> +  param_vals = grub_realloc (param_vals, word_list_len * sizeof (char *));

Again, missing error checks. This kind of code has to be fixed everywhere.

> +  for (grub_size_t i = 0; i < word_list_len; i++)
> +    {
> +      current_word = word_list[i];
> +      current_word_len = grub_strlen (current_word) + 1;
> +      char *current_word_eq_ptr = grub_strchr (current_word, '=');

Missing empty line.

> +      if (current_word_eq_ptr)
> +        {
> +          grub_size_t eq_idx
> +              = (grub_size_t)(current_word_eq_ptr - current_word);
> +          grub_size_t pre_eq_len
> +              = current_word_len - (current_word_len - eq_idx);

I am OK with lines (a bit) longer than 80 characters.

> +          grub_size_t post_eq_len = current_word_len - eq_idx - 1;
> +          if (check_key_is_safe (current_word, pre_eq_len))
> +            {
> +              param_keys[param_dict_len] = grub_malloc (pre_eq_len + 1);
> +              param_vals[param_dict_len] = grub_malloc (post_eq_len + 1);
> +              grub_strncpy (param_keys[param_dict_len],
> +                         current_word, pre_eq_len);
> +              grub_strncpy (param_vals[param_dict_len],
> +                         current_word + pre_eq_len + 1, post_eq_len);
> +              param_keys[param_dict_len][pre_eq_len] = '\0';
> +              param_vals[param_dict_len][post_eq_len] = '\0';
> +           param_dict_len++;
> +            }
> +        }
> +      else
> +        {
> +          if (check_key_is_safe (current_word, current_word_len - 1))
> +            {
> +              param_keys[param_dict_len] = grub_malloc (current_word_len + 
> 1);
> +              param_vals[param_dict_len] = grub_malloc (2);
> +              grub_strncpy (param_keys[param_dict_len],
> +                         current_word, current_word_len);
> +              param_keys[param_dict_len][current_word_len] = '\0';
> +              grub_strcpy (param_vals[param_dict_len], "1\0");
> +           param_dict_len++;
> +            }
> +        }
> +    }
> +
> +  for (grub_size_t i = 0; i < param_dict_len; i++)
> +    {
> +      /*
> +       * Find keys that start with "xen_grub_env_" and export them
> +       * as environment variables.
> +       */
> +      if (grub_strlen (param_keys[i]) < (sizeof ("xen_grub_env_") - 1))
> +        {
> +          continue;
> +        }
> +      if (grub_strncmp (param_keys[i],
> +                     "xen_grub_env_",
> +                     sizeof ("xen_grub_env_") - 1) != 0)
> +        {
> +          continue;
> +        }
> +      grub_env_set (param_keys[i], param_vals[i]);
> +      grub_env_export (param_keys[i]);
> +      grub_free (param_keys[i]);
> +      grub_free (param_vals[i]);
> +    }
> +
> +cleanup:

Missing space before label.

Daniel

_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to