From 4f3a3b769549a7a2f788e06156b32afa3633a124 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 12 Feb 2025 09:31:29 +0100 Subject: [PATCH] sd-boot: also read type #1 entries from SMBIOS Type #11 With this we can now do: systemd-vmspawn -n -i foobar.raw -s io.systemd.boot.entries-extra:particleos-current.conf=$'title ParticleOS Current\nuki-url http://example.com/somedir/uki.efi' Assuming sd-boot is available inside the ESP of foobar.raw a new item will show up in the boot menu that allows booting directly into the specified UKI. --- man/smbios-type-11.xml | 10 +++++++ man/systemd-boot.xml | 22 +++++++++++++++ src/boot/boot.c | 63 ++++++++++++++++++++++++++++++++++++------ src/boot/smbios.c | 8 +++--- src/boot/smbios.h | 2 +- src/boot/stub.c | 2 +- 6 files changed, 93 insertions(+), 14 deletions(-) diff --git a/man/smbios-type-11.xml b/man/smbios-type-11.xml index 4d78f612e7e..b576677d276 100644 --- a/man/smbios-type-11.xml +++ b/man/smbios-type-11.xml @@ -74,6 +74,16 @@ + + + io.systemd.boot-entries.extra:ID=DEFINITION + + This allows inserting additional entries into the systemd-boot + menu. For details see + systemd-boot7 + + + diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 22f3cdb4cae..6da27145810 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -594,6 +594,28 @@ + + + io.systemd.boot-entries.extra:ID=DEFINITION + + This allows inserting additional entries into the systemd-boot + menu. Take a pair of menu entry identifier and menu entry definition string. The former should be + suitable for use as a filename of a Boot Loader Specification Type #1 entry filename (note that it is + used for identification purposes only, no file of this name is actually accessed), the latter shall + follow the syntax of the contents of a Type #1 entry. Any menu entry defined this way is processed + and shown in pretty much the same way as a Type #1 entry read from the ESP or XBOOTLDR + partition. Example: + + io.systemd.boot-entries.extra:fooos-current.conf=title FooOS (Current) +uki-url http://example.com/somedir/fooos.efi + + Note that this example contains a newline character. When generating this string from a shell + care must be taken to encode it correctly. + + Pass multiple strings formatted this way to generate multiple menu entries. + + + diff --git a/src/boot/boot.c b/src/boot/boot.c index 980fcff7c32..75f764bc4e1 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1464,7 +1464,6 @@ static void boot_entry_add_type1( assert(config); assert(device); assert(root_dir); - assert(path); assert(file); assert(content); @@ -1607,7 +1606,8 @@ static void boot_entry_add_type1( config_add_entry(config, entry); - boot_entry_parse_tries(entry, path, file, u".conf"); + if (path) + boot_entry_parse_tries(entry, path, file, u".conf"); TAKE_PTR(entry); } @@ -1717,6 +1717,19 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) { (void) efivar_get_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", &config->entry_saved); } +static bool valid_type1_filename(const char16_t *fname) { + assert(fname); + + if (IN_SET(fname[0], u'.', u'\0')) + return false; + if (!endswith_no_case(fname, u".conf")) + return false; + if (startswith_no_case(fname, u"auto-")) + return false; + + return true; +} + static void config_load_type1_entries( Config *config, EFI_HANDLE *device, @@ -1747,13 +1760,9 @@ static void config_load_type1_entries( if (err != EFI_SUCCESS || !f) break; - if (f->FileName[0] == '.') - continue; if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY)) continue; - if (!endswith_no_case(f->FileName, u".conf")) - continue; - if (startswith_no_case(f->FileName, u"auto-")) + if (!valid_type1_filename(f->FileName)) continue; err = file_read(entries_dir, @@ -1769,6 +1778,41 @@ static void config_load_type1_entries( } } +static void config_load_smbios_entries( + Config *config, + EFI_HANDLE *device, + EFI_FILE *root_dir, + const char16_t *loaded_image_path) { + + assert(config); + assert(device); + assert(root_dir); + + /* Loads Boot Loader Type #1 entries from SMBIOS 11 */ + + if (is_confidential_vm()) + return; /* Don't consume SMBIOS in CoCo contexts */ + + for (const char *after = NULL, *extra;; after = extra) { + extra = smbios_find_oem_string("io.systemd.boot.entries-extra:", after); + if (!extra) + break; + + const char *eq = strchr8(extra, '='); + if (!eq) + continue; + + _cleanup_free_ char16_t *fname = xstrn8_to_16(extra, eq - extra); + if (!valid_type1_filename(fname)) + continue; + + /* Make a copy, since boot_entry_add_type1() wants to modify it */ + _cleanup_free_ char *contents = xstrdup8(eq + 1); + + boot_entry_add_type1(config, device, root_dir, /* path= */ NULL, fname, contents, loaded_image_path); + } +} + static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { int r; @@ -2775,7 +2819,7 @@ static EFI_STATUS image_start( _cleanup_free_ char16_t *options = xstrdup16(options_initrd ?: entry->options_implied ? NULL : entry->options); if (entry->type == LOADER_LINUX && !is_confidential_vm()) { - const char *extra = smbios_find_oem_string("io.systemd.boot.kernel-cmdline-extra"); + const char *extra = smbios_find_oem_string("io.systemd.boot.kernel-cmdline-extra=", /* after= */ NULL); if (extra) { _cleanup_free_ char16_t *tmp = TAKE_PTR(options), *extra16 = xstr8_to_16(extra); if (isempty(tmp)) @@ -2989,6 +3033,9 @@ static void config_load_all_entries( /* Similar, but on any XBOOTLDR partition */ config_load_xbootldr(config, loaded_image->DeviceHandle); + /* Pick up entries defined via SMBIOS Type #11 */ + config_load_smbios_entries(config, loaded_image->DeviceHandle, root_dir, loaded_image_path); + /* Sort entries after version number */ sort_pointer_array((void **) config->entries, config->n_entries, (compare_pointer_func_t) boot_entry_compare); diff --git a/src/boot/smbios.c b/src/boot/smbios.c index e1bcd62e2c7..184aca14ce2 100644 --- a/src/boot/smbios.c +++ b/src/boot/smbios.c @@ -182,7 +182,7 @@ bool smbios_in_hypervisor(void) { return FLAGS_SET(type0->bios_characteristics_ext[1], 1 << 4); } -const char* smbios_find_oem_string(const char *name) { +const char* smbios_find_oem_string(const char *name, const char *after) { uint64_t left; assert(name); @@ -199,9 +199,9 @@ const char* smbios_find_oem_string(const char *name) { if (!e || e == p) /* Double NUL byte means we've reached the end of the OEM strings. */ break; - const char *eq = startswith8(p, name); - if (eq && *eq == '=') - return eq + 1; + const char *suffix = startswith8(p, name); + if (suffix && (!after || suffix > after)) + return suffix; p = e + 1; } diff --git a/src/boot/smbios.h b/src/boot/smbios.h index 83c3c435898..694ef568e68 100644 --- a/src/boot/smbios.h +++ b/src/boot/smbios.h @@ -5,7 +5,7 @@ bool smbios_in_hypervisor(void); -const char* smbios_find_oem_string(const char *name); +const char* smbios_find_oem_string(const char *name, const char *after); typedef struct RawSmbiosInfo { const char *manufacturer; diff --git a/src/boot/stub.c b/src/boot/stub.c index ac6c1124590..4793391b06d 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -783,7 +783,7 @@ static void cmdline_append_and_measure_smbios(char16_t **cmdline, int *parameter if (is_confidential_vm()) return; - const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra"); + const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra=", /* after= */ NULL); if (!extra) return; -- 2.47.3