diff --git a/man/kernel-install.xml b/man/kernel-install.xml index 0bf57b4d53..26ae87f1d4 100644 --- a/man/kernel-install.xml +++ b/man/kernel-install.xml @@ -562,6 +562,13 @@ on the file name and extension: Files named initrd* will be installed as INITRD-FILEs, and files named microcode* will be prepended before INITRD-FILEs. + $KERNEL_INSTALL_ENTRY_NAME is set for the plugins to the resolved entry name, + derived by expanding specifiers in the entry name format string. This is the base name (without file + extension) used for UKI files, loader entry configuration files, and similar. By default this is + ENTRY-TOKEN-KERNEL-VERSION, + matching the traditional naming. See $KERNEL_INSTALL_ENTRY_NAME_FORMAT below for + details on the available specifiers. + @@ -589,6 +596,73 @@ The last two variables may also be set in install.conf. Variables set in the environment take precedence over the values specified in the config file. + + $KERNEL_INSTALL_ENTRY_NAME_FORMAT can be set to configure the format string + used to derive the entry name for boot loader entries and UKI files. The format string is expanded + using specifier expansion. May also be configured as entry_name_format= in + install.conf. The following specifiers are understood: + + + Specifiers available in <varname>$KERNEL_INSTALL_ENTRY_NAME_FORMAT</varname> + + + + + + + Specifier + Meaning + Details + + + + + + + + %e + Entry token + The entry token, same as $KERNEL_INSTALL_ENTRY_TOKEN. + + + + + + + + + %v + Kernel version + The kernel version being installed. Note that unlike the common specifier %v which expands to the currently running kernel version (i.e. uname -r), here it refers to the target kernel version passed to kernel-install. + + + + + + +
+ + The resolved entry name must be a valid filename (no path separators). Defaults to + %e-%v, which produces the traditional + ENTRY-TOKEN-KERNEL-VERSION + naming. + + For example, setting entry_name_format=%M_%A_%a on a system with + IMAGE_ID=myos, IMAGE_VERSION=20250830, and architecture + x86-64 would produce entry names like + myos_20250830_x86-64. + + Note that the entry name is resolved at the time of each + kernel-install invocation. If the format string uses specifiers that + depend on mutable system state (e.g. %M, %A, + %H), and that state changes between installing and removing a kernel, + the remove operation may compute a different entry name than + add did, leaving stale files on the boot partition. The + entry_name_format= setting should therefore remain stable across a + kernel's install/remove lifecycle. Additionally, the format should produce a unique name + for each installed kernel, e.g. by including %v (kernel version) or + %A (IMAGE_VERSION); otherwise installing multiple + kernels will overwrite each other's boot entries.
@@ -726,7 +800,8 @@ BOOT_ROOT=, layout=, initrd_generator=, - uki_generator=. + uki_generator=, + entry_name_format=. See the Environment variables section above for details. diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 1610d11e92..080900c1f5 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -338,7 +338,8 @@ static int load_kernel_install_layout(InstallContext *c) { /* ret_boot_root= */ NULL, &layout, /* ret_initrd_generator= */ NULL, - /* ret_uki_generator= */ NULL); + /* ret_uki_generator= */ NULL, + /* ret_entry_name_format= */ NULL); if (r <= 0) return r; diff --git a/src/kernel-install/90-loaderentry.install.in b/src/kernel-install/90-loaderentry.install.in index 66cfec8886..f791cab3c2 100755 --- a/src/kernel-install/90-loaderentry.install.in +++ b/src/kernel-install/90-loaderentry.install.in @@ -35,6 +35,7 @@ fi MACHINE_ID="${KERNEL_INSTALL_MACHINE_ID:?}" ENTRY_TOKEN="${KERNEL_INSTALL_ENTRY_TOKEN:?}" BOOT_ROOT="${KERNEL_INSTALL_BOOT_ROOT:?}" +ENTRY_NAME="${KERNEL_INSTALL_ENTRY_NAME:-$ENTRY_TOKEN-$KERNEL_VERSION}" [ -n "$BOOT_MNT" ] || BOOT_MNT="$(stat -c %m "$BOOT_ROOT")" if [ "$BOOT_MNT" = '/' ]; then @@ -45,7 +46,7 @@ fi KERNEL_DEST="$ENTRY_DIR_ABS/linux" KERNEL_ENTRY="$ENTRY_DIR/linux" -LOADER_ENTRY="$BOOT_ROOT/loader/entries/$ENTRY_TOKEN-$KERNEL_VERSION.conf" +LOADER_ENTRY="$BOOT_ROOT/loader/entries/$ENTRY_NAME.conf" case "$COMMAND" in remove) diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install index a55c7b8e84..48ed86ccb5 100755 --- a/src/kernel-install/90-uki-copy.install +++ b/src/kernel-install/90-uki-copy.install @@ -28,6 +28,7 @@ KERNEL_IMAGE="$4" ENTRY_TOKEN="$KERNEL_INSTALL_ENTRY_TOKEN" BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT" +ENTRY_NAME="${KERNEL_INSTALL_ENTRY_NAME:-$ENTRY_TOKEN-$KERNEL_VERSION}" UKI_DIR="$BOOT_ROOT/EFI/Linux" @@ -39,11 +40,11 @@ case "$COMMAND" in exit 0 fi [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \ - echo "Removing $UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION*.efi and extras" + echo "Removing $UKI_DIR/$ENTRY_NAME*.efi and extras" exec rm -rf \ - "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" \ - "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi.extra.d/" \ - "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+"*".efi" + "$UKI_DIR/$ENTRY_NAME.efi" \ + "$UKI_DIR/$ENTRY_NAME.efi.extra.d/" \ + "$UKI_DIR/$ENTRY_NAME+"*".efi" ;; add) ;; @@ -67,24 +68,24 @@ if [ -f "$TRIES_FILE" ]; then echo "$TRIES_FILE does not contain an integer." >&2 exit 1 fi - if [ -f "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" ]; then + if [ -f "$UKI_DIR/$ENTRY_NAME.efi" ]; then [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \ - echo "Removing previous UKI '$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi' without boot counting." >&2 - rm -f "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" + echo "Removing previous UKI '$UKI_DIR/$ENTRY_NAME.efi' without boot counting." >&2 + rm -f "$UKI_DIR/$ENTRY_NAME.efi" fi - for uki in "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+"*.efi; do - [ "$uki" = "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+*.efi" ] && break + for uki in "$UKI_DIR/$ENTRY_NAME+"*.efi; do + [ "$uki" = "$UKI_DIR/$ENTRY_NAME+*.efi" ] && break [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \ echo "Removing previous UKI '$uki' that has not yet booted successfully." >&2 rm -f "$uki" done - UKI_FILE="$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+$TRIES.efi" + UKI_FILE="$UKI_DIR/$ENTRY_NAME+$TRIES.efi" else - UKI_FILE="$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" + UKI_FILE="$UKI_DIR/$ENTRY_NAME.efi" fi -EXTRA_DIR="$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi.extra.d" +EXTRA_DIR="$UKI_DIR/$ENTRY_NAME.efi.extra.d" # If there is a UKI named uki.efi on the staging area use that, if not use what # was passed in as $KERNEL_IMAGE but insist it has a .efi extension diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 69fdbcfc34..be097cd5cd 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -35,6 +35,7 @@ #include "path-util.h" #include "recurse-dir.h" #include "rm-rf.h" +#include "specifier.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" @@ -108,12 +109,19 @@ typedef struct Context { char **initrds; char *initrd_generator; char *uki_generator; + char *entry_name_format; + char *entry_name; char *staging_area; char **plugins; char **argv; char **envp; } Context; +/* Longest filename suffix plugins append to the entry name (from 90-uki-copy.install) */ +#define ENTRY_NAME_SUFFIX_MAX STRLEN(".efi.extra.d") + +#define DEFAULT_ENTRY_NAME_FORMAT "%e-%v" + #define CONTEXT_NULL \ (Context) { \ .rfd = XAT_FDROOT, \ @@ -137,6 +145,8 @@ static void context_done(Context *c) { strv_free(c->initrds); free(c->initrd_generator); free(c->uki_generator); + free(c->entry_name_format); + free(c->entry_name); if (c->action == ACTION_INSPECT) free(c->staging_area); else @@ -199,6 +209,9 @@ static int context_copy(const Context *source, Context *ret) { if (r < 0) return r; r = strdup_to(©.uki_generator, source->uki_generator); + if (r < 0) + return r; + r = strdup_to(©.entry_name_format, source->entry_name_format); if (r < 0) return r; r = strdup_to(©.staging_area, source->staging_area); @@ -327,6 +340,63 @@ static int context_set_uki_generator(Context *c, const char *s, const char *sour return context_set_string(s, source, "UKI_GENERATOR", &c->uki_generator); } +static int context_set_entry_name_format(Context *c, const char *s, const char *source) { + assert(c); + return context_set_string(s, source, "ENTRY_NAME_FORMAT", &c->entry_name_format); +} + +static int context_resolve_entry_name(Context *c) { + assert(c); + + if (c->entry_name) + return 0; + + const Specifier table[] = { + { 'e', specifier_string, c->entry_token }, + { 'm', specifier_id128, &c->machine_id }, + { 'v', specifier_string, c->version ?: "KERNEL_VERSION" }, + { 'a', specifier_architecture, NULL }, + { 'A', specifier_os_image_version, NULL }, + { 'B', specifier_os_build_id, NULL }, + { 'H', specifier_hostname, NULL }, + { 'l', specifier_short_hostname, NULL }, + { 'q', specifier_pretty_hostname, NULL }, + { 'M', specifier_os_image_id, NULL }, + { 'o', specifier_os_id, NULL }, + { 'w', specifier_os_version_id, NULL }, + { 'W', specifier_os_variant_id, NULL }, + {} + }; + + _cleanup_free_ char *resolved = NULL; + int r; + + r = specifier_printf( + c->entry_name_format ?: DEFAULT_ENTRY_NAME_FORMAT, + NAME_MAX - ENTRY_NAME_SUFFIX_MAX, + table, + arg_root, + /* userdata= */ c, + &resolved); + if (r < 0) + return log_error_errno(r, "Failed to expand entry name format '%s': %m", + c->entry_name_format ?: DEFAULT_ENTRY_NAME_FORMAT); + + if (isempty(resolved)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Entry name format '%s' resolved to empty string.", + c->entry_name_format ?: DEFAULT_ENTRY_NAME_FORMAT); + + if (!filename_is_valid(resolved)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Entry name format '%s' resolved to invalid filename: %s", + c->entry_name_format ?: DEFAULT_ENTRY_NAME_FORMAT, resolved); + + c->entry_name = TAKE_PTR(resolved); + log_debug("Using entry name: %s", c->entry_name); + return 0; +} + static int context_set_version(Context *c, const char *s) { assert(c); @@ -449,12 +519,14 @@ static int context_load_environment(Context *c) { (void) context_set_boot_root(c, getenv("BOOT_ROOT"), "environment"); (void) context_set_conf_root(c, getenv("KERNEL_INSTALL_CONF_ROOT"), "environment"); (void) context_set_plugins(c, getenv("KERNEL_INSTALL_PLUGINS"), "environment"); + (void) context_set_entry_name_format(c, getenv("KERNEL_INSTALL_ENTRY_NAME_FORMAT"), "environment"); return 0; } static int context_load_install_conf(Context *c) { _cleanup_free_ char *machine_id = NULL, *boot_root = NULL, *layout = NULL, - *initrd_generator = NULL, *uki_generator = NULL; + *initrd_generator = NULL, *uki_generator = NULL, + *entry_name_format = NULL; int r; assert(c); @@ -467,7 +539,8 @@ static int context_load_install_conf(Context *c) { &boot_root, &layout, &initrd_generator, - &uki_generator); + &uki_generator, + &entry_name_format); if (r <= 0) return r; @@ -476,6 +549,7 @@ static int context_load_install_conf(Context *c) { (void) context_set_layout(c, layout, "config"); (void) context_set_initrd_generator(c, initrd_generator, "config"); (void) context_set_uki_generator(c, uki_generator, "config"); + (void) context_set_entry_name_format(c, entry_name_format, "config"); log_debug("Loaded config."); return 0; @@ -1032,6 +1106,10 @@ static int context_build_environment(Context *c) { if (c->envp) return 0; + r = context_resolve_entry_name(c); + if (r < 0) + return r; + r = strv_env_assign_many(&e, "LC_COLLATE", SYSTEMD_DEFAULT_LOCALE, "KERNEL_INSTALL_VERBOSE", one_zero(arg_verbose), @@ -1043,7 +1121,8 @@ static int context_build_environment(Context *c) { "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c->initrd_generator), "KERNEL_INSTALL_UKI_GENERATOR", strempty(c->uki_generator), "KERNEL_INSTALL_BOOT_ENTRY_TYPE", boot_entry_type_to_string(c->entry_type), - "KERNEL_INSTALL_STAGING_AREA", c->staging_area); + "KERNEL_INSTALL_STAGING_AREA", c->staging_area, + "KERNEL_INSTALL_ENTRY_NAME", c->entry_name); if (r < 0) return log_error_errno(r, "Failed to build environment variables for plugins: %m"); @@ -1413,6 +1492,10 @@ static int verb_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) TABLE_STRING, boot_entry_token_type_to_string(c.entry_token_type), TABLE_FIELD, "Entry Token", TABLE_STRING, c.entry_token, + TABLE_FIELD, "Entry Name Format", + TABLE_STRING, c.entry_name_format ?: DEFAULT_ENTRY_NAME_FORMAT, + TABLE_FIELD, "Entry Name", + TABLE_STRING, c.entry_name, TABLE_FIELD, "Entry Directory", TABLE_STRING, c.entry_dir, TABLE_FIELD, "Kernel Version", diff --git a/src/kernel-install/test-kernel-install.sh b/src/kernel-install/test-kernel-install.sh index ee29890edd..32278f8a61 100755 --- a/src/kernel-install/test-kernel-install.sh +++ b/src/kernel-install/test-kernel-install.sh @@ -294,6 +294,99 @@ test ! -e "$BOOT_ROOT/hoge/1.1.1" test -d "$BOOT_ROOT/hoge" rmdir "$BOOT_ROOT/hoge" +########################################### +# tests for entry_name_format= +########################################### + +# Test inspect with custom entry_name_format +cat >>"$D/sources/install.conf" </dev/null +echo "$output" | grep -E '"EntryName" : "the-token_1.1.1"' >/dev/null + +# Test type#1 BLS entry with custom entry_name_format +rm -f "$D/sources/tries" +cat >"$D/sources/install.conf" <"$D/sources/install.conf" <"$D/sources/tries" + KERNEL_INSTALL_PLUGINS="'${ukify_install}' '${loaderentry_install}' '${uki_copy_install}'" \ + "$kernel_install" -v add 1.1.5 "$D/sources/linux" "$D/sources/initrd" + uki="${BOOT_ROOT}/EFI/Linux/the-token_1.1.5+56.efi" + test -f "$uki" + KERNEL_INSTALL_PLUGINS="'${ukify_install}' '${loaderentry_install}' '${uki_copy_install}'" \ + "$kernel_install" -v remove 1.1.5 + test ! -e "$uki" + + rm -f "$D/sources/tries" +fi + +# Test add-all with custom entry_name_format (exercises context_copy) +KVER="$(uname -r)" +if test -f "/usr/lib/modules/$KVER/vmlinuz"; then + rm -rf "$BOOT_ROOT" + mkdir -p "$BOOT_ROOT" + cat >"$D/sources/install.conf" <"$D/sources/install.conf" <&2 <&2 <= 0; /* Return 0 if we got -ENOENT above, 1 otherwise. */ } diff --git a/src/shared/kernel-config.h b/src/shared/kernel-config.h index d46aba8278..34c71387b9 100644 --- a/src/shared/kernel-config.h +++ b/src/shared/kernel-config.h @@ -12,7 +12,8 @@ int load_kernel_install_conf_at( char **ret_boot_root, char **ret_layout, char **ret_initrd_generator, - char **ret_uki_generator); + char **ret_uki_generator, + char **ret_entry_name_format); static inline int load_kernel_install_conf( const char *root, @@ -21,7 +22,8 @@ static inline int load_kernel_install_conf( char **ret_boot_root, char **ret_layout, char **ret_initrd_generator, - char **ret_uki_generator) { + char **ret_uki_generator, + char **ret_entry_name_format) { - return load_kernel_install_conf_at(root, XAT_FDROOT, conf_root, ret_machine_id, ret_boot_root, ret_layout, ret_initrd_generator, ret_uki_generator); + return load_kernel_install_conf_at(root, XAT_FDROOT, conf_root, ret_machine_id, ret_boot_root, ret_layout, ret_initrd_generator, ret_uki_generator, ret_entry_name_format); }