Merge pull request #22778 from poettering/kernel-install-layout-rework

kernel-install/bootctl: layout fixes
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2022-03-22 13:57:28 +01:00
committed by GitHub
7 changed files with 189 additions and 47 deletions

16
NEWS
View File

@@ -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.

View File

@@ -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

View File

@@ -237,9 +237,8 @@
</variablelist>
<para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> is set for plugins to select the initrd
generator. This should be configured as <varname>initrd_generator=</varname> in
<filename>install.conf</filename>.
</para>
generator. This may be configured as <varname>initrd_generator=</varname> in
<filename>install.conf</filename>. See below.</para>
<para><varname>$KERNEL_INSTALL_STAGING_AREA</varname> 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 @@
<filename>/etc/kernel/install.conf</filename>
</term>
<listitem>
<para>Configuration options for <command>kernel-install</command>,
as a series of <varname>KEY=</varname><replaceable>VALUE</replaceable> assignments,
compatible with shell syntax.
See the Environment variables section for supported keys.</para>
<para>Configuration options for <command>kernel-install</command>, as a series of
<varname>KEY=</varname><replaceable>VALUE</replaceable> assignments, compatible with shell
syntax. This currently supports two keys: <varname>layout=</varname> and
<varname>initrd_generator=</varname>, for details see the Environment variables section above.</para>
</listitem>
</varlistentry>
</variablelist>

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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