diff --git a/NEWS b/NEWS index 4677c75177..0149c0f965 100644 --- a/NEWS +++ b/NEWS @@ -656,15 +656,13 @@ CHANGES WITH 250: may be used to set the boot menu time-out of the boot loader (for all or just the subsequent boot). - * bootctl and kernel-install will now read KERNEL_INSTALL_MACHINE_ID - and KERNEL_INSTALL_LAYOUT from kernel/install.conf. The first - variable specifies the machine-id to use for installation. It would - previously be used if set in the environment, and now it'll also be - read automatically from the config file. The second variable is new. - When set, it specifies the layout to use for installation directories - on the boot partition, so that tools don't need to guess it based on - the already-existing directories. The only value that is defined - natively is "bls", corresponding to the layout specified in + * bootctl and kernel-install will now read variables + KERNEL_INSTALL_LAYOUT= from /etc/machine-info and layout= from + /etc/kernel/install.conf. When set, it specifies the layout to use + for installation directories on the boot partition, so that tools + don't need to guess it based on the already-existing directories. The + only value that is defined natively is "bls", corresponding to the + layout specified in https://systemd.io/BOOT_LOADER_SPECIFICATION/. Plugins for kernel-install that implement a different layout can declare other values for this variable. diff --git a/docs/BOOT_LOADER_SPECIFICATION.md b/docs/BOOT_LOADER_SPECIFICATION.md index 7f15a23164..375efeb1d1 100644 --- a/docs/BOOT_LOADER_SPECIFICATION.md +++ b/docs/BOOT_LOADER_SPECIFICATION.md @@ -309,6 +309,18 @@ focus for this specification. More specifically, on non-EFI systems configuration snippets following this specification cannot be used to spawn other operating systems (such as Windows). +Unfortunately, there are implementations of boot loading infrastructure that +are also using the /loader/entries/ directory, but place files in them that are +not valid by this specification. In order to minimize confusion a boot loader +implementation may place a file /loader/entries.srel next to the +/loader/entries/ directory containing the ASCII string "type1" (suffixed +with a UNIX newline). Tools that need to determine whether an existing +directory implements the semantics described here may check for this file and +contents: if it exists and contains the mentioned string, it shall assume a +standards compliant implementation is in place. If it exists but contains a +different string it shall assume non-standard semantics are implemented. If the +file does not exist no assumptions should be made. + ### Type #2 EFI Unified Kernel Images A unified kernel image is a single EFI PE executable combining an EFI stub diff --git a/man/kernel-install.xml b/man/kernel-install.xml index 5ec76999ad..d6a5e43031 100644 --- a/man/kernel-install.xml +++ b/man/kernel-install.xml @@ -237,9 +237,8 @@ $KERNEL_INSTALL_INITRD_GENERATOR is set for plugins to select the initrd - generator. This should be configured as initrd_generator= in - install.conf. - + generator. This may be configured as initrd_generator= in + install.conf. See below. $KERNEL_INSTALL_STAGING_AREA is set for plugins to a path to a directory. Plugins may drop files in that directory, and they will be installed as part of the loader entry, based @@ -325,10 +324,10 @@ /etc/kernel/install.conf - Configuration options for kernel-install, - as a series of KEY=VALUE assignments, - compatible with shell syntax. - See the Environment variables section for supported keys. + Configuration options for kernel-install, as a series of + KEY=VALUE assignments, compatible with shell + syntax. This currently supports two keys: layout= and + initrd_generator=, for details see the Environment variables section above. diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c index cf3bbad1c4..e0a338c163 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -275,6 +275,28 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { return fd; } +int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file) { + _cleanup_free_ char *path = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_close_ int fd = -1; + + assert(target); + assert(ret_file); + assert(ret_path); + + fd = open_tmpfile_linkable(target, flags, &path); + if (fd < 0) + return fd; + + f = take_fdopen(&fd, "w"); + if (!f) + return -ENOMEM; + + *ret_path = TAKE_PTR(path); + *ret_file = TAKE_PTR(f); + return 0; +} + int link_tmpfile(int fd, const char *path, const char *target) { assert(fd >= 0); assert(target); @@ -292,6 +314,23 @@ int link_tmpfile(int fd, const char *path, const char *target) { return RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW)); } +int flink_tmpfile(FILE *f, const char *path, const char *target) { + int fd, r; + + assert(f); + assert(target); + + fd = fileno(f); + if (fd < 0) /* Not all FILE* objects encapsulate fds */ + return -EBADF; + + r = fflush_sync_and_check(f); + if (r < 0) + return r; + + return link_tmpfile(fd, path, target); +} + int mkdtemp_malloc(const char *template, char **ret) { _cleanup_free_ char *p = NULL; int r; diff --git a/src/basic/tmpfile-util.h b/src/basic/tmpfile-util.h index 45255fc062..610cbaf87e 100644 --- a/src/basic/tmpfile-util.h +++ b/src/basic/tmpfile-util.h @@ -13,7 +13,9 @@ int tempfn_random_child(const char *p, const char *extra, char **ret); int open_tmpfile_unlinkable(const char *directory, int flags); int open_tmpfile_linkable(const char *target, int flags, char **ret_path); +int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file); int link_tmpfile(int fd, const char *path, const char *target); +int flink_tmpfile(FILE *f, const char *path, const char *target); int mkdtemp_malloc(const char *template, char **ret); diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index a1d5af8c21..8103a1cf09 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -144,38 +144,70 @@ static int acquire_xbootldr( return 1; } -static int load_install_machine_id_and_layout(void) { - /* Figure out the right machine-id for operations. If KERNEL_INSTALL_MACHINE_ID is configured in - * /etc/machine-info, let's use that. Otherwise, just use the real machine-id. - * - * Also load KERNEL_INSTALL_LAYOUT. - */ +static int load_etc_machine_id(void) { + int r; + + r = sd_id128_get_machine(&arg_machine_id); + if (IN_SET(r, -ENOENT, -ENOMEDIUM)) /* Not set or empty */ + return 0; + if (r < 0) + return log_error_errno(r, "Failed to get machine-id: %m"); + + log_debug("Loaded machine ID %s from /etc/machine-id.", SD_ID128_TO_STRING(arg_machine_id)); + return 0; +} + +static int load_etc_machine_info(void) { + /* systemd v250 added support to store the kernel-install layout setting and the machine ID to use + * for setting up the ESP in /etc/machine-info. The newer /etc/kernel/entry-token file, as well as + * the $layout field in /etc/kernel/install.conf are better replacements for this though, hence this + * has been deprecated and is only returned for compatibility. */ _cleanup_free_ char *s = NULL, *layout = NULL; int r; r = parse_env_file(NULL, "/etc/machine-info", "KERNEL_INSTALL_LAYOUT", &layout, "KERNEL_INSTALL_MACHINE_ID", &s); - if (r < 0 && r != -ENOENT) + if (r == -ENOENT) + return 0; + if (r < 0) return log_error_errno(r, "Failed to parse /etc/machine-info: %m"); - if (isempty(s)) { - r = sd_id128_get_machine(&arg_machine_id); - if (r < 0 && !IN_SET(r, -ENOENT, -ENOMEDIUM)) - return log_error_errno(r, "Failed to get machine-id: %m"); - } else { + if (!isempty(s)) { + log_notice("Read $KERNEL_INSTALL_MACHINE_ID from /etc/machine-info. Please move it to /etc/kernel/entry-token."); + r = sd_id128_from_string(s, &arg_machine_id); if (r < 0) return log_error_errno(r, "Failed to parse KERNEL_INSTALL_MACHINE_ID=%s in /etc/machine-info: %m", s); + log_debug("Loaded KERNEL_INSTALL_MACHINE_ID=%s from KERNEL_INSTALL_MACHINE_ID in /etc/machine-info.", + SD_ID128_TO_STRING(arg_machine_id)); } - log_debug("Using KERNEL_INSTALL_MACHINE_ID=%s from %s.", - SD_ID128_TO_STRING(arg_machine_id), - isempty(s) ? "/etc/machine_id" : "KERNEL_INSTALL_MACHINE_ID in /etc/machine-info"); if (!isempty(layout)) { + log_notice("Read $KERNEL_INSTALL_LAYOUT from /etc/machine-info. Please move it to the layout= setting of /etc/kernel/install.conf."); + log_debug("KERNEL_INSTALL_LAYOUT=%s is specified in /etc/machine-info.", layout); - arg_install_layout = TAKE_PTR(layout); + free_and_replace(arg_install_layout, layout); + } + + return 0; +} + +static int load_etc_kernel_install_conf(void) { + _cleanup_free_ char *layout = NULL; + int r; + + r = parse_env_file(NULL, "/etc/kernel/install.conf", + "layout", &layout); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to parse /etc/kernel/install.conf: %m"); + + if (!isempty(layout)) { + log_debug("layout=%s is specified in /etc/machine-info.", layout); + free_and_replace(arg_install_layout, layout); } return 0; @@ -282,7 +314,15 @@ static bool use_boot_loader_spec_type1(void) { static int settle_make_entry_directory(void) { int r; - r = load_install_machine_id_and_layout(); + r = load_etc_machine_id(); + if (r < 0) + return r; + + r = load_etc_machine_info(); + if (r < 0) + return r; + + r = load_etc_kernel_install_conf(); if (r < 0) return r; @@ -1246,7 +1286,6 @@ static int remove_loader_variables(void) { static int install_loader_config(const char *esp_path) { _cleanup_(unlink_and_freep) char *t = NULL; _cleanup_fclose_ FILE *f = NULL; - _cleanup_close_ int fd = -1; const char *p; int r; @@ -1256,13 +1295,9 @@ static int install_loader_config(const char *esp_path) { if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ return 0; - fd = open_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t); - if (fd < 0) - return log_error_errno(fd, "Failed to open \"%s\" for writing: %m", p); - - f = take_fdopen(&fd, "w"); - if (!f) - return log_oom(); + r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p); fprintf(f, "#timeout 3\n" "#console-mode keep\n"); @@ -1272,11 +1307,36 @@ static int install_loader_config(const char *esp_path) { fprintf(f, "default %s-*\n", arg_entry_token); } - r = fflush_sync_and_check(f); + r = flink_tmpfile(f, t, p); + if (r == -EEXIST) + return 0; /* Silently skip creation if the file exists now (recheck) */ if (r < 0) - return log_error_errno(r, "Failed to write \"%s\": %m", p); + return log_error_errno(r, "Failed to move \"%s\" into place: %m", p); - r = link_tmpfile(fileno(f), t, p); + t = mfree(t); + return 1; +} + +static int install_loader_specification(const char *root) { + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + int r; + + p = path_join(root, "/loader/entries.srel"); + if (!p) + return log_oom(); + + if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ + return 0; + + r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p); + + fprintf(f, "type1\n"); + + r = flink_tmpfile(f, t, p); if (r == -EEXIST) return 0; /* Silently skip creation if the file exists now (recheck) */ if (r < 0) @@ -1998,6 +2058,10 @@ static int verb_install(int argc, char *argv[], void *userdata) { if (r < 0) return r; } + + r = install_loader_specification(arg_dollar_boot_path()); + if (r < 0) + return r; } (void) sync_everything(); @@ -2037,6 +2101,10 @@ static int verb_remove(int argc, char *argv[], void *userdata) { if (q < 0 && r >= 0) r = q; + q = remove_file(arg_esp_path, "/loader/entries.srel"); + if (q < 0 && r >= 0) + r = q; + q = remove_subdirs(arg_esp_path, esp_subdirs); if (q < 0 && r >= 0) r = q; @@ -2050,7 +2118,12 @@ static int verb_remove(int argc, char *argv[], void *userdata) { r = q; if (arg_xbootldr_path) { - /* Remove the latter two also in the XBOOTLDR partition if it exists */ + /* Remove a subset of these also from the XBOOTLDR partition if it exists */ + + q = remove_file(arg_xbootldr_path, "/loader/entries.srel"); + if (q < 0 && r >= 0) + r = q; + q = remove_subdirs(arg_xbootldr_path, dollar_boot_subdirs); if (q < 0 && r >= 0) r = q; diff --git a/src/kernel-install/kernel-install b/src/kernel-install/kernel-install index 719abf2b81..a09b998362 100755 --- a/src/kernel-install/kernel-install +++ b/src/kernel-install/kernel-install @@ -155,10 +155,29 @@ done [ -z "$ENTRY_TOKEN" ] && ENTRY_TOKEN="$MACHINE_ID" if [ -z "$layout" ]; then - # Administrative decision: if not present, some scripts generate into /boot. - if [ -d "$BOOT_ROOT/$ENTRY_TOKEN" ]; then + # No layout configured by the administrator. Let's try to figure it out + # automatically from metadata already contained in $BOOT_ROOT. + if [ -e "$BOOT_ROOT/loader/entries.srel" ]; then + read -r ENTRIES_SREL <"$BOOT_ROOT/loader/entries.srel" + if [ "$ENTRIES_SREL" = "type1" ]; then + # The loader/entries.srel file clearly indicates that the installed + # boot loader implements the proper standard upstream boot loader + # spec for Type #1 entries. Let's default to that, then. + layout="bls" + else + # The loader/entries.srel file indicates some other spec is + # implemented and owns the /loader/entries/ directory. Since we + # have no idea what that means, let's stay away from it by default. + layout="other" + fi + elif [ -d "$BOOT_ROOT/$ENTRY_TOKEN" ]; then + # If the metadata in $BOOT_ROOT doesn't tell us anything, then check if + # the entry token directory already exists. If so, let's assume it's + # the standard boot loader spec, too. layout="bls" else + # There's no metadata in $BOOT_ROOT, and apparently no entry token + # directory installed? Then we really don't know anything. layout="other" fi fi