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:
@@ -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">
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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."),
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user