On Tue, Mar 25, 2025 at 8:16 AM Alec Brown via Grub-devel
<grub-devel@gnu.org> wrote:
>
> A Unified Kernel Image is a single UEFI PE file that combines a UEFI boot 
> stub,
> a Linux kernel image, an initrd, and further resources. The uki command will
> locate where the uki file is and create a GRUB menu entry to load it.
>
> Signed-off-by: Alec Brown <alec.r.br...@oracle.com>

Nice to see someone picking that back up. I had proposed something
similar a while ago,
although over at the rhboot grub, because it already had blscfg
support back then.
https://github.com/rhboot/grub2/pull/119

Oliver

> ---
>  docs/grub.texi              |  26 +++
>  grub-core/commands/blsuki.c | 415 ++++++++++++++++++++++++++++++++++--
>  include/grub/menu.h         |   2 +
>  3 files changed, 428 insertions(+), 15 deletions(-)
>
> diff --git a/docs/grub.texi b/docs/grub.texi
> index 19b0cc024..9317b4130 100644
> --- a/docs/grub.texi
> +++ b/docs/grub.texi
> @@ -6491,6 +6491,7 @@ you forget a command, you can run the command 
> @command{help}
>  * tpm2_key_protector_clear::    Clear the TPM2 key protector
>  * true::                        Do nothing, successfully
>  * trust::                       Add public key to list of trusted keys
> +* uki::                         Load Unified Kernel Image menu entries
>  * unset::                       Unset an environment variable
>  @comment * vbeinfo::                     List available video modes
>  * verify_detached::             Verify detached digital signature
> @@ -8164,6 +8165,31 @@ Unset the environment variable @var{envvar}.
>  @end deffn
>
>
> +@node uki
> +@subsection uki
> +
> +@deffn Command uki [@option{--path} dir] [@option{--show-default}] 
> [@option{--show-non-default}] [@option{--entry} file]
> +Load Unified Kernel Image entries into the GRUB menu.
> +
> +The @option{--path} option overrides the default path to the directory 
> containing
> +the UKI entries. If this option isn't used, the default location is
> +/EFI/Linux in the EFI system partition.
> +
> +The @option{--show-default} option allows the default boot entry to be added 
> to the
> +GRUB menu from the UKI entries.
> +
> +The @option{--show-non-default} option allows non-default boot entries to be 
> added to
> +the GRUB menu from the UKI entries.
> +
> +The @option{--entry} option allows specific boot entries to be added to the 
> GRUB menu
> +from the UKI entries.
> +
> +The @option{--entry}, @option{--show-default}, and 
> @option{--show-non-default} options
> +are used to filter which UKI entries are added to the GRUB menu. If none are
> +used, all entries in the default location or the location specified by 
> @option{--path}
> +will be added to the GRUB menu.
> +@end deffn
> +
>  @ignore
>  @node vbeinfo
>  @subsection vbeinfo
> diff --git a/grub-core/commands/blsuki.c b/grub-core/commands/blsuki.c
> index 12a9a1ed1..bf284e002 100644
> --- a/grub-core/commands/blsuki.c
> +++ b/grub-core/commands/blsuki.c
> @@ -39,9 +39,21 @@
>  #define GRUB_BOOT_DEVICE ""
>  #endif
>
> +#ifdef GRUB_MACHINE_EFI
> +#include <grub/efi/efi.h>
> +#include <grub/efi/disk.h>
> +#include <grub/efi/pe32.h>
> +#endif
> +
>  GRUB_MOD_LICENSE ("GPLv3+");
>
>  #define GRUB_BLS_CONFIG_PATH "/loader/entries/"
> +#define GRUB_UKI_CONFIG_PATH "/EFI/Linux"
> +
> +#define GRUB_BLS_CMD 1
> +#define GRUB_UKI_CMD 2
> +
> +static int cmd_type = 0;
>
>  static const struct grub_arg_option bls_opt[] =
>    {
> @@ -52,6 +64,17 @@ static const struct grub_arg_option bls_opt[] =
>      {0, 0, 0, 0, 0, 0}
>    };
>
> +#ifdef GRUB_MACHINE_EFI
> +static const struct grub_arg_option uki_opt[] =
> +  {
> +    {"path", 'p', 0, N_("Specify path to find UKI entries."), N_("DIR"), 
> ARG_TYPE_PATHNAME},
> +    {"show-default", 'd', 0, N_("Allow the default UKI entry to be added to 
> the GRUB menu."), 0, ARG_TYPE_NONE},
> +    {"show-non-default", 'n', 0, N_("Allow the non-default UKI entries to be 
> added to the GRUB menu."), 0, ARG_TYPE_NONE},
> +    {"entry", 'e', 0, N_("Allow specificUKII entries to be added to the GRUB 
> menu."), N_("FILE"), ARG_TYPE_FILE},
> +    {0, 0, 0, 0, 0, 0}
> +  };
> +#endif
> +
>  struct keyval
>  {
>    const char *key;
> @@ -288,6 +311,206 @@ bls_read_entry (grub_file_t f, grub_blsuki_entry_t 
> *entry)
>    return err;
>  }
>
> +#ifdef GRUB_MACHINE_EFI
> +static grub_err_t
> +uki_read_entry (grub_file_t f, grub_blsuki_entry_t *entry)
> +{
> +  struct grub_msdos_image_header *dos = NULL;
> +  struct grub_pe_image_header *pe = NULL;
> +  grub_off_t section_offset = 0;
> +  struct grub_pe32_section_table *section = NULL;
> +  struct grub_pe32_coff_header *coff_header = NULL;
> +  char *val = NULL;
> +  char *key = NULL;
> +  const char *target[] = {".cmdline", ".osrel", ".linux", NULL};
> +  bool has_linux = false;
> +  grub_err_t err = GRUB_ERR_NONE;
> +
> +  dos = grub_zalloc (sizeof (*dos));
> +  if (dos == NULL)
> +    return grub_errno;
> +  if (grub_file_read (f, dos, sizeof (*dos)) < (grub_ssize_t) sizeof (*dos))
> +    {
> +      err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read UKI image 
> header");
> +      goto fail;
> +    }
> +  if (dos->msdos_magic != GRUB_PE32_MAGIC)
> +    {
> +      err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, N_("plain image kernel 
> not supported"));
> +      goto fail;
> +    }
> +
> +  grub_dprintf ("blsuki", "PE/COFF header @ %08x\n", 
> dos->pe_image_header_offset);
> +  pe = grub_zalloc (sizeof (*pe));
> +  if (pe == NULL)
> +    {
> +      err = grub_errno;
> +      goto fail;
> +    }
> +  if (grub_file_seek (f, dos->pe_image_header_offset) == (grub_off_t) -1
> +      || grub_file_read (f, pe, sizeof (*pe)) != sizeof (*pe))
> +    {
> +      err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read COFF image 
> header");
> +      goto fail;
> +    }
> +  if (pe->optional_header.magic != GRUB_PE32_NATIVE_MAGIC)
> +    {
> +      err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "non-native image not 
> supported");
> +      goto fail;
> +    }
> +
> +  coff_header = &(pe->coff_header);
> +  section_offset = dos->pe_image_header_offset + sizeof (*pe);
> +
> +  for (int i = 0; i < coff_header->num_sections; i++)
> +    {
> +      key = NULL;
> +      val = NULL;
> +      section = grub_zalloc (sizeof (*section));
> +      if (section == NULL)
> +       {
> +         err = grub_errno;
> +         goto fail;
> +       }
> +
> +      grub_file_seek (f, section_offset);
> +      if (grub_file_read (f, section, sizeof (*section)) != sizeof 
> (*section))
> +       {
> +         err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read section 
> header");
> +         goto fail;
> +       }
> +
> +      key = grub_strndup (section->name, 8);
> +      if (key == NULL)
> +       {
> +         err = grub_errno;
> +         goto fail;
> +       }
> +
> +      for (int j = 0; target[j] != NULL; j++)
> +       {
> +         if (grub_strcmp (key, target[j]) == 0)
> +           {
> +             /*
> +              * We don't need to read the contents of the .linux PE section, 
> but we
> +              * should verify that the section exists.
> +              */
> +             if (grub_strcmp (key, ".linux") == 0)
> +               {
> +                 has_linux = true;
> +                 break;
> +               }
> +
> +             val = grub_zalloc (section->raw_data_size);
> +             if (val == NULL)
> +               {
> +                 err = grub_errno;
> +                 goto fail;
> +               }
> +
> +             grub_file_seek (f, section->raw_data_offset);
> +             if (grub_file_read (f, val, section->raw_data_size) != 
> (grub_ssize_t) section->raw_data_size)
> +               {
> +                 err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read 
> section");
> +                 goto fail;
> +               }
> +
> +             err = blsuki_add_keyval (entry, key, val);
> +             if (err != GRUB_ERR_NONE)
> +               goto fail;
> +
> +             break;
> +           }
> +       }
> +
> +      section_offset += sizeof (*section);
> +      grub_free (section);
> +      grub_free (val);
> +      grub_free (key);
> +    }
> +
> +  if (has_linux == false)
> +    err = grub_error (GRUB_ERR_NO_KERNEL, "UKI is missing the '.linux' 
> section");
> +
> +  grub_free (dos);
> +  grub_free (pe);
> +  return err;
> +
> + fail:
> +  grub_free (dos);
> +  grub_free (pe);
> +  grub_free (section);
> +  grub_free (val);
> +  grub_free (key);
> +  return err;
> +}
> +#endif
> +
> +static char *
> +uki_read_osrel (char *content, grub_off_t *pos, char **key_ret, char 
> **val_ret)
> +{
> +  char *line;
> +  char *value;
> +  grub_size_t linelen;
> +
> + skip:
> +  line = content + *pos;
> +  if (*line == '\0')
> +    return NULL;
> +
> +  linelen = 0;
> +  while (line[linelen] != '\0' && !grub_strchr ("\n\r", line[linelen]))
> +    linelen++;
> +
> +  /* Move pos to the next line */
> +  *pos += linelen;
> +  if (content[*pos] != '\0')
> +    (*pos)++;
> +
> +  /* Skip empty line */
> +  if (linelen == 0)
> +    goto skip;
> +
> +  line[linelen] = '\0';
> +
> +  /* Remove leading white space */
> +  while (grub_strchr (" \t", *line))
> +    {
> +      line++;
> +      linelen--;
> +    }
> +
> +  /* Remove trailing whitespace */
> +  while (linelen > 0 && grub_strchr ("=", line[linelen - 1]))
> +    linelen--;
> +  line[linelen] = '\0';
> +
> +  if (*line == '#')
> +    goto skip;
> +
> +  /* Split key/value */
> +  value = line;
> +  while (*value != '\0' && !grub_strchr ("=", *value))
> +    value++;
> +  if (*value == '\0')
> +    goto skip;
> +  *value = '\0';
> +  value++;
> +  while (*value != '\0' && grub_strchr ("=", *value))
> +    value++;
> +
> +  /* Remove quotes from value */
> +  if (value[0] == '\"' && line[linelen - 1] == '\"')
> +    {
> +      value++;
> +      line[linelen - 1] = '\0';
> +    }
> +
> +  *key_ret = line;
> +  *val_ret = value;
> +  return line;
> +}
> +
>  struct read_entry_info
>  {
>    const char *devid;
> @@ -301,9 +524,11 @@ blsuki_read_entry (const char *filename,
>                    void *data)
>  {
>    grub_size_t m = 0, n, ext_len = 5;
> -  grub_err_t err;
> +  grub_err_t err = GRUB_ERR_NONE;
>    char *p = NULL;
> +  const char *ext = NULL;
>    grub_file_t f = NULL;
> +  enum grub_file_type file_type = 0;
>    grub_blsuki_entry_t *entry;
>    struct read_entry_info *info = (struct read_entry_info *) data;
>
> @@ -311,6 +536,18 @@ blsuki_read_entry (const char *filename,
>
>    n = grub_strlen (filename);
>
> +  if (cmd_type == GRUB_BLS_CMD)
> +    {
> +      ext = ".conf";
> +      file_type = GRUB_FILE_TYPE_CONFIG;
> +    }
> +  else if (cmd_type == GRUB_UKI_CMD)
> +    {
> +      ext = ".efi";
> +      file_type = GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE;
> +    }
> +  ext_len = grub_strlen (ext);
> +
>    if (info->file != NULL)
>      {
>        f = info->file;
> @@ -320,15 +557,14 @@ blsuki_read_entry (const char *filename,
>        if (filename[0] == '.')
>         return 0;
>
> -      if (n <= 5)
> +      if (n <= ext_len)
>         return 0;
>
> -      if (grub_strcmp (filename + n - ext_len, ".conf") != 0)
> +      if (grub_strcmp (filename + n - ext_len, ext) != 0)
>         return 0;
>
>        p = grub_xasprintf ("(%s)%s/%s", info->devid, info->dirname, filename);
> -
> -      f = grub_file_open (p, GRUB_FILE_TYPE_CONFIG);
> +      f = grub_file_open (p, file_type);
>        grub_free (p);
>        if (f == NULL)
>         goto finish;
> @@ -365,7 +601,26 @@ blsuki_read_entry (const char *filename,
>        goto finish;
>      }
>
> -  err = bls_read_entry (f, entry);
> +  entry->dirname = grub_strdup (info->dirname);
> +  if (entry->dirname == NULL)
> +    {
> +      grub_free (entry);
> +      goto finish;
> +    }
> +
> +  entry->devid = grub_strdup (info->devid);
> +  if (entry->devid == NULL)
> +    {
> +      grub_free (entry);
> +      goto finish;
> +    }
> +
> +  if (cmd_type == GRUB_BLS_CMD)
> +    err = bls_read_entry (f, entry);
> +#ifdef GRUB_MACHINE_EFI
> +  else if (cmd_type == GRUB_UKI_CMD)
> +    err = uki_read_entry (f, entry);
> +#endif
>
>    if (err == GRUB_ERR_NONE)
>      blsuki_add_entry (entry);
> @@ -810,6 +1065,75 @@ bls_create_entry (grub_blsuki_entry_t *entry)
>    grub_free (src);
>  }
>
> +static void
> +uki_create_entry (grub_blsuki_entry_t *entry)
> +{
> +  int argc = 0;
> +  const char **argv = NULL;
> +  char *id = entry->filename;
> +  char *title = NULL;
> +  char *options = NULL;
> +  char *osrel = NULL;
> +  char *line;
> +  char *key = NULL;
> +  char *value = NULL;
> +  char *src = NULL;
> +  grub_size_t size = 0;
> +  grub_off_t pos = 0;
> +
> +  options = blsuki_get_val (entry, ".cmdline", NULL);
> +  if (options == NULL)
> +    {
> +      grub_dprintf ("blsuki", "Skipping file %s with no '.cmdline' key.\n", 
> entry->filename);
> +      goto finish;
> +    }
> +  osrel = blsuki_get_val (entry, ".osrel", NULL);
> +  if (osrel == NULL)
> +    {
> +      grub_dprintf ("blsuki", "Skipping file %s with no '.osrel' key.\n", 
> entry->filename);
> +      goto finish;
> +    }
> +
> +  line = osrel;
> +  while ((line = uki_read_osrel (osrel, &pos, &key, &value)))
> +    {
> +      if (grub_strcmp ("PRETTY_NAME", key) == 0)
> +       {
> +         title = value;
> +         break;
> +       }
> +    }
> +
> +  argc += 1;
> +  if (grub_mul (argc + 1, sizeof (char *), &size))
> +    {
> +      grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow detected creating argv 
> list"));
> +      goto finish;
> +    }
> +  argv = grub_malloc (size);
> +  if (argv == NULL)
> +    {
> +      grub_error (GRUB_ERR_OUT_OF_MEMORY, "failed to allocate argv list");
> +      goto finish;
> +    }
> +  argv[0] = title;
> +  argv[argc] = NULL;
> +
> +  src = grub_xasprintf ("insmod chain\n"
> +                       "chainloader (%s)%s/%s%s%s\n",
> +                       entry->devid, entry->dirname,
> +                       entry->filename, options ? " " : "", options ? 
> options : "");
> +
> +
> +  grub_normal_add_menu_entry (argc, argv, NULL, id, NULL, NULL, NULL, src, 
> 0, entry);
> +
> + finish:
> +  grub_free (argv);
> +  grub_free (src);
> +  grub_free (options);
> +  grub_free (osrel);
> +}
> +
>  struct find_entry_info
>  {
>    const char *dirname;
> @@ -830,7 +1154,12 @@ blsuki_find_entry (struct find_entry_info *info)
>    int r = 0;
>
>    if (dir == NULL)
> -    dir = GRUB_BLS_CONFIG_PATH;
> +    {
> +      if (cmd_type == GRUB_BLS_CMD)
> +       dir = GRUB_BLS_CONFIG_PATH;
> +      else if (cmd_type == GRUB_UKI_CMD)
> +       dir = GRUB_UKI_CONFIG_PATH;
> +    }
>
>    read_entry_info.file = NULL;
>    read_entry_info.dirname = dir;
> @@ -853,11 +1182,17 @@ blsuki_find_entry (struct find_entry_info *info)
>    /*
>     * If we aren't able to find BLS entries in the directory given by 
> info->dirname,
>     * we can fallback to the default location "/boot/loader/entries/" and see 
> if we
> -   * can find the files there.
> +   * can find the files there. If we can't find UKI entries, fallback to
> +   * "/boot/efi/EFI/Linux".
>     */
>    if (r != 0 && info->dirname == NULL && fallback == 0)
>      {
> -      read_entry_info.dirname = "/boot" GRUB_BLS_CONFIG_PATH;
> +      if (cmd_type == GRUB_BLS_CMD)
> +       read_entry_info.dirname = "/boot" GRUB_BLS_CONFIG_PATH;
> +      else if (cmd_type == GRUB_UKI_CMD)
> +       {
> +         read_entry_info.dirname = GRUB_UKI_CONFIG_PATH;
> +       }
>        grub_dprintf ("blsuki", "Entries weren't found in %s, fallback to 
> %s\n",
>                     dir, read_entry_info.dirname);
>        fallback = 1;
> @@ -869,11 +1204,13 @@ static grub_err_t
>  blsuki_load_entries (char *path)
>  {
>    grub_size_t len;
> +  grub_size_t ext_len;
>    grub_fs_t fs;
>    grub_device_t dev;
>    static grub_err_t r;
>    const char *devid = NULL;
>    char *dir = NULL;
> +  const char *ext = NULL;
>    struct find_entry_info info = {
>        .dev = NULL,
>        .fs = NULL,
> @@ -886,8 +1223,14 @@ blsuki_load_entries (char *path)
>
>    if (path != NULL)
>      {
> +      if (cmd_type == GRUB_BLS_CMD)
> +       ext = ".conf";
> +      else if (cmd_type == GRUB_UKI_CMD)
> +       ext = ".efi";
> +
>        len = grub_strlen (path);
> -      if (grub_strcmp (path + len - 5, ".conf") == 0)
> +      ext_len = grub_strlen (ext);
> +      if (grub_strcmp (path + len - ext_len, ext) == 0)
>         {
>           rei.file = grub_file_open (path, GRUB_FILE_TYPE_CONFIG);
>           if (rei.file == NULL)
> @@ -916,11 +1259,22 @@ blsuki_load_entries (char *path)
>
>    if (devid == NULL)
>      {
> +      if (cmd_type == GRUB_BLS_CMD)
> +       {
>  #ifdef GRUB_MACHINE_EMU
> -      devid = "host";
> +         devid = "host";
>  #else
> -      devid = grub_env_get ("root");
> +         devid = grub_env_get ("root");
>  #endif
> +       }
> +      else if (cmd_type == GRUB_UKI_CMD)
> +       {
> +#ifdef GRUB_MACHINE_EFI
> +         grub_efi_loaded_image_t *image;
> +         image = grub_efi_get_loaded_image (grub_efi_image_handle);
> +         devid = grub_efidisk_get_device_name (image->device_handle);
> +#endif
> +       }
>        if (devid == NULL)
>         return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't 
> set"), "root");
>      }
> @@ -1001,7 +1355,10 @@ blsuki_create_entries (bool show_default, bool 
> show_non_default, char *entry_id)
>           (show_non_default == true && blsuki_is_default_entry (def_entry, 
> entry, idx) == false) ||
>           (entry_id != NULL && grub_strcmp (entry_id, entry->filename) == 0))
>         {
> -         bls_create_entry (entry);
> +         if (cmd_type == GRUB_BLS_CMD)
> +           bls_create_entry (entry);
> +         else if (cmd_type == GRUB_UKI_CMD)
> +           uki_create_entry (entry);
>           entry->visible = 1;
>         }
>        idx++;
> @@ -1011,8 +1368,7 @@ blsuki_create_entries (bool show_default, bool 
> show_non_default, char *entry_id)
>  }
>
>  static grub_err_t
> -grub_cmd_blscfg (grub_extcmd_context_t ctxt, int argc __attribute__ 
> ((unused)),
> -                char **args __attribute__ ((unused)))
> +blsuki_cmd (grub_extcmd_context_t ctxt)
>  {
>    grub_err_t err;
>    struct grub_arg_list *state = ctxt->state;
> @@ -1021,6 +1377,7 @@ grub_cmd_blscfg (grub_extcmd_context_t ctxt, int argc 
> __attribute__ ((unused)),
>    bool show_default = false;
>    bool show_non_default = false;
>    bool all = true;
> +  entries = NULL;
>
>    if (state[0].set)
>      path = state[0].arg;
> @@ -1052,17 +1409,45 @@ grub_cmd_blscfg (grub_extcmd_context_t ctxt, int argc 
> __attribute__ ((unused)),
>    return blsuki_create_entries (show_default, show_non_default, entry_id);
>  }
>
> +static grub_err_t
> +grub_cmd_blscfg (grub_extcmd_context_t ctxt, int argc __attribute__ 
> ((unused)),
> +                char **args __attribute__ ((unused)))
> +{
> +  cmd_type = GRUB_BLS_CMD;
> +  return blsuki_cmd (ctxt);
> +}
> +
>  static grub_extcmd_t bls_cmd;
>
> +#ifdef GRUB_MACHINE_EFI
> +static grub_err_t
> +grub_cmd_uki (grub_extcmd_context_t ctxt, int argc __attribute__ ((unused)),
> +                char **args __attribute__ ((unused)))
> +{
> +  cmd_type = GRUB_UKI_CMD;
> +  return blsuki_cmd (ctxt);
> +}
> +
> +static grub_extcmd_t uki_cmd;
> +#endif
> +
>  GRUB_MOD_INIT(blsuki)
>  {
>    bls_cmd = grub_register_extcmd ("blscfg", grub_cmd_blscfg, 0,
>                                   N_("[-p|--path] DIR [-d|--show-default] 
> [-n|--show-non-default] [-e|--entry] FILE"),
>                                   N_("Import Boot Loader Specification 
> snippets."),
>                                   bls_opt);
> +#ifdef GRUB_MACHINE_EFI
> +  uki_cmd = grub_register_extcmd ("uki", grub_cmd_uki, 0,
> +                                 N_("[-p|--path] DIR [-d|--show-default] 
> [-n|--show-non-default] [-e|--entry] FILE"),
> +                                 N_("Import Unified Kernel Images"), 
> uki_opt);
> +#endif
>  }
>
>  GRUB_MOD_FINI(blsuki)
>  {
>    grub_unregister_extcmd (bls_cmd);
> +#ifdef GRUB_MACHINE_EFI
> +  grub_unregister_extcmd (uki_cmd);
> +#endif
>  }
> diff --git a/include/grub/menu.h b/include/grub/menu.h
> index c25a0d16d..907373625 100644
> --- a/include/grub/menu.h
> +++ b/include/grub/menu.h
> @@ -28,6 +28,8 @@ struct grub_blsuki_entry
>    grub_size_t keyvals_size;
>    int nkeyvals;
>    char *filename;
> +  char *dirname;
> +  char *devid;
>    int visible;
>  };
>  typedef struct grub_blsuki_entry grub_blsuki_entry_t;
> --
> 2.27.0
>
>
> _______________________________________________
> Grub-devel mailing list
> Grub-devel@gnu.org
> https://lists.gnu.org/mailman/listinfo/grub-devel


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

Reply via email to