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 to the boot loader menu + Authentication is required to indicate to the boot loader to boot to the boot loader menu. + + auth_admin_keep + auth_admin_keep + yes + + 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