diff --git a/TODO b/TODO
index 18fdf4e257..bcf1d02b94 100644
--- a/TODO
+++ b/TODO
@@ -91,17 +91,12 @@ Features:
show state of these flags, and optionally trigger such a factory reset on
next boot by setting the flag.
-* sd-boot: add "oneshot boot timeout" variable support
-
* sd-boot: automatically load EFI modules from some drop-in dir, so that people
can add in file system drivers and such
* sd-boot: optionally, show boot menu when previous default boot item has
non-zero "tries done" count
-* logind: add "boot into bootmenu" API, and possibly even "boot into windows"
- and "boot into macos".
-
* maybe set a special xattr on cgroups that have delegate=yes set, to make it
easy to mark cut points
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 99b5b03b68..f72ed20dd0 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -107,6 +107,51 @@ systemd-udevd:
with `:` in which case the kernel command line option takes precedence, if it
is specified as well.
+* `$SYSTEMD_REBOOT_TO_FIRMWARE_SETUP` — if set overrides systemd-logind's
+ built-in EFI logic of requesting a reboot into the firmware. Takes a
+ boolean. If set to false the functionality is turned off entirely. If set to
+ true instead of requesting a reboot into the firmware setup UI through EFI a
+ file `/run/systemd/reboot-to-firmware-setup` is created whenever this is
+ requested. This file may be checked for by services run during system
+ shutdown in order to request the appropriate operation from the firmware in
+ an alternative fashion.
+
+* `$SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU` — similar to the above, allows
+ overriding of systemd-logind's built-in EFI logic of requesting a reboot into
+ the boot loader menu. Takes a boolean. If set to false the functionality is
+ turned off entirely. If set to true instead of requesting a reboot into the
+ boot loader menu through EFI a file `/run/systemd/reboot-to-boot-loader-menu`
+ is created whenever this is requested. The file contains the requested boot
+ loader menu timeout in µs, formatted in ASCII decimals, or zero in case no
+ time-out is requested. This file may be checked for by services run during
+ system shutdown in order to request the appropriate operation from the boot
+ loader in an alternative fashion.
+
+* `$SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY` — similar to the above, allows
+ overriding of systemd-logind's built-in EFI logic of requesting a reboot into
+ a specific boot loader entry. Takes a boolean. If set to false the
+ functionality is turned off entirely. If set to true instead of requesting a
+ reboot into a specific boot loader entry through EFI a file
+ `/run/systemd/reboot-to-boot-loader-entry` is created whenever this is
+ requested. The file contains the requested boot loader entry identifier. This
+ file may be checked for by services run during system shutdown in order to
+ request the appropriate operation from the boot loader in an alternative
+ fashion. Note that by default only boot loader entries which follow the [Boot
+ Loader Specification](https://systemd.io/BOOT_LOADER_SPECIFICATION) and are
+ placed in the ESP or the Extended Boot Loader partition may be selected this
+ way. However, if a directory `/run/boot-loader-entries/` exists, the entries
+ are loaded from there instead. The directory should contain the usual
+ directory hierarchy mandated by the Boot Loader Specification, i.e. the entry
+ drop-ins should be placed in
+ `/run/boot-loader-entries/loader/entries/*.conf`, and the files referenced by
+ the drop-ins (including the kernels and initrds) somewhere else below
+ `/run/boot-loader-entries/`. Note that all these files may be (and are
+ supposed to be) symlinks. systemd-logind will load these files on-demand,
+ these files can hence be updated (ideally atomically) whenever the boot
+ loader configuration changes. A foreign boot loader installer script should
+ hence synthesize drop-in snippets and symlinks for all boot entries at boot
+ or whenever they change if it wants to integrate with systemd-logind's APIs.
+
installed systemd tests:
* `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if
diff --git a/man/systemctl.xml b/man/systemctl.xml
index 08aacd8f41..512c255b39 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -617,11 +617,30 @@
- When used with the reboot command,
- indicate to the system's firmware to boot into setup
- mode. Note that this is currently only supported on some EFI
- systems and only if the system was booted in EFI
- mode.
+ When used with the reboot command, indicate to the system's firmware to reboot into
+ the firmware setup interface. Note that this functionality is not available on all systems.
+
+
+
+
+
+
+
+ When used with the reboot command, indicate to the system's boot loader to show the
+ boot loader menu on the following boot. Takes a time value as parameter — indicating the menu time-out. Pass
+ zero in order to disable the menu time-out. Note that not all boot loaders support this
+ functionality.
+
+
+
+
+
+
+
+ When used with the reboot command, indicate to the system's boot loader to boot into
+ a specific boot loader entry on the following boot. Takes a boot loader entry identifier as argument, or
+ help in order to list available entries. Note that not all boot loaders support this
+ functionality.
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
index 1e0d115fe3..b5f4b8e5a3 100644
--- a/src/boot/bootctl.c
+++ b/src/boot/bootctl.c
@@ -311,13 +311,15 @@ static int status_variables(void) {
static int boot_entry_show(const BootEntry *e, bool show_as_default) {
assert(e);
- printf(" title: %s%s%s%s%s%s\n",
+ printf(" title: %s%s%s%s%s%s\n"
+ " type: %s\n",
ansi_highlight(),
boot_entry_title(e),
ansi_normal(),
ansi_highlight_green(),
show_as_default ? " (default)" : "",
- ansi_normal());
+ ansi_normal(),
+ boot_entry_type_to_string(e->type));
if (e->id)
printf(" id: %s\n", e->id);
@@ -1173,7 +1175,6 @@ static int verb_status(int argc, char *argv[], void *userdata) {
static int verb_list(int argc, char *argv[], void *userdata) {
_cleanup_(boot_config_free) BootConfig config = {};
- _cleanup_free_ char **found_by_loader = NULL;
int r;
/* If we lack privileges we invoke find_esp_and_warn() in "unprivileged mode" here, which does two things: turn
@@ -1196,9 +1197,7 @@ static int verb_list(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
- r = efi_loader_get_entries(&found_by_loader);
- if (r < 0 && !IN_SET(r, -ENOENT, -EOPNOTSUPP))
- log_debug_errno(r, "Failed to acquire boot loader discovered entries: %m");
+ (void) boot_entries_augment_from_loader(&config, false);
if (config.n_entries == 0)
log_info("No boot loader entries found.");
@@ -1214,21 +1213,11 @@ static int verb_list(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
- puts("");
-
- strv_remove(found_by_loader, config.entries[n].id);
+ if (n+1 < config.n_entries)
+ putchar('\n');
}
}
- if (!strv_isempty(found_by_loader)) {
- char **i;
-
- printf("Automatic/Other Entries Found by Boot Loader:\n\n");
-
- STRV_FOREACH(i, found_by_loader)
- puts(*i);
- }
-
return 0;
}
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index b9ea370ec0..13e7d3bcda 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -10,6 +10,7 @@
#include "alloc-util.h"
#include "audit-util.h"
+#include "bootspec.h"
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-unit-util.h"
@@ -18,6 +19,7 @@
#include "device-util.h"
#include "dirent-util.h"
#include "efivars.h"
+#include "env-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio-label.h"
@@ -27,11 +29,13 @@
#include "logind.h"
#include "missing_capability.h"
#include "mkdir.h"
+#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "selinux-util.h"
#include "sleep-config.h"
#include "special.h"
+#include "stdio-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
@@ -2392,9 +2396,24 @@ static int property_get_reboot_to_firmware_setup(
assert(reply);
assert(userdata);
- r = efi_get_reboot_to_firmware();
- if (r < 0 && r != -EOPNOTSUPP)
- log_warning_errno(r, "Failed to determine reboot-to-firmware state: %m");
+ r = getenv_bool("SYSTEMD_REBOOT_TO_FIRMWARE_SETUP");
+ if (r == -ENXIO) {
+ /* EFI case: let's see what is currently configured in the EFI variables */
+ r = efi_get_reboot_to_firmware();
+ if (r < 0 && r != -EOPNOTSUPP)
+ log_warning_errno(r, "Failed to determine reboot-to-firmware-setup state: %m");
+ } else if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP: %m");
+ else if (r > 0) {
+ /* Non-EFI case: let's see whether /run/systemd/reboot-to-firmware-setup exists. */
+ if (access("/run/systemd/reboot-to-firmware-setup", F_OK) < 0) {
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to check whether /run/systemd/reboot-to-firmware-setup exists: %m");
+
+ r = false;
+ } else
+ r = true;
+ }
return sd_bus_message_append(reply, "b", r > 0);
}
@@ -2404,8 +2423,9 @@ static int method_set_reboot_to_firmware_setup(
void *userdata,
sd_bus_error *error) {
- int b, r;
Manager *m = userdata;
+ bool use_efi;
+ int b, r;
assert(message);
assert(m);
@@ -2414,6 +2434,29 @@ static int method_set_reboot_to_firmware_setup(
if (r < 0)
return r;
+ r = getenv_bool("SYSTEMD_REBOOT_TO_FIRMWARE_SETUP");
+ if (r == -ENXIO) {
+ /* EFI case: let's see what the firmware supports */
+
+ r = efi_reboot_to_firmware_supported();
+ if (r == -EOPNOTSUPP)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Firmware does not support boot into firmware.");
+ if (r < 0)
+ return r;
+
+ use_efi = true;
+
+ } else if (r <= 0) {
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP is set to off */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP: %m");
+
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Firmware does not support boot into firmware.");
+ } else
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP is set to on */
+ use_efi = false;
+
r = bus_verify_polkit_async(message,
CAP_SYS_ADMIN,
"org.freedesktop.login1.set-reboot-to-firmware-setup",
@@ -2427,41 +2470,37 @@ static int method_set_reboot_to_firmware_setup(
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
- r = efi_set_reboot_to_firmware(b);
- if (r < 0)
- return r;
+ if (use_efi) {
+ r = efi_set_reboot_to_firmware(b);
+ if (r < 0)
+ return r;
+ } else {
+ if (b) {
+ r = touch("/run/systemd/reboot-to-firmware-setup");
+ if (r < 0)
+ return r;
+ } else {
+ if (unlink("/run/systemd/reboot-to-firmware-setup") < 0 && errno != ENOENT)
+ return -errno;
+ }
+ }
return sd_bus_reply_method_return(message, NULL);
}
-static int method_can_reboot_to_firmware_setup(
+static int return_test_polkit(
sd_bus_message *message,
- void *userdata,
- sd_bus_error *error) {
+ int capability,
+ const char *action,
+ const char **details,
+ uid_t good_user,
+ sd_bus_error *e) {
- int r;
- bool challenge;
const char *result;
- Manager *m = userdata;
+ bool challenge;
+ int r;
- assert(message);
- assert(m);
-
- r = efi_reboot_to_firmware_supported();
- if (r < 0) {
- if (r != -EOPNOTSUPP)
- log_warning_errno(r, "Failed to determine whether reboot to firmware is supported: %m");
-
- return sd_bus_reply_method_return(message, "s", "na");
- }
-
- r = bus_test_polkit(message,
- CAP_SYS_ADMIN,
- "org.freedesktop.login1.set-reboot-to-firmware-setup",
- NULL,
- UID_INVALID,
- &challenge,
- error);
+ r = bus_test_polkit(message, capability, action, details, good_user, &challenge, e);
if (r < 0)
return r;
@@ -2475,6 +2514,466 @@ static int method_can_reboot_to_firmware_setup(
return sd_bus_reply_method_return(message, "s", result);
}
+static int method_can_reboot_to_firmware_setup(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_FIRMWARE_SETUP");
+ if (r == -ENXIO) {
+ /* EFI case: let's see what the firmware supports */
+
+ r = efi_reboot_to_firmware_supported();
+ if (r < 0) {
+ if (r != -EOPNOTSUPP)
+ log_warning_errno(r, "Failed to determine whether reboot to firmware is supported: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ } else if (r <= 0) {
+ /* Non-EFI case: let's trust $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-firmware-setup",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
+static int property_get_reboot_to_boot_loader_menu(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ uint64_t x = UINT64_MAX;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU");
+ if (r == -ENXIO) {
+ _cleanup_free_ char *v = NULL;
+
+ /* EFI case: returns the current value of LoaderConfigTimeoutOneShot. Three cases are distuingished:
+ *
+ * 1. Variable not set, boot into boot loader menu is not enabled (we return UINT64_MAX to the user)
+ * 2. Variable set to "0", boot into boot loader menu is enabled with no timeout (we return 0 to the user)
+ * 3. Variable set to numeric value formatted in ASCII, boot into boot loader menu with the specified timeout in seconds
+ */
+
+ r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderConfigTimeoutOneShot", &v);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read LoaderConfigTimeoutOneShot variable: %m");
+ } else {
+ uint64_t sec;
+
+ r = safe_atou64(v, &sec);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse LoaderConfigTimeoutOneShot value '%s': %m", v);
+ else if (sec > (USEC_INFINITY / USEC_PER_SEC))
+ log_warning("LoaderConfigTimeoutOneShot too large, ignoring: %m");
+ else
+ x = sec * USEC_PER_SEC; /* return in µs */
+ }
+
+ } else if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU: %m");
+ else if (r > 0) {
+ _cleanup_free_ char *v = NULL;
+
+ /* Non-EFI case, let's process /run/systemd/reboot-to-boot-loader-menu. */
+
+ r = read_one_line_file("/run/systemd/reboot-to-boot-loader-menu", &v);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read /run/systemd/reboot-to-boot-loader-menu: %m");
+ } else {
+ r = safe_atou64(v, &x);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse /run/systemd/reboot-to-boot-loader-menu: %m");
+ }
+ }
+
+ return sd_bus_message_append(reply, "t", x);
+}
+
+static int method_set_reboot_to_boot_loader_menu(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ bool use_efi;
+ uint64_t x;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "t", &x);
+ if (r < 0)
+ return r;
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU");
+ if (r == -ENXIO) {
+ uint64_t features;
+
+ /* EFI case: let's see if booting into boot loader menu is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot to boot loader menu is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Boot loader does not support boot into boot loader menu.");
+
+ use_efi = true;
+
+ } else if (r <= 0) {
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU is set to off */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU: %m");
+
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Boot loader does not support boot into boot loader menu.");
+ } else
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU is set to on */
+ use_efi = false;
+
+ r = bus_verify_polkit_async(message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-menu",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ if (use_efi) {
+ if (x == UINT64_MAX)
+ r = efi_set_variable(EFI_VENDOR_LOADER, "LoaderConfigTimeoutOneShot", NULL, 0);
+ else {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1];
+ xsprintf(buf, "%" PRIu64, DIV_ROUND_UP(x, USEC_PER_SEC)); /* second granularity */
+
+ r = efi_set_variable_string(EFI_VENDOR_LOADER, "LoaderConfigTimeoutOneShot", buf);
+ }
+ if (r < 0)
+ return r;
+ } else {
+ if (x == UINT64_MAX) {
+ if (unlink("/run/systemd/reboot-to-loader-menu") < 0 && errno != ENOENT)
+ return -errno;
+ } else {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1];
+
+ xsprintf(buf, "%" PRIu64, x); /* µs granularity */
+
+ r = write_string_file_atomic_label("/run/systemd/reboot-to-loader-menu", buf);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_to_boot_loader_menu(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ int r;
+
+
+ assert(message);
+ assert(m);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU");
+ if (r == -ENXIO) {
+ uint64_t features = 0;
+
+ /* EFI case, let's see if booting into boot loader menu is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot to boot loader menu is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT))
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ } else if (r <= 0) {
+ /* Non-EFI case: let's trust $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-menu",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
+static int property_get_reboot_to_boot_loader_entry(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *v = NULL;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY");
+ if (r == -ENXIO) {
+ /* EFI case: let's read the LoaderEntryOneShot variable */
+
+ r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &v);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read LoaderEntryOneShot variable: %m");
+ } else if (!efi_loader_entry_name_valid(v)) {
+ log_warning("LoaderEntryOneShot contains invalid entry name '%s', ignoring.", v);
+ v = mfree(v);
+ }
+ } else if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY: %m");
+ else if (r > 0) {
+
+ /* Non-EFI case, let's process /run/systemd/reboot-to-boot-loader-entry. */
+
+ r = read_one_line_file("/run/systemd/reboot-to-boot-loader-entry", &v);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read /run/systemd/reboot-to-boot-loader-entry: %m");
+ } else if (!efi_loader_entry_name_valid(v)) {
+ log_warning("/run/systemd/reboot-to-boot-loader-entry is not valid, ignoring.");
+ v = mfree(v);
+ }
+ }
+
+ return sd_bus_message_append(reply, "s", v);
+}
+
+static int boot_loader_entry_exists(const char *id) {
+ _cleanup_(boot_config_free) BootConfig config = {};
+ int r;
+
+ assert(id);
+
+ r = boot_entries_load_config_auto(NULL, NULL, &config);
+ if (r < 0)
+ return r;
+
+ (void) boot_entries_augment_from_loader(&config, true);
+
+ return boot_config_has_entry(&config, id);
+}
+
+static int method_set_reboot_to_boot_loader_entry(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ bool use_efi;
+ const char *v;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &v);
+ if (r < 0)
+ return r;
+
+ if (isempty(v))
+ v = NULL;
+ else if (efi_loader_entry_name_valid(v)) {
+ r = boot_loader_entry_exists(v);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Boot loader entry '%s' is not known.", v);
+ } else
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Boot loader entry name '%s' is not valid, refusing.", v);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY");
+ if (r == -ENXIO) {
+ uint64_t features;
+
+ /* EFI case: let's see if booting into boot loader entry is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot into boot loader entry is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_ENTRY_ONESHOT))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Loader does not support boot into boot loader entry.");
+
+ use_efi = true;
+
+ } else if (r <= 0) {
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY is set to off */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY: %m");
+
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Loader does not support boot into boot loader entry.");
+ } else
+ /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY is set to on */
+ use_efi = false;
+
+ r = bus_verify_polkit_async(message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-entry",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ if (use_efi) {
+ if (isempty(v))
+ /* Delete item */
+ r = efi_set_variable(EFI_VENDOR_LOADER, "LoaderEntryOneShot", NULL, 0);
+ else
+ r = efi_set_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", v);
+ if (r < 0)
+ return r;
+ } else {
+ if (isempty(v)) {
+ if (unlink("/run/systemd/reboot-to-boot-loader-entry") < 0 && errno != ENOENT)
+ return -errno;
+ } else {
+ r = write_string_file_atomic_label("/run/systemd/reboot-boot-to-loader-entry", v);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_to_boot_loader_entry(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = getenv_bool("SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY");
+ if (r == -ENXIO) {
+ uint64_t features = 0;
+
+ /* EFI case, let's see if booting into boot loader entry is supported. */
+
+ r = efi_loader_get_features(&features);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether reboot to boot loader entry is supported: %m");
+ if (r < 0 || !FLAGS_SET(features, EFI_LOADER_FEATURE_ENTRY_ONESHOT))
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ } else if (r <= 0) {
+ /* Non-EFI case: let's trust $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY */
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY: %m");
+
+ return sd_bus_reply_method_return(message, "s", "na");
+ }
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-boot-loader-entry",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
+static int property_get_boot_loader_entries(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_(boot_config_free) BootConfig config = {};
+ size_t i;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = boot_entries_load_config_auto(NULL, NULL, &config);
+ if (r < 0)
+ return r;
+
+ (void) boot_entries_augment_from_loader(&config, true);
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ return r;
+
+ for (i = 0; i < config.n_entries; i++) {
+ BootEntry *e = config.entries + i;
+
+ r = sd_bus_message_append(reply, "s", e->id);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
static int method_set_wall_message(
sd_bus_message *message,
void *userdata,
@@ -2640,6 +3139,9 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_PROPERTY("KillExcludeUsers", "as", NULL, offsetof(Manager, kill_exclude_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KillUserProcesses", "b", NULL, offsetof(Manager, kill_user_processes), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RebootToFirmwareSetup", "b", property_get_reboot_to_firmware_setup, 0, 0),
+ SD_BUS_PROPERTY("RebootToBootLoaderMenu", "t", property_get_reboot_to_boot_loader_menu, 0, 0),
+ SD_BUS_PROPERTY("RebootToBootLoaderEntry", "s", property_get_reboot_to_boot_loader_entry, 0, 0),
+ SD_BUS_PROPERTY("BootLoaderEntries", "as", property_get_boot_loader_entries, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IdleHint", "b", property_get_idle_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IdleSinceHint", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IdleSinceHintMonotonic", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
@@ -2714,6 +3216,10 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_METHOD("Inhibit", "ssss", "h", method_inhibit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CanRebootToFirmwareSetup", NULL, "s", method_can_reboot_to_firmware_setup, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetRebootToFirmwareSetup", "b", NULL, method_set_reboot_to_firmware_setup, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CanRebootToBootLoaderMenu", NULL, "s", method_can_reboot_to_boot_loader_menu, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetRebootToBootLoaderMenu", "t", NULL, method_set_reboot_to_boot_loader_menu, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CanRebootToBootLoaderEntry", NULL, "s", method_can_reboot_to_boot_loader_entry, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetRebootToBootLoaderEntry", "s", NULL, method_set_reboot_to_boot_loader_entry, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetWallMessage", "sb", NULL, method_set_wall_message, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("SessionNew", "so", 0),
diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf
index f880f3e2da..5677c15b8a 100644
--- a/src/login/org.freedesktop.login1.conf
+++ b/src/login/org.freedesktop.login1.conf
@@ -198,6 +198,22 @@
send_interface="org.freedesktop.login1.Manager"
send_member="SetRebootToFirmwareSetup"/>
+
+
+
+
+
+
+
+
diff --git a/src/login/org.freedesktop.login1.policy b/src/login/org.freedesktop.login1.policy
index 5ee62ab5b4..671384d34e 100644
--- a/src/login/org.freedesktop.login1.policy
+++ b/src/login/org.freedesktop.login1.policy
@@ -338,7 +338,7 @@
- Allow indication to the firmware to boot to setup interface
+ Indicate to the firmware to boot to setup interface
Authentication is required to indicate to the firmware to boot to setup interface.
auth_admin_keep
@@ -348,6 +348,28 @@
org.freedesktop.login1.reboot
+
+
+
+ Indicate to the boot loader to boot a specific entry
+ Authentication is required to indicate to the boot loader to boot into a specific boot loader entry.
+
+ auth_admin_keep
+ auth_admin_keep
+ yes
+
+ org.freedesktop.login1.reboot
+
+
Set a wall message
Authentication is required to set a wall message
diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c
index 53ab042404..39a7a97b12 100644
--- a/src/shared/bootspec.c
+++ b/src/shared/bootspec.c
@@ -22,6 +22,7 @@
#include "path-util.h"
#include "pe-header.h"
#include "stat-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "unaligned.h"
@@ -50,7 +51,10 @@ static int boot_entry_load(
const char *path,
BootEntry *entry) {
- _cleanup_(boot_entry_free) BootEntry tmp = {};
+ _cleanup_(boot_entry_free) BootEntry tmp = {
+ .type = BOOT_ENTRY_CONF,
+ };
+
_cleanup_fclose_ FILE *f = NULL;
unsigned line = 1;
char *b, *c;
@@ -62,13 +66,16 @@ static int boot_entry_load(
c = endswith_no_case(path, ".conf");
if (!c)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", path);
b = basename(path);
tmp.id = strndup(b, c - b);
if (!tmp.id)
return log_oom();
+ if (!efi_loader_entry_name_valid(tmp.id))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
+
tmp.path = strdup(path);
if (!tmp.path)
return log_oom();
@@ -272,7 +279,9 @@ static int boot_entry_load_unified(
BootEntry *ret) {
_cleanup_free_ char *os_pretty_name = NULL, *os_id = NULL, *version_id = NULL, *build_id = NULL;
- _cleanup_(boot_entry_free) BootEntry tmp = {};
+ _cleanup_(boot_entry_free) BootEntry tmp = {
+ .type = BOOT_ENTRY_UNIFIED,
+ };
_cleanup_fclose_ FILE *f = NULL;
const char *k;
int r;
@@ -304,6 +313,9 @@ static int boot_entry_load_unified(
if (!tmp.id)
return log_oom();
+ if (!efi_loader_entry_name_valid(tmp.id))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry: %s", tmp.id);
+
tmp.path = strdup(path);
if (!tmp.path)
return log_oom();
@@ -476,7 +488,7 @@ static int boot_entries_find_unified(
r = fd_verify_regular(fd);
if (r < 0) {
- log_warning_errno(errno, "File %s/%s is not regular, ignoring: %m", dir, de->d_name);
+ log_warning_errno(r, "File %s/%s is not regular, ignoring: %m", dir, de->d_name);
continue;
}
@@ -664,6 +676,118 @@ int boot_entries_load_config(
return 0;
}
+int boot_entries_load_config_auto(
+ const char *override_esp_path,
+ const char *override_xbootldr_path,
+ BootConfig *config) {
+
+ _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
+ int r;
+
+ assert(config);
+
+ /* This function is similar to boot_entries_load_config(), however we automatically search for the
+ * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
+ * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
+ * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
+ * it). This allows other boot loaders to pass boot loader entry information to our tools if they
+ * want to. */
+
+ if (!override_esp_path && !override_xbootldr_path) {
+
+ if (access("/run/boot-loader-entries/", F_OK) < 0) {
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to determine whether /run/boot-loader-entries/ exists: %m");
+ } else {
+ r = boot_entries_load_config("/run/boot-loader-entries/", NULL, config);
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+ }
+
+ r = find_esp_and_warn(override_esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
+ if (r == -ENOKEY) /* find_esp_and_warn() doesn't warn about this case */
+ return log_error_errno(r, "Cannot find the ESP partition mount point.");
+ if (r < 0) /* But it logs about all these cases, hence don't log here again */
+ return r;
+
+ r = find_xbootldr_and_warn(override_xbootldr_path, false, &xbootldr_where, NULL);
+ if (r < 0 && r != -ENOKEY)
+ return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
+
+ r = boot_entries_load_config(esp_where, xbootldr_where, config);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
+
+ static const char * const title_table[] = {
+ /* Pretty names for a few well-known automatically discovered entries. */
+ "auto-osx", "macOS",
+ "auto-windows", "Windows Boot Manager",
+ "auto-efi-shell", "EFI Shell",
+ "auto-efi-default", "EFI Default Loader",
+ "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
+ };
+
+ _cleanup_free_ char **found_by_loader = NULL;
+ size_t n_allocated;
+ char **i;
+ int r;
+
+ assert(config);
+
+ /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
+ * already included there. */
+
+ r = efi_loader_get_entries(&found_by_loader);
+ if (IN_SET(r, -ENOENT, -EOPNOTSUPP))
+ return log_debug_errno(r, "Boot loader reported no entries.");
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine entries reported by boot loader: %m");
+
+ n_allocated = config->n_entries;
+
+ STRV_FOREACH(i, found_by_loader) {
+ _cleanup_free_ char *c = NULL, *t = NULL;
+ char **a, **b;
+
+ if (boot_config_has_entry(config, *i))
+ continue;
+
+ if (only_auto && !startswith(*i, "auto-"))
+ continue;
+
+ c = strdup(*i);
+ if (!c)
+ return log_oom();
+
+ STRV_FOREACH_PAIR(a, b, (char**) title_table)
+ if (streq(*a, *i)) {
+ t = strdup(*b);
+ if (!t)
+ return log_oom();
+ break;
+ }
+
+ if (!GREEDY_REALLOC0(config->entries, n_allocated, config->n_entries + 1))
+ return log_oom();
+
+ config->entries[config->n_entries++] = (BootEntry) {
+ .type = BOOT_ENTRY_LOADER,
+ .id = TAKE_PTR(c),
+ .title = TAKE_PTR(t),
+ };
+ }
+
+ return 0;
+}
+
/********************************************************************************/
static int verify_esp_blkid(
@@ -1294,36 +1418,10 @@ found:
return 0;
}
-int find_default_boot_entry(
- const char *esp_path,
- const char *xbootldr_path,
- BootConfig *config,
- const BootEntry **e) {
+static const char* const boot_entry_type_table[_BOOT_ENTRY_MAX] = {
+ [BOOT_ENTRY_CONF] = "conf",
+ [BOOT_ENTRY_UNIFIED] = "unified",
+ [BOOT_ENTRY_LOADER] = "loader",
+};
- _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
- int r;
-
- assert(config);
- assert(e);
-
- r = find_esp_and_warn(esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
- if (r < 0)
- return r;
-
- r = find_xbootldr_and_warn(xbootldr_path, false, &xbootldr_where, NULL);
- if (r < 0 && r != -ENOKEY)
- return r;
-
- r = boot_entries_load_config(esp_where, xbootldr_where, config);
- if (r < 0)
- return log_error_errno(r, "Failed to load boot loader entries: %m");
-
- if (config->default_entry < 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
- "No boot loader entry suitable as default, refusing to guess.");
-
- *e = &config->entries[config->default_entry];
- log_debug("Found default boot loader entry in file \"%s\"", (*e)->path);
-
- return 0;
-}
+DEFINE_STRING_TABLE_LOOKUP(boot_entry_type, BootEntryType);
diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h
index f184902558..45e1fc37c6 100644
--- a/src/shared/bootspec.h
+++ b/src/shared/bootspec.h
@@ -8,7 +8,18 @@
#include "sd-id128.h"
+#include "string-util.h"
+
+typedef enum BootEntryType {
+ BOOT_ENTRY_CONF, /* Type #1 entries: *.conf files */
+ BOOT_ENTRY_UNIFIED, /* Type #2 entries: *.efi files */
+ BOOT_ENTRY_LOADER, /* Additional entries augmented from LoaderEntries EFI var */
+ _BOOT_ENTRY_MAX,
+ _BOOT_ENTRY_INVALID = -1,
+} BootEntryType;
+
typedef struct BootEntry {
+ BootEntryType type;
char *id; /* This is the file basename without extension */
char *path; /* This is the full path to the drop-in file */
char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */
@@ -40,8 +51,27 @@ typedef struct BootConfig {
ssize_t default_entry;
} BootConfig;
+static inline bool boot_config_has_entry(BootConfig *config, const char *id) {
+ size_t j;
+
+ for (j = 0; j < config->n_entries; j++)
+ if (streq(config->entries[j].id, id))
+ return true;
+
+ return false;
+}
+
+static inline BootEntry* boot_config_default_entry(BootConfig *config) {
+ if (config->default_entry < 0)
+ return NULL;
+
+ return config->entries + config->default_entry;
+}
+
void boot_config_free(BootConfig *config);
int boot_entries_load_config(const char *esp_path, const char *xbootldr_path, BootConfig *config);
+int boot_entries_load_config_auto(const char *override_esp_path, const char *override_xbootldr_path, BootConfig *config);
+int boot_entries_augment_from_loader(BootConfig *config, bool only_auto);
static inline const char* boot_entry_title(const BootEntry *entry) {
return entry->show_title ?: entry->title ?: entry->id;
@@ -50,4 +80,5 @@ static inline const char* boot_entry_title(const BootEntry *entry) {
int find_esp_and_warn(const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid);
int find_xbootldr_and_warn(const char *path, bool unprivileged_mode, char **ret_path,sd_id128_t *ret_uuid);
-int find_default_boot_entry(const char *esp_path, const char *xbootldr_path, BootConfig *config, const BootEntry **e);
+const char* boot_entry_type_to_string(BootEntryType t) _const_;
+BootEntryType boot_entry_type_from_string(const char *s) _pure_;
diff --git a/src/shared/efivars.c b/src/shared/efivars.c
index 26f905bfaa..caae7b9138 100644
--- a/src/shared/efivars.c
+++ b/src/shared/efivars.c
@@ -796,7 +796,7 @@ bool efi_loader_entry_name_valid(const char *s) {
if (strlen(s) > FILENAME_MAX) /* Make sure entry names fit in filenames */
return false;
- return in_charset(s, ALPHANUMERICAL "-");
+ return in_charset(s, ALPHANUMERICAL "-_.");
}
int efi_loader_get_entries(char ***ret) {
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 444d426eb0..38897ecefb 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -170,6 +170,8 @@ static unsigned arg_lines = 10;
static OutputMode arg_output = OUTPUT_SHORT;
static bool arg_plain = false;
static bool arg_firmware_setup = false;
+static usec_t arg_boot_loader_menu = USEC_INFINITY;
+static const char *arg_boot_loader_entry = NULL;
static bool arg_now = false;
static bool arg_jobs_before = false;
static bool arg_jobs_after = false;
@@ -3473,7 +3475,11 @@ static int logind_check_inhibitors(enum action a) {
#endif
}
-static int logind_prepare_firmware_setup(void) {
+static int prepare_firmware_setup(void) {
+
+ if (!arg_firmware_setup)
+ return 0;
+
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
@@ -3497,27 +3503,75 @@ static int logind_prepare_firmware_setup(void) {
return 0;
#else
- log_error("Cannot remotely indicate to EFI to boot into setup mode.");
+ log_error("Booting into firmware setup not supported.");
return -ENOSYS;
#endif
}
-static int prepare_firmware_setup(void) {
- int r;
+static int prepare_boot_loader_menu(void) {
- if (!arg_firmware_setup)
+ if (arg_boot_loader_menu == USEC_INFINITY)
return 0;
- if (arg_transport == BUS_TRANSPORT_LOCAL) {
+#if ENABLE_LOGIND
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus;
+ int r;
- r = efi_set_reboot_to_firmware(true);
- if (r < 0)
- log_debug_errno(r, "Cannot indicate to EFI to boot into setup mode, will retry via logind: %m");
- else
- return r;
- }
+ r = acquire_bus(BUS_FULL, &bus);
+ if (r < 0)
+ return r;
- return logind_prepare_firmware_setup();
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ "SetRebootToBootLoaderMenu",
+ &error,
+ NULL,
+ "t", arg_boot_loader_menu);
+ if (r < 0)
+ return log_error_errno(r, "Cannot indicate to boot loader to enter boot loader entry menu: %s", bus_error_message(&error, r));
+
+ return 0;
+#else
+ log_error("Booting into boot loader menu not supported.");
+ return -ENOSYS;
+#endif
+}
+
+static int prepare_boot_loader_entry(void) {
+
+ if (!arg_boot_loader_entry)
+ return 0;
+
+#if ENABLE_LOGIND
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus;
+ int r;
+
+ r = acquire_bus(BUS_FULL, &bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ "SetRebootToBootLoaderEntry",
+ &error,
+ NULL,
+ "s", arg_boot_loader_entry);
+ if (r < 0)
+ return log_error_errno(r, "Cannot set boot into loader entry '%s': %s", arg_boot_loader_entry, bus_error_message(&error, r));
+
+ return 0;
+#else
+ log_error("Booting into boot loader entry not supported.");
+ return -ENOSYS;
+#endif
}
static int load_kexec_kernel(void) {
@@ -3535,15 +3589,22 @@ static int load_kexec_kernel(void) {
if (access(KEXEC, X_OK) < 0)
return log_error_errno(errno, KEXEC" is not available: %m");
- r = find_default_boot_entry(NULL, NULL, &config, &e);
- if (r == -ENOKEY) /* find_default_boot_entry() doesn't warn about this case */
- return log_error_errno(r, "Cannot find the ESP partition mount point.");
+ r = boot_entries_load_config_auto(NULL, NULL, &config);
if (r < 0)
- /* But it logs about all these cases, hence don't log here again */
return r;
+ e = boot_config_default_entry(&config);
+ if (!e)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "No boot loader entry suitable as default, refusing to guess.");
+
+ log_debug("Found default boot loader entry in file \"%s\"", e->path);
+
+ if (!e->kernel)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Boot entry does not refer to Linux kernel, which is not supported currently.");
if (strv_length(e->initrd) > 1)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Boot entry specifies multiple initrds, which is not supported currently.");
kernel = path_join(e->root, e->kernel);
@@ -3551,7 +3612,7 @@ static int load_kexec_kernel(void) {
return log_oom();
if (!strv_isempty(e->initrd)) {
- initrd = path_join(e->root, *e->initrd);
+ initrd = path_join(e->root, e->initrd[0]);
if (!initrd)
return log_oom();
}
@@ -3569,7 +3630,7 @@ static int load_kexec_kernel(void) {
if (arg_dry_run)
return 0;
- r = safe_fork("(kexec)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
+ r = safe_fork("(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
if (r < 0)
return r;
if (r == 0) {
@@ -3578,19 +3639,14 @@ static int load_kexec_kernel(void) {
"--load", kernel,
"--append", options,
initrd ? "--initrd" : NULL, initrd,
- NULL };
+ NULL
+ };
/* Child */
execv(args[0], (char * const *) args);
_exit(EXIT_FAILURE);
}
- r = wait_for_terminate_and_check("kexec", pid, WAIT_LOG);
- if (r < 0)
- return r;
- if (r > 0)
- /* Command failed */
- return -EPROTO;
return 0;
}
@@ -3642,6 +3698,14 @@ static int start_special(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
+ r = prepare_boot_loader_menu();
+ if (r < 0)
+ return r;
+
+ r = prepare_boot_loader_entry();
+ if (r < 0)
+ return r;
+
if (a == ACTION_REBOOT && argc > 1) {
r = update_reboot_parameter_and_warn(argv[1]);
if (r < 0)
@@ -7520,6 +7584,10 @@ static int systemctl_help(void) {
" short-monotonic, short-unix,\n"
" verbose, export, json, json-pretty, json-sse, cat)\n"
" --firmware-setup Tell the firmware to show the setup menu on next boot\n"
+ " --boot-loader-menu=TIME\n"
+ " Boot into boot loader menu on next boot\n"
+ " --boot-loader-entry=NAME\n"
+ " Boot into a specific boot loader entry on next boot\n"
" --plain Print unit dependencies as a list instead of a tree\n\n"
"Unit Commands:\n"
" list-units [PATTERN...] List units currently in memory\n"
@@ -7774,6 +7842,39 @@ static void help_states(void) {
DUMP_STRING_TABLE(timer_state, TimerState, _TIMER_STATE_MAX);
}
+static int help_boot_loader_entry(void) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char **l = NULL;
+ sd_bus *bus;
+ char **i;
+ int r;
+
+ r = acquire_bus(BUS_FULL, &bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_get_property_strv(
+ bus,
+ "org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ "BootLoaderEntries",
+ &error,
+ &l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate boot loader entries: %s", bus_error_message(&error, r));
+
+ if (strv_isempty(l)) {
+ log_error("No boot loader entries discovered.");
+ return -ENODATA;
+ }
+
+ STRV_FOREACH(i, l)
+ puts(*i);
+
+ return 0;
+}
+
static int systemctl_parse_argv(int argc, char *argv[]) {
enum {
ARG_FAIL = 0x100,
@@ -7804,6 +7905,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_JOB_MODE,
ARG_PRESET_MODE,
ARG_FIRMWARE_SETUP,
+ ARG_BOOT_LOADER_MENU,
+ ARG_BOOT_LOADER_ENTRY,
ARG_NOW,
ARG_MESSAGE,
ARG_WAIT,
@@ -7853,6 +7956,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "recursive", no_argument, NULL, 'r' },
{ "preset-mode", required_argument, NULL, ARG_PRESET_MODE },
{ "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP },
+ { "boot-loader-menu", required_argument, NULL, ARG_BOOT_LOADER_MENU },
+ { "boot-loader-entry", required_argument, NULL, ARG_BOOT_LOADER_ENTRY },
{ "now", no_argument, NULL, ARG_NOW },
{ "message", required_argument, NULL, ARG_MESSAGE },
{}
@@ -8126,6 +8231,27 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_firmware_setup = true;
break;
+ case ARG_BOOT_LOADER_MENU:
+
+ r = parse_sec(optarg, &arg_boot_loader_menu);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --boot-loader-menu= argument '%s': %m", optarg);
+
+ break;
+
+ case ARG_BOOT_LOADER_ENTRY:
+
+ if (streq(optarg, "help")) { /* Yes, this means, "help" is not a valid boot loader entry name we can deal with */
+ r = help_boot_loader_entry();
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+
+ arg_boot_loader_entry = empty_to_null(optarg);
+ break;
+
case ARG_STATE: {
if (isempty(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
diff --git a/units/systemd-logind.service.in b/units/systemd-logind.service.in
index fb6fda4907..a864f66c68 100644
--- a/units/systemd-logind.service.in
+++ b/units/systemd-logind.service.in
@@ -21,7 +21,7 @@ After=dbus.socket
[Service]
BusName=org.freedesktop.login1
-CapabilityBoundingSet=CAP_SYS_ADMIN CAP_MAC_ADMIN CAP_AUDIT_CONTROL CAP_CHOWN CAP_KILL CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_FOWNER CAP_SYS_TTY_CONFIG
+CapabilityBoundingSet=CAP_SYS_ADMIN CAP_MAC_ADMIN CAP_AUDIT_CONTROL CAP_CHOWN CAP_KILL CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_FOWNER CAP_SYS_TTY_CONFIG CAP_LINUX_IMMUTABLE
ExecStart=@rootlibexecdir@/systemd-logind
FileDescriptorStoreMax=512
IPAddressDeny=any