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