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
[email protected]
https://lists.gnu.org/mailman/listinfo/grub-devel