bootctl: add link-auto/LinkAuto and auto-link on completed system update

Add a "bootctl link-auto" verb and a matching io.systemd.BootControl.LinkAuto()
Varlink method that behave exactly like "bootctl link" / Link(), except that
the UKI and extra resources are discovered automatically instead of being
passed in. The following directories are searched, in decreasing priority:
/etc/systemd/uki/, /run/systemd/uki/, /var/lib/systemd/uki/ (where
systemd-sysupdate stages downloaded resources), /usr/local/lib/systemd/uki/
and /usr/lib/systemd/uki/.

  - the UKI is taken from kernel.efi, or the best version in kernel.efi.v/
    (resolved via vpick, without honouring boot-counting suffixes), from the
    highest-priority directory that has one;
  - extra resources are picked up from extras.d/, matching *.sysext.raw,
    *.confext.raw and *.cred, each either as a plain file or as a versioned
    *.v/ directory resolved via vpick, combined across all directories with
    higher-priority directories winning on conflicts.

Everything is resolved relative to the pinned root directory fd. Files passed
via --extra= on the command line are linked in addition to the auto-discovered
ones.

Also bind io.systemd.SysUpdate.Notify.OnCompletedUpdate() in the boot control
Varlink server, which simply does the same as LinkAuto(), and hook a socket
into /run/systemd/sysupdate/notify/ via systemd-sysupdate-notify-bootctl.socket
(enabled by default via the preset) so a freshly downloaded kernel is linked
into $BOOT automatically after a sysupdate run.
This commit is contained in:
Lennart Poettering
2026-06-24 13:05:34 +02:00
parent 27b7fb9b20
commit 1421e6c5f4
14 changed files with 791 additions and 147 deletions
+54
View File
@@ -147,6 +147,34 @@
<xi:include href="version-info.xml" xpointer="v261"/></listitem> <xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>link-auto</option></term>
<listitem><para>Like <command>link</command>, but instead of taking the kernel and sidecar resources
as arguments, discovers them automatically. The following directories are searched, in decreasing
order of priority: <filename>/etc/systemd/uki/</filename>, <filename>/run/systemd/uki/</filename>,
<filename>/var/lib/systemd/uki/</filename> (where
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>
stages downloaded resources), <filename>/usr/local/lib/systemd/uki/</filename> and
<filename>/usr/lib/systemd/uki/</filename>. In each directory the UKI is taken from
<filename>kernel.efi</filename>, or — if absent — the newest version in the versioned directory
<filename>kernel.efi.v/</filename> (see
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>); the
UKI from the highest-priority directory that has one is used. Sidecar resources are picked up from
the <filename>extras.d/</filename> subdirectory of each, matching the suffixes
<literal>.sysext.raw</literal>, <literal>.confext.raw</literal> and <literal>.cred</literal>, each
either as a plain file or as a versioned <literal>.v/</literal> directory; resources are combined
across all directories, with the same resource in a higher-priority directory taking precedence. Any
files specified with <option>--extra=</option> are linked in addition to the discovered ones.</para>
<para>This is used by
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>
when it completes an update, so a freshly downloaded kernel and its sidecar resources are linked into
place without specifying any paths.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>cleanup</option></term> <term><option>cleanup</option></term>
@@ -845,6 +873,32 @@ Boot Loader Entries:
</example> </example>
</refsect1> </refsect1>
<refsect1>
<title>Files</title>
<variablelist>
<varlistentry>
<term><filename>/etc/systemd/uki/</filename></term>
<term><filename>/run/systemd/uki/</filename></term>
<term><filename>/var/lib/systemd/uki/</filename></term>
<term><filename>/usr/local/lib/systemd/uki/</filename></term>
<term><filename>/usr/lib/systemd/uki/</filename></term>
<listitem><para>The <command>link-auto</command> command discovers the UKI to install from these
directories (listed in decreasing order of priority). It uses <filename>kernel.efi</filename> or the
versioned <filename>kernel.efi.v/</filename> directory from the highest-priority directory that
contains one. Additional sidecar resources (system extension images, configuration extension images,
credential files) are picked up from the <filename>extras.d/</filename> subdirectory of each, combined
across all directories. <filename>/var/lib/systemd/uki/</filename> is the directory where
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>
is supposed to place downloaded kernels, system extensions, configuration extensions and
credentials.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1> <refsect1>
<title>See Also</title> <title>See Also</title>
<para><simplelist type="inline"> <para><simplelist type="inline">
+1
View File
@@ -40,6 +40,7 @@ enable systemd-report-sign-tsm.socket
enable systemd-report.socket enable systemd-report.socket
enable systemd-resolved.service enable systemd-resolved.service
enable systemd-sysext.service enable systemd-sysext.service
enable systemd-sysupdate-notify-bootctl.socket
enable systemd-sysupdate-notify-pcrlock.socket enable systemd-sysupdate-notify-pcrlock.socket
enable systemd-timesyncd.service enable systemd-timesyncd.service
enable systemd-tpm2-clear.service enable systemd-tpm2-clear.service
+1 -1
View File
@@ -87,7 +87,7 @@ _bootctl() {
fi fi
local -A VERBS=( local -A VERBS=(
[STANDALONE]='help status install update remove is-installed random-seed list set-timeout set-timeout-oneshot cleanup' [STANDALONE]='help status install update remove is-installed random-seed list set-timeout set-timeout-oneshot cleanup link-auto'
[BOOTENTRY]='set-default set-oneshot set-sysfail set-preferred unlink' [BOOTENTRY]='set-default set-oneshot set-sysfail set-preferred unlink'
[BOOLEAN]='reboot-to-firmware' [BOOLEAN]='reboot-to-firmware'
[FILE]='kernel-identify kernel-inspect link' [FILE]='kernel-identify kernel-inspect link'
+1
View File
@@ -75,6 +75,7 @@ _bootctl_reboot-to-firmware() {
"set-timeout-oneshot:Set the menu timeout for the next boot only" "set-timeout-oneshot:Set the menu timeout for the next boot only"
"unlink:Remove boot loader entry" "unlink:Remove boot loader entry"
"link:Add boot loader entry" "link:Add boot loader entry"
"link-auto:Add boot loader entry for kernel and resources staged in /var/lib/systemd/uki/"
"cleanup:Remove files in ESP not referenced in any boot entry" "cleanup:Remove files in ESP not referenced in any boot entry"
"kernel-identify:Identify kernel image type" "kernel-identify:Identify kernel image type"
"kernel-inspect:Print details about the kernel image" "kernel-inspect:Print details about the kernel image"
+5
View File
@@ -45,6 +45,7 @@
#include "tmpfile-util.h" #include "tmpfile-util.h"
#include "umask-util.h" #include "umask-util.h"
#include "utf8.h" #include "utf8.h"
#include "varlink-util.h"
typedef enum InstallOperation { typedef enum InstallOperation {
INSTALL_NEW, INSTALL_NEW,
@@ -2121,6 +2122,10 @@ int vl_method_install(
if (r != 0) if (r != 0)
return r; return r;
r = varlink_check_privileged_peer(link);
if (r < 0)
return r;
if (!IN_SET(p.context.operation, INSTALL_NEW, INSTALL_UPDATE)) if (!IN_SET(p.context.operation, INSTALL_NEW, INSTALL_UPDATE))
return sd_varlink_error_invalid_parameter_name(link, "operation"); return sd_varlink_error_invalid_parameter_name(link, "operation");
+620 -138
View File
@@ -32,6 +32,7 @@
#include "parse-argument.h" #include "parse-argument.h"
#include "path-util.h" #include "path-util.h"
#include "recurse-dir.h" #include "recurse-dir.h"
#include "set.h"
#include "stat-util.h" #include "stat-util.h"
#include "stdio-util.h" #include "stdio-util.h"
#include "string-util.h" #include "string-util.h"
@@ -39,6 +40,8 @@
#include "tmpfile-util.h" #include "tmpfile-util.h"
#include "uki.h" #include "uki.h"
#include "utf8.h" #include "utf8.h"
#include "varlink-util.h"
#include "vpick.h"
/* Keeps track of an "extra" file to associate with the type 1 entries to generate */ /* Keeps track of an "extra" file to associate with the type 1 entries to generate */
typedef struct ExtraFile { typedef struct ExtraFile {
@@ -205,77 +208,14 @@ static void link_context_done(LinkContext *c) {
c->linked_ids = strv_free(c->linked_ids); c->linked_ids = strv_free(c->linked_ids);
} }
static int link_context_from_cmdline(LinkContext *ret, const char *kernel) { static int link_context_acquire_dollar_boot(LinkContext *b) {
int r; int r;
assert(ret); assert(b);
assert(kernel);
_cleanup_(link_context_done) LinkContext b = LINK_CONTEXT_NULL;
b.entry_token_type = arg_entry_token_type;
b.tries_left = arg_tries_left;
b.entry_commit = arg_entry_commit;
b.keep_free = arg_keep_free;
if (strdup_to(&b.entry_token, arg_entry_token) < 0 ||
strdup_to(&b.entry_title, arg_entry_title) < 0 ||
strdup_to(&b.entry_version, arg_entry_version) < 0)
return log_oom();
if (arg_root) {
b.root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH);
if (b.root_fd < 0)
return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
if (strdup_to(&b.root, arg_root) < 0)
return log_oom();
} else
b.root_fd = XAT_FDROOT;
r = path_extract_filename(kernel, &b.kernel_filename);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel);
if (!efi_loader_entry_resource_filename_valid(b.kernel_filename))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Kernel '%s' is not suitable for reference in a boot menu entry.", kernel);
b.kernel_fd = xopenat_full(AT_FDCWD, kernel, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
if (b.kernel_fd < 0)
return log_error_errno(b.kernel_fd, "Failed to open kernel path '%s': %m", kernel);
KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID;
r = inspect_kernel(b.kernel_fd, /* filename= */ NULL, &kit);
if (r == -EBADMSG)
return log_error_errno(r, "Kernel image '%s' is not valid.", kernel);
if (r < 0)
return log_error_errno(r, "Failed to determine kernel image type of '%s': %m", kernel);
if (kit != KERNEL_IMAGE_TYPE_UKI)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Kernel image '%s' is not a UKI.", kernel);
STRV_FOREACH(x, arg_extras) {
_cleanup_free_ char *fn = NULL;
r = path_extract_filename(*x, &fn);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", *x);
if (r == O_DIRECTORY)
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Extra file path '%s' does not refer to regular file.", *x);
_cleanup_close_ int fd = -EBADF;
fd = xopenat_full(AT_FDCWD, *x, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
if (fd < 0)
return log_error_errno(fd, "Failed to open '%s': %m", *x);
if (!GREEDY_REALLOC(b.extra, b.n_extra+1))
return log_oom();
b.extra[b.n_extra++] = (ExtraFile) {
.source_fd = TAKE_FD(fd),
.filename = TAKE_PTR(fn),
.temp_fd = -EBADF,
};
}
r = acquire_xbootldr( r = acquire_xbootldr(
/* unprivileged_mode= */ false, /* unprivileged_mode= */ false,
&b.dollar_boot_fd, &b->dollar_boot_fd,
/* ret_uuid= */ NULL, /* ret_uuid= */ NULL,
/* ret_devid= */ NULL); /* ret_devid= */ NULL);
if (r < 0) if (r < 0)
@@ -288,19 +228,19 @@ static int link_context_from_cmdline(LinkContext *ret, const char *kernel) {
if (!e) if (!e)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root);
r = strdup_to(&b.dollar_boot_path, e); r = strdup_to(&b->dollar_boot_path, e);
} else } else
r = strdup_to(&b.dollar_boot_path, arg_xbootldr_path); r = strdup_to(&b->dollar_boot_path, arg_xbootldr_path);
if (r < 0) if (r < 0)
return log_oom(); return log_oom();
b.dollar_boot_source = BOOT_ENTRY_XBOOTLDR; b->dollar_boot_source = BOOT_ENTRY_XBOOTLDR;
} else { } else {
/* No XBOOTLDR has been found, look for ESP */ /* No XBOOTLDR has been found, look for ESP */
r = acquire_esp(/* unprivileged_mode= */ false, r = acquire_esp(/* unprivileged_mode= */ false,
/* graceful= */ false, /* graceful= */ false,
&b.dollar_boot_fd, &b->dollar_boot_fd,
/* ret_part= */ NULL, /* ret_part= */ NULL,
/* ret_pstart= */ NULL, /* ret_pstart= */ NULL,
/* ret_psize= */ NULL, /* ret_psize= */ NULL,
@@ -316,15 +256,126 @@ static int link_context_from_cmdline(LinkContext *ret, const char *kernel) {
if (!e) if (!e)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root);
r = strdup_to(&b.dollar_boot_path, e); r = strdup_to(&b->dollar_boot_path, e);
} else } else
r = strdup_to(&b.dollar_boot_path, arg_esp_path); r = strdup_to(&b->dollar_boot_path, arg_esp_path);
if (r < 0) if (r < 0)
return log_oom(); return log_oom();
b.dollar_boot_source = BOOT_ENTRY_ESP; b->dollar_boot_source = BOOT_ENTRY_ESP;
} }
return 0;
}
/* Fills the bits of a LinkContext that both "bootctl link" and "bootctl link-auto" derive from the global
* command line options (everything except the kernel image and extra files themselves). */
static int link_context_from_cmdline_common(LinkContext *b) {
assert(b);
b->entry_token_type = arg_entry_token_type;
b->tries_left = arg_tries_left;
b->entry_commit = arg_entry_commit;
b->keep_free = arg_keep_free;
if (strdup_to(&b->entry_token, arg_entry_token) < 0 ||
strdup_to(&b->entry_title, arg_entry_title) < 0 ||
strdup_to(&b->entry_version, arg_entry_version) < 0)
return log_oom();
if (arg_root) {
b->root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH);
if (b->root_fd < 0)
return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
if (strdup_to(&b->root, arg_root) < 0)
return log_oom();
} else
b->root_fd = XAT_FDROOT;
return 0;
}
/* Appends the files passed via --extra= on the command line as extra resources to the link context. */
static int link_context_add_cmdline_extras(LinkContext *b) {
int r;
assert(b);
STRV_FOREACH(x, arg_extras) {
_cleanup_free_ char *fn = NULL;
r = path_extract_filename(*x, &fn);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", *x);
if (r == O_DIRECTORY)
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Extra file path '%s' does not refer to regular file.", *x);
_cleanup_close_ int fd = xopenat_full(AT_FDCWD, *x, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
if (fd < 0)
return log_error_errno(fd, "Failed to open '%s': %m", *x);
if (!GREEDY_REALLOC(b->extra, b->n_extra+1))
return log_oom();
b->extra[b->n_extra++] = (ExtraFile) {
.source_fd = TAKE_FD(fd),
.filename = TAKE_PTR(fn),
.temp_fd = -EBADF,
};
}
return 0;
}
static int validate_kernel(int kernel_fd, const char *filename) {
int r;
assert(kernel_fd >= 0);
KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID;
r = inspect_kernel(kernel_fd, /* filename= */ NULL, &kit);
if (r == -EBADMSG)
return log_error_errno(r, "UKI '%s' is not valid.", filename);
if (r < 0)
return log_error_errno(r, "Failed to determine kernel image type of '%s': %m", filename);
if (kit != KERNEL_IMAGE_TYPE_UKI)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Image '%s' is not a UKI.", filename);
return 0;
}
static int link_context_from_cmdline(LinkContext *ret, const char *kernel) {
int r;
assert(ret);
assert(kernel);
_cleanup_(link_context_done) LinkContext b = LINK_CONTEXT_NULL;
r = link_context_from_cmdline_common(&b);
if (r < 0)
return r;
r = path_extract_filename(kernel, &b.kernel_filename);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel);
if (!efi_loader_entry_resource_filename_valid(b.kernel_filename))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Kernel '%s' is not suitable for reference in a boot menu entry.", kernel);
b.kernel_fd = xopenat_full(AT_FDCWD, kernel, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
if (b.kernel_fd < 0)
return log_error_errno(b.kernel_fd, "Failed to open kernel path '%s': %m", kernel);
r = validate_kernel(b.kernel_fd, kernel);
if (r < 0)
return r;
r = link_context_add_cmdline_extras(&b);
if (r < 0)
return r;
r = link_context_acquire_dollar_boot(&b);
if (r < 0)
return r;
*ret = TAKE_GENERIC(b, LinkContext, LINK_CONTEXT_NULL); *ret = TAKE_GENERIC(b, LinkContext, LINK_CONTEXT_NULL);
return 0; return 0;
} }
@@ -993,6 +1044,311 @@ int verb_link(int argc, char *argv[], uintptr_t data, void *userdata) {
return run_link(&c); return run_link(&c);
} }
/* Directories (relative to the operative root) below which "bootctl link-auto" looks for a staged UKI and
* extra resources, in decreasing priority order. /var/lib/systemd/uki/ is where systemd-sysupdate stages
* downloaded resources; the others follow the usual configuration search path precedence, with
* /var/lib/ slotted in just below the volatile locations and above the vendor trees. */
static const char* const uki_auto_dirs[] = {
"etc/systemd/uki",
"run/systemd/uki",
"var/lib/systemd/uki",
"usr/local/lib/systemd/uki",
"usr/lib/systemd/uki",
};
static const PickFilter pick_filter_uki = {
.type_mask = UINT32_C(1) << DT_REG,
.architecture = _ARCHITECTURE_INVALID,
.suffix = ".efi",
};
/* The recognized suffixes of extra resources we pick up alongside the UKI. Each may appear as a plain file
* (e.g. foo.sysext.raw) or as a versioned directory (e.g. foo.sysext.raw.v/) resolved via the vpick
* mechanism. */
static const char* const auto_link_extra_suffixes[] = {
".sysext.raw",
".confext.raw",
".cred",
};
static int link_context_add_extra(LinkContext *c, int dir_fd, const char *path, const char *filename) {
assert(c);
assert(dir_fd >= 0);
assert(path);
assert(filename);
if (!efi_loader_entry_resource_filename_valid(filename))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra file '%s' is not suitable for reference in a boot menu entry, refusing.", filename);
_cleanup_close_ int fd = xopenat_full(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID);
if (fd < 0)
return log_error_errno(fd, "Failed to open extra file '%s': %m", path);
_cleanup_free_ char *fn = strdup(filename);
if (!fn)
return log_oom();
if (!GREEDY_REALLOC(c->extra, c->n_extra + 1))
return log_oom();
c->extra[c->n_extra++] = (ExtraFile) {
.source_fd = TAKE_FD(fd),
.filename = TAKE_PTR(fn),
.temp_fd = -EBADF,
};
return 0;
}
/* Adds the extra resources found in <uki_dir_fd>/extras.d/ to the link context. The 'seen' set tracks the
* logical resource names already added by a higher-priority directory, so the same resource appearing in
* multiple directories is taken from the highest-priority one only. */
static int link_context_add_extras(LinkContext *c, int uki_dir_fd, Set **seen) {
int r;
assert(c);
assert(uki_dir_fd >= 0);
assert(seen);
_cleanup_close_ int extras_dir_fd = chase_and_openat(
c->root_fd,
uki_dir_fd,
"extras.d",
/* chase_flags= */ 0,
O_DIRECTORY|O_CLOEXEC,
/* ret_path= */ NULL);
if (extras_dir_fd == -ENOENT)
return 0;
if (extras_dir_fd < 0)
return log_error_errno(extras_dir_fd, "Failed to open extras.d/ directory: %m");
_cleanup_free_ DirectoryEntries *des = NULL;
r = readdir_all(extras_dir_fd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_SORT, &des);
if (r < 0)
return log_error_errno(r, "Failed to enumerate extras.d/ directory: %m");
FOREACH_ARRAY(i, des->entries, des->n_entries) {
struct dirent *de = *i;
if (!IN_SET(de->d_type, DT_DIR, DT_REG))
continue;
FOREACH_ELEMENT(suffix, auto_link_extra_suffixes) {
_cleanup_free_ char *dot_v = strjoin(*suffix, ".v");
if (!dot_v)
return log_oom();
bool versioned;
_cleanup_free_ char *key = NULL;
if (endswith(de->d_name, *suffix)) {
if (de->d_type != DT_REG)
continue;
versioned = false;
key = strdup(de->d_name);
} else if (endswith(de->d_name, dot_v)) {
if (de->d_type != DT_DIR)
continue;
/* The dedup key is the directory entry name with any trailing ".v" stripped, so that
* a plain file and a versioned directory of the same resource collapse, and a
* higher-priority directory wins. */
versioned = true;
key = strndup(de->d_name, strlen(de->d_name) - strlen(".v"));
} else
continue;
if (!key)
return log_oom();
if (set_contains(*seen, key))
break;
if (versioned) {
/* A versioned directory: resolve the best version via vpick, relative to the
* extras directory fd. We deliberately do not honour boot-counting suffixes
* here. */
_cleanup_(pick_result_done) PickResult pick = PICK_RESULT_NULL;
const PickFilter filter = {
.type_mask = UINT32_C(1) << DT_REG,
.architecture = _ARCHITECTURE_INVALID,
.suffix = *suffix,
};
r = path_pick(/* root_path= */ NULL,
/* root_fd= */ c->root_fd,
/* dir_fd= */ extras_dir_fd,
de->d_name,
&filter,
/* n_filters= */ 1,
/* flags= */ 0,
&pick);
if (r == -ENOENT || r == 0) {
log_debug("No matching entry in versioned directory '%s', skipping.", de->d_name);
break;
}
if (r < 0)
return log_error_errno(r, "Failed to pick version in '%s': %m", de->d_name);
_cleanup_free_ char *fn = NULL;
r = path_extract_filename(pick.path, &fn);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from '%s': %m", pick.path);
r = link_context_add_extra(c, extras_dir_fd, pick.path, fn);
} else
/* A plain file. */
r = link_context_add_extra(c, extras_dir_fd, de->d_name, de->d_name);
if (r < 0)
return r;
r = set_ensure_consume(seen, &string_hash_ops_free, TAKE_PTR(key));
if (r < 0)
return log_oom();
break;
}
}
return 0;
}
/* Looks for a UKI (kernel.efi, or the best version in kernel.efi.v/) in the directory referenced by
* uki_dir_fd and, if found, fills c->kernel_fd and c->kernel_filename. Returns 1 if found, 0 if not, and a
* negative errno on error. We deliberately do not honour boot-counting suffixes here. */
static int link_context_find_kernel(LinkContext *c, int uki_dir_fd) {
int r;
assert(c);
assert(uki_dir_fd >= 0);
_cleanup_free_ char *filename = NULL;
_cleanup_close_ int kernel_fd = xopenat_full(
uki_dir_fd, "kernel.efi",
O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW,
XO_REGULAR,
/* mode= */ MODE_INVALID);
if (kernel_fd == -ENOENT) {
_cleanup_(pick_result_done) PickResult pick = PICK_RESULT_NULL;
r = path_pick(/* root_path= */ NULL,
/* root_fd= */ c->root_fd,
/* dir_fd= */ uki_dir_fd,
"kernel.efi.v",
&pick_filter_uki,
/* n_filters= */ 1,
/* flags= */ 0,
&pick);
if (r == -ENOENT || r == 0)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to pick UKI version in 'kernel.efi.v': %m");
r = path_extract_filename(pick.path, &filename);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from '%s': %m", pick.path);
kernel_fd = fd_reopen(pick.fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (kernel_fd < 0)
return log_error_errno(kernel_fd, "Failed to open UKI '%s': %m", pick.path);
r = fd_verify_regular(kernel_fd);
if (r < 0)
return log_error_errno(r, "UKI '%s' is not a regular file: %m", pick.path);
} else if (kernel_fd < 0)
return log_error_errno(kernel_fd, "Failed to open 'kernel.efi': %m");
else {
filename = strdup("kernel.efi");
if (!filename)
return log_oom();
}
if (!efi_loader_entry_resource_filename_valid(filename))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UKI '%s' is not suitable for reference in a boot menu entry.", filename);
r = validate_kernel(kernel_fd, filename);
if (r < 0)
return r;
c->kernel_fd = TAKE_FD(kernel_fd);
c->kernel_filename = TAKE_PTR(filename);
return 1;
}
/* Discovers the staged UKI and extra resources below the uki_auto_dirs[] (relative to the already-pinned
* c->root_fd) and fills c->kernel_fd, c->kernel_filename and c->extra[] accordingly. The directories are
* searched in priority order: the UKI is taken from the first directory that has one, and extra resources
* are combined across all directories with higher-priority directories winning on conflicts. Returns 1 if a
* UKI was found, 0 if there is nothing to link, and a negative errno on error. */
static int link_context_discover_resources(LinkContext *c) {
_cleanup_set_free_ Set *seen = NULL;
int r;
assert(c);
FOREACH_ELEMENT(dir, uki_auto_dirs) {
_cleanup_close_ int uki_dir_fd = chase_and_openat(
/* root_fd= */ c->root_fd,
/* dir_fd= */ c->root_fd,
*dir,
CHASE_MUST_BE_DIRECTORY,
O_RDONLY|O_DIRECTORY|O_CLOEXEC,
/* ret_path= */ NULL);
if (uki_dir_fd == -ENOENT)
continue;
if (uki_dir_fd < 0)
return log_error_errno(uki_dir_fd, "Failed to open '%s': %m", *dir);
/* Take the UKI from the first (highest-priority) directory that has one. */
if (c->kernel_fd < 0) {
r = link_context_find_kernel(c, uki_dir_fd);
if (r < 0)
return r;
}
r = link_context_add_extras(c, uki_dir_fd, &seen);
if (r < 0)
return r;
}
if (c->kernel_fd < 0) {
log_debug("No UKI found in any of the search directories, nothing to link.");
return 0;
}
return 1;
}
int verb_link_auto(int argc, char *argv[], uintptr_t data, void *userdata) {
int r;
assert(argc == 1);
_cleanup_(link_context_done) LinkContext c = LINK_CONTEXT_NULL;
r = link_context_from_cmdline_common(&c);
if (r < 0)
return r;
r = link_context_discover_resources(&c);
if (r < 0)
return r;
if (r == 0) {
log_info("No UKI staged for linking, nothing to do.");
return 0;
}
/* In addition to the auto-discovered resources, also honour any files passed via --extra=. */
r = link_context_add_cmdline_extras(&c);
if (r < 0)
return r;
r = link_context_acquire_dollar_boot(&c);
if (r < 0)
return r;
return run_link(&c);
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string); static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string);
typedef struct LinkParameters { typedef struct LinkParameters {
@@ -1089,6 +1445,107 @@ static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatc
return 0; return 0;
} }
/* Resolves the root_fd of the LinkContext from the dispatched Link/LinkAuto parameters, fills in the
* boot entry token type default, and validates the entry title/version/commit fields. Returns 0 on success,
* non-zero (Varlink error or negative errno) otherwise. */
static int vl_link_prepare(sd_varlink *link, LinkParameters *p) {
int r;
assert(link);
assert(p);
if (p->root_fd_index != UINT_MAX) {
p->context.root_fd = sd_varlink_peek_dup_fd(link, p->root_fd_index);
if (p->context.root_fd < 0)
return log_debug_errno(p->context.root_fd, "Failed to acquire root fd from Varlink: %m");
r = fd_verify_safe_flags_full(p->context.root_fd, O_DIRECTORY);
if (r < 0)
return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor");
r = fd_verify_directory(p->context.root_fd);
if (r < 0)
return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
if (!p->context.root) {
r = fd_get_path(p->context.root_fd, &p->context.root);
if (r < 0)
return log_debug_errno(r, "Failed to get path of file descriptor: %m");
if (empty_or_root(p->context.root))
p->context.root = mfree(p->context.root);
}
} else if (p->context.root) {
p->context.root_fd = open(p->context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (p->context.root_fd < 0)
return log_debug_errno(errno, "Failed to open '%s': %m", p->context.root);
} else
p->context.root_fd = XAT_FDROOT;
if (p->context.entry_token_type < 0)
p->context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
if (p->context.entry_title && !efi_loader_entry_title_valid(p->context.entry_title))
return sd_varlink_error_invalid_parameter_name(link, "entryTitle");
if (p->context.entry_version && !version_is_valid_versionspec(p->context.entry_version))
return sd_varlink_error_invalid_parameter_name(link, "entryVersion");
if (p->context.entry_commit != 0 && !entry_commit_valid(p->context.entry_commit))
return sd_varlink_error_invalid_parameter_name(link, "entryCommit");
return 0;
}
/* Resolves $BOOT, runs the link operation and sends the reply. Expects the kernel and extra files to already
* be set up in the context. If with_ids is true the reply carries the list of created entry IDs (as expected
* by Link()/LinkAuto()), otherwise an empty reply is sent (as expected by
* io.systemd.SysUpdate.Notify.OnCompletedUpdate(), which declares no output fields). */
static int vl_link_finish(sd_varlink *link, LinkParameters *p, bool with_ids) {
int r;
assert(link);
assert(p);
r = find_xbootldr_and_warn_at(
p->context.root_fd,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&p->context.dollar_boot_path,
&p->context.dollar_boot_fd);
if (r < 0) {
if (r != -ENOKEY)
return r;
/* No XBOOTLDR found, let's look for ESP then. */
r = find_esp_and_warn_at(
p->context.root_fd,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&p->context.dollar_boot_path,
&p->context.dollar_boot_fd);
if (r == -ENOKEY)
return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL);
if (r < 0)
return r;
p->context.dollar_boot_source = BOOT_ENTRY_ESP;
} else
p->context.dollar_boot_source = BOOT_ENTRY_XBOOTLDR;
r = run_link(&p->context);
if (r == -EUNATCH) /* no boot entry token is set */
return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL);
if (r < 0)
return r;
if (with_ids)
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", p->context.linked_ids));
return sd_varlink_reply(link, NULL);
}
int vl_method_link( int vl_method_link(
sd_varlink *link, sd_varlink *link,
sd_json_variant *parameters, sd_json_variant *parameters,
@@ -1125,45 +1582,13 @@ int vl_method_link(
if (r != 0) if (r != 0)
return r; return r;
if (p.root_fd_index != UINT_MAX) { r = varlink_check_privileged_peer(link);
p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index); if (r < 0)
if (p.context.root_fd < 0) return r;
return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m");
r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY); r = vl_link_prepare(link, &p);
if (r < 0) if (r != 0)
return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor"); return r;
r = fd_verify_directory(p.context.root_fd);
if (r < 0)
return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
if (!p.context.root) {
r = fd_get_path(p.context.root_fd, &p.context.root);
if (r < 0)
return log_debug_errno(r, "Failed to get path of file descriptor: %m");
if (empty_or_root(p.context.root))
p.context.root = mfree(p.context.root);
}
} else if (p.context.root) {
p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (p.context.root_fd < 0)
return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root);
} else
p.context.root_fd = XAT_FDROOT;
if (p.context.entry_token_type < 0)
p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
if (p.context.entry_title && !efi_loader_entry_title_valid(p.context.entry_title))
return sd_varlink_error_invalid_parameter_name(link, "entryTitle");
if (p.context.entry_version && !version_is_valid_versionspec(p.context.entry_version))
return sd_varlink_error_invalid_parameter_name(link, "entryVersion");
if (p.context.entry_commit != 0 && !entry_commit_valid(p.context.entry_commit))
return sd_varlink_error_invalid_parameter_name(link, "entryCommit");
p.context.kernel_fd = sd_varlink_peek_dup_fd(link, p.kernel_fd_index); p.context.kernel_fd = sd_varlink_peek_dup_fd(link, p.kernel_fd_index);
if (p.context.kernel_fd < 0) if (p.context.kernel_fd < 0)
@@ -1186,38 +1611,95 @@ int vl_method_link(
if (kit != KERNEL_IMAGE_TYPE_UKI) if (kit != KERNEL_IMAGE_TYPE_UKI)
return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL); return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL);
r = find_xbootldr_and_warn_at( return vl_link_finish(link, &p, /* with_ids= */ true);
p.context.root_fd, }
/* path= */ NULL,
/* unprivileged_mode= */ false,
&p.context.dollar_boot_path,
&p.context.dollar_boot_fd);
if (r < 0) {
if (r != -ENOKEY)
return r;
/* No XBOOTLDR found, let's look for ESP then. */ int vl_method_link_auto(
sd_varlink *link,
sd_json_variant *parameters,
sd_varlink_method_flags_t flags,
void *userdata) {
r = find_esp_and_warn_at( int r;
p.context.root_fd,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&p.context.dollar_boot_path,
&p.context.dollar_boot_fd);
if (r == -ENOKEY)
return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL);
if (r < 0)
return r;
p.context.dollar_boot_source = BOOT_ENTRY_ESP; assert(link);
} else
p.context.dollar_boot_source = BOOT_ENTRY_XBOOTLDR;
r = run_link(&p.context); _cleanup_(link_parameters_done) LinkParameters p = {
if (r == -EUNATCH) /* no boot entry token is set */ .context = LINK_CONTEXT_NULL,
return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL); .root_fd_index = UINT_MAX,
.kernel_fd_index = UINT_MAX,
.link = link,
};
static const sd_json_dispatch_field dispatch_table[] = {
{ "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, root_fd_index), 0 },
{ "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, voffsetof(p, context.root), 0 },
{ "bootEntryTokenType", SD_JSON_VARIANT_STRING, json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 },
{ "entryTitle", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_title), 0 },
{ "entryVersion", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_version), 0 },
{ "entryCommit", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, voffsetof(p, context.entry_commit), 0 },
{ "triesLeft", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, context.tries_left), 0 },
{ "keepFree", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, context.keep_free), 0 },
{},
};
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
r = varlink_check_privileged_peer(link);
if (r < 0) if (r < 0)
return r; return r;
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", p.context.linked_ids)); r = vl_link_prepare(link, &p);
if (r != 0)
return r;
r = link_context_discover_resources(&p.context);
if (r < 0)
return r;
if (r == 0) /* Nothing staged for linking. */
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", STRV_EMPTY));
return vl_link_finish(link, &p, /* with_ids= */ true);
}
int vl_method_on_completed_update(
sd_varlink *link,
sd_json_variant *parameters,
sd_varlink_method_flags_t flags,
void *userdata) {
int r;
assert(link);
/* Only honour update notifications if they come from root */
r = varlink_check_privileged_peer(link);
if (r < 0)
return r;
/* Triggered by systemd-sysupdate after an update completed. We deliberately ignore all parameters
* (we don't even dispatch them) and behave like LinkAuto() with default parameters: discover the
* staged UKI and extra resources and link them in. Contrary to LinkAuto() we reply without any
* parameters, matching the io.systemd.SysUpdate.Notify.OnCompletedUpdate() signature. */
_cleanup_(link_parameters_done) LinkParameters p = {
.context = LINK_CONTEXT_NULL,
.root_fd_index = UINT_MAX,
.kernel_fd_index = UINT_MAX,
.link = link,
};
r = vl_link_prepare(link, &p);
if (r != 0)
return r;
r = link_context_discover_resources(&p.context);
if (r < 0)
return r;
if (r == 0) /* Nothing staged for linking. */
return sd_varlink_reply(link, NULL);
return vl_link_finish(link, &p, /* with_ids= */ false);
} }
+3
View File
@@ -4,5 +4,8 @@
#include "shared-forward.h" #include "shared-forward.h"
int verb_link(int argc, char *argv[], uintptr_t data, void *userdata); int verb_link(int argc, char *argv[], uintptr_t data, void *userdata);
int verb_link_auto(int argc, char *argv[], uintptr_t data, void *userdata);
int vl_method_link(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_link_auto(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_on_completed_update(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+5
View File
@@ -11,6 +11,7 @@
#include "errno-util.h" #include "errno-util.h"
#include "log.h" #include "log.h"
#include "parse-util.h" #include "parse-util.h"
#include "varlink-util.h"
int verb_reboot_to_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { int verb_reboot_to_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) {
int r; int r;
@@ -61,6 +62,10 @@ int vl_method_set_reboot_to_firmware(sd_varlink *link, sd_json_variant *paramete
if (r != 0) if (r != 0)
return r; return r;
r = varlink_check_privileged_peer(link);
if (r < 0)
return r;
r = efi_set_reboot_to_firmware(b); r = efi_set_reboot_to_firmware(b);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
return sd_varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); return sd_varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL);
+5
View File
@@ -26,6 +26,7 @@
#include "stat-util.h" #include "stat-util.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
#include "varlink-util.h"
typedef struct UnlinkContext { typedef struct UnlinkContext {
char *root; char *root;
@@ -593,6 +594,10 @@ int vl_method_unlink(
if (p.id && !efi_loader_entry_name_valid(p.id)) if (p.id && !efi_loader_entry_name_valid(p.id))
return sd_varlink_error_invalid_parameter_name(link, "id"); return sd_varlink_error_invalid_parameter_name(link, "id");
r = varlink_check_privileged_peer(link);
if (r < 0)
return r;
if (p.root_fd_index != UINT_MAX) { if (p.root_fd_index != UINT_MAX) {
p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index); p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index);
if (p.context.root_fd < 0) if (p.context.root_fd < 0)
+17 -8
View File
@@ -43,6 +43,7 @@
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
#include "varlink-io.systemd.BootControl.h" #include "varlink-io.systemd.BootControl.h"
#include "varlink-io.systemd.SysUpdate.Notify.h"
#include "varlink-util.h" #include "varlink-util.h"
#include "verbs.h" #include "verbs.h"
#include "virt.h" #include "virt.h"
@@ -380,6 +381,9 @@ VERB_SCOPE(, verb_unlink, "unlink", "ID", VERB_ANY, 2, 0,
VERB_SCOPE(, verb_link, "link", "KERNEL", 2, 2, 0, VERB_SCOPE(, verb_link, "link", "KERNEL", 2, 2, 0,
"Create boot loader entry for specified kernel"); "Create boot loader entry for specified kernel");
VERB_SCOPE_NOARG(, verb_link_auto, "link-auto",
"Create boot loader entry for the kernel and extra resources staged in /var/lib/systemd/uki/");
VERB_SCOPE_NOARG(, verb_cleanup, "cleanup", VERB_SCOPE_NOARG(, verb_cleanup, "cleanup",
"Remove files in ESP not referenced in any boot entry"); "Remove files in ESP not referenced in any boot entry");
@@ -822,18 +826,23 @@ static int vl_server(void) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m"); return log_error_errno(r, "Failed to allocate Varlink server: %m");
r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_BootControl); r = sd_varlink_server_add_interface_many(
varlink_server,
&vl_interface_io_systemd_BootControl,
&vl_interface_io_systemd_SysUpdate_Notify);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m"); return log_error_errno(r, "Failed to add Varlink interfaces: %m");
r = sd_varlink_server_bind_method_many( r = sd_varlink_server_bind_method_many(
varlink_server, varlink_server,
"io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries, "io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries,
"io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware, "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware,
"io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware, "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware,
"io.systemd.BootControl.Install", vl_method_install, "io.systemd.BootControl.Install", vl_method_install,
"io.systemd.BootControl.Link", vl_method_link, "io.systemd.BootControl.Link", vl_method_link,
"io.systemd.BootControl.Unlink", vl_method_unlink); "io.systemd.BootControl.LinkAuto", vl_method_link_auto,
"io.systemd.BootControl.Unlink", vl_method_unlink,
"io.systemd.SysUpdate.Notify.OnCompletedUpdate", vl_method_on_completed_update);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m"); return log_error_errno(r, "Failed to bind Varlink methods: %m");
@@ -185,6 +185,27 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_FIELD_COMMENT("The IDs of the created boot loader entries."), SD_VARLINK_FIELD_COMMENT("The IDs of the created boot loader entries."),
SD_VARLINK_DEFINE_OUTPUT(ids, SD_VARLINK_STRING, SD_VARLINK_ARRAY)); SD_VARLINK_DEFINE_OUTPUT(ids, SD_VARLINK_STRING, SD_VARLINK_ARRAY));
static SD_VARLINK_DEFINE_METHOD(
LinkAuto,
SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"),
SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."),
SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The entry title for the newly created boot menu entry"),
SD_VARLINK_DEFINE_INPUT(entryTitle, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The entry version for the newly created boot menu entry"),
SD_VARLINK_DEFINE_INPUT(entryVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The commit number for the newly created boot menu entry"),
SD_VARLINK_DEFINE_INPUT(entryCommit, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("What to set the triesLeft counter of the boot menu entry to initially."),
SD_VARLINK_DEFINE_INPUT(triesLeft, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("How much space to always keep free on ESP/XBOOTLDR. Defaults to 1 MiB"),
SD_VARLINK_DEFINE_INPUT(keepFree, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The IDs of the created boot loader entries."),
SD_VARLINK_DEFINE_OUTPUT(ids, SD_VARLINK_STRING, SD_VARLINK_ARRAY));
static SD_VARLINK_DEFINE_ERROR( static SD_VARLINK_DEFINE_ERROR(
RebootToFirmwareNotSupported); RebootToFirmwareNotSupported);
@@ -233,6 +254,8 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_method_Unlink, &vl_method_Unlink,
SD_VARLINK_SYMBOL_COMMENT("Install a kernel as boot menu item"), SD_VARLINK_SYMBOL_COMMENT("Install a kernel as boot menu item"),
&vl_method_Link, &vl_method_Link,
SD_VARLINK_SYMBOL_COMMENT("Like Link(), but automatically discovers the UKI and extra resources staged below /var/lib/systemd/uki/"),
&vl_method_LinkAuto,
SD_VARLINK_SYMBOL_COMMENT("SetRebootToFirmware() and GetRebootToFirmware() return this if the firmware does not actually support the reboot-to-firmware-UI concept."), SD_VARLINK_SYMBOL_COMMENT("SetRebootToFirmware() and GetRebootToFirmware() return this if the firmware does not actually support the reboot-to-firmware-UI concept."),
&vl_error_RebootToFirmwareNotSupported, &vl_error_RebootToFirmwareNotSupported,
SD_VARLINK_SYMBOL_COMMENT("No boot entry defined."), SD_VARLINK_SYMBOL_COMMENT("No boot entry defined."),
+8
View File
@@ -303,6 +303,14 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER'], 'conditions' : ['ENABLE_BOOTLOADER'],
'symlinks' : ['sockets.target.wants/'], 'symlinks' : ['sockets.target.wants/'],
}, },
{
'file' : 'systemd-sysupdate-notify-bootctl@.service',
'conditions' : ['ENABLE_BOOTLOADER'],
},
{
'file' : 'systemd-sysupdate-notify-bootctl.socket',
'conditions' : ['ENABLE_BOOTLOADER'],
},
{ {
'file' : 'systemd-confext.service', 'file' : 'systemd-confext.service',
'conditions' : ['ENABLE_SYSEXT'], 'conditions' : ['ENABLE_SYSEXT'],
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Boot Loader UKI Auto-Link on Completed System Update
Documentation=man:bootctl(1)
DefaultDependencies=no
ConditionFirmware=uefi
After=local-fs.target
Before=sockets.target
[Socket]
ListenStream=/run/systemd/sysupdate/notify/io.systemd.BootControl
FileDescriptorName=varlink
SocketMode=0644
Accept=yes
MaxConnectionsPerSource=16
XAttrEntryPoint=user.varlink=entrypoint
XAttrListen=user.varlink=listen
XAttrAccept=user.varlink=server
[Install]
WantedBy=sockets.target
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Boot Loader UKI Auto-Link on Completed System Update
Documentation=man:bootctl(1)
DefaultDependencies=no
Conflicts=shutdown.target
After=local-fs.target
Before=shutdown.target
[Service]
ExecStart=bootctl