Merge pull request #15935 from poettering/cache-more-efi-vars

logind + efi-loader: cache more efi vars
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2020-05-30 15:44:26 +02:00 committed by GitHub
commit 956508cb5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 211 additions and 64 deletions

View File

@ -102,7 +102,7 @@
</varlistentry>
<varlistentry>
<term><option>systemd-efi-options</option> <optional><replaceable>VALUE</replaceable></optional></term>
<term><option>systemd-efi-options</option> <optional><replaceable>STRING</replaceable></optional></term>
<listitem><para>When called without the optional argument, prints the current value of the
<literal>SystemdOptions</literal> EFI variable. When called with an argument, sets the
@ -111,6 +111,17 @@
for the meaning of that variable.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>reboot-to-firmware</option> <optional><replaceable>BOOL</replaceable></optional></term>
<listitem><para>Query or set the "Reboot-Into-Firmware-Setup" flag of the EFI firmware. Takes a
boolean argument which controls whether to show the firmware setup on next system reboot. If the
argument is omitted shows the current status of the flag, or whether the flag is supported. This
controls the same flag as <command>systemctl reboot --firmware-setup</command>, but is more
low-level and allows setting the flag independently from actually requesting a
reboot.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>list</option></term>

View File

@ -69,19 +69,21 @@ int efi_get_variable(
return 0;
}
if (DEBUG_LOGGING)
if (DEBUG_LOGGING) {
log_debug("Reading EFI variable %s.", p);
begin = now(CLOCK_MONOTONIC);
}
fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return log_debug_errno(errno, "open(\"%s\") failed: %m", p);
if (fstat(fd, &st) < 0)
return -errno;
return log_debug_errno(errno, "fstat(\"%s\") failed: %m", p);
if (st.st_size < 4)
return -ENODATA;
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "EFI variable %s is shorter than 4 bytes, refusing.", p);
if (st.st_size > 4*1024*1024 + 4)
return -E2BIG;
return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "EFI variable %s is ridiculously large, refusing.", p);
if (ret_value || ret_attribute) {
/* The kernel ratelimits reads from the efivarfs because EFI is inefficient, and we'll
@ -96,31 +98,34 @@ int efi_get_variable(
n = read(fd, &a, sizeof(a));
if (n >= 0)
break;
log_debug_errno(errno, "read from \"%s\" failed: %m", p);
log_debug_errno(errno, "Reading from \"%s\" failed: %m", p);
if (errno != EINTR)
return -errno;
if (try >= EFI_N_RETRIES)
return -EBUSY;
usleep(EFI_RETRY_DELAY);
(void) usleep(EFI_RETRY_DELAY);
}
if (n != sizeof(a))
return -EIO;
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Read %zi bytes from EFI variable %s, expected %zu.", n, p, sizeof(a));
}
if (ret_value) {
buf = malloc(st.st_size - 4 + 2);
buf = malloc(st.st_size - 4 + 3);
if (!buf)
return -ENOMEM;
n = read(fd, buf, (size_t) st.st_size - 4);
if (n < 0)
return -errno;
return log_debug_errno(errno, "Failed to read value of EFI variable %s: %m", p);
assert(n <= st.st_size - 4);
/* Always NUL terminate (2 bytes, to protect UTF-16) */
/* Always NUL terminate (3 bytes, to properly protect UTF-16, even if truncated in the middle of a character) */
((char*) buf)[n] = 0;
((char*) buf)[n + 1] = 0;
((char*) buf)[n + 2] = 0;
} else
/* Assume that the reported size is accurate */
n = st.st_size - 4;
@ -229,6 +234,14 @@ int efi_set_variable(
if (r < 0)
goto finish;
/* For some reason efivarfs doesn't update mtime automatically. Let's do it manually then. This is
* useful for processes that cache EFI variables to detect when changes occurred. */
if (futimens(fd, (struct timespec[2]) {
{ .tv_nsec = UTIME_NOW },
{ .tv_nsec = UTIME_NOW }
}) < 0)
log_debug_errno(errno, "Failed to update mtime/atime on %s, ignoring: %m", p);
r = 0;
finish:

View File

@ -1041,7 +1041,10 @@ static int help(int argc, char *argv[], void *userdata) {
" remove Remove systemd-boot from the ESP and EFI variables\n"
" is-installed Test whether systemd-boot is installed in the ESP\n"
" random-seed Initialize random seed in ESP and EFI variables\n"
" systemd-efi-options Query or set system options string in EFI variable\n"
" systemd-efi-options [STRING]\n"
" Query or set system options string in EFI variable\n"
" reboot-to-firmware [BOOL]\n"
" Query or set reboot-to-firmware EFI flag\n"
"\nBoot Loader Entries Commands:\n"
" list List boot loader entries\n"
" set-default ID Set default boot loader entry\n"
@ -1245,6 +1248,18 @@ static int verb_status(int argc, char *argv[], void *userdata) {
printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal());
printf(" Secure Boot: %sd\n", enable_disable(is_efi_secure_boot()));
printf(" Setup Mode: %s\n", is_efi_secure_boot_setup_mode() ? "setup" : "user");
r = efi_get_reboot_to_firmware();
if (r > 0)
printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal());
else if (r == 0)
printf(" Boot into FW: supported\n");
else if (r == -EOPNOTSUPP)
printf(" Boot into FW: not supported\n");
else {
errno = -r;
printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal());
}
printf("\n");
printf("Current Boot Loader:\n");
@ -1311,6 +1326,7 @@ 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_strv_free_ char **efi_entries = NULL;
int r;
/* If we lack privileges we invoke find_esp_and_warn() in "unprivileged mode" here, which does two things: turn
@ -1333,7 +1349,13 @@ static int verb_list(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
(void) boot_entries_augment_from_loader(&config, false);
r = efi_loader_get_entries(&efi_entries);
if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r))
log_debug_errno(r, "Boot loader reported no entries.");
else if (r < 0)
log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m");
else
(void) boot_entries_augment_from_loader(&config, efi_entries, false);
if (config.n_entries == 0)
log_info("No boot loader entries found.");
@ -1766,6 +1788,39 @@ static int verb_systemd_efi_options(int argc, char *argv[], void *userdata) {
return 0;
}
static int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) {
int r;
if (argc < 2) {
r = efi_get_reboot_to_firmware();
if (r > 0) {
puts("active");
return EXIT_SUCCESS; /* success */
}
if (r == 0) {
puts("supported");
return 1; /* recognizable error #1 */
}
if (r == -EOPNOTSUPP) {
puts("not supported");
return 2; /* recognizable error #2 */
}
log_error_errno(r, "Failed to query reboot-to-firmware state: %m");
return 3; /* other kind of error */
} else {
r = parse_boolean(argv[1]);
if (r < 0)
return log_error_errno(r, "Failed to parse argument: %s", argv[1]);
r = efi_set_reboot_to_firmware(r);
if (r < 0)
return log_error_errno(r, "Failed to set reboot-to-firmware option: %m");
return 0;
}
}
static int bootctl_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
@ -1779,6 +1834,7 @@ static int bootctl_main(int argc, char *argv[]) {
{ "set-oneshot", 2, 2, 0, verb_set_default },
{ "random-seed", VERB_ANY, 1, 0, verb_random_seed },
{ "systemd-efi-options", VERB_ANY, 2, 0, verb_systemd_efi_options },
{ "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware },
{}
};
@ -1802,4 +1858,4 @@ static int run(int argc, char *argv[]) {
return bootctl_main(argc, argv);
}
DEFINE_MAIN_FUNCTION(run);
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

View File

@ -16,6 +16,7 @@
#include "cgroup-util.h"
#include "conf-parser.h"
#include "device-util.h"
#include "efi-loader.h"
#include "errno-util.h"
#include "fd-util.h"
#include "limits-util.h"
@ -817,3 +818,27 @@ void manager_reconnect_utmp(Manager *m) {
manager_connect_utmp(m);
#endif
}
int manager_read_efi_boot_loader_entries(Manager *m) {
#if ENABLE_EFI
int r;
assert(m);
if (m->efi_boot_loader_entries_set)
return 0;
r = efi_loader_get_entries(&m->efi_boot_loader_entries);
if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) {
log_debug_errno(r, "Boot loader reported no entries.");
m->efi_boot_loader_entries_set = true;
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to determine entries reported by boot loader: %m");
m->efi_boot_loader_entries_set = true;
return 1;
#else
return 0;
#endif
}

View File

@ -2969,17 +2969,20 @@ static int property_get_reboot_to_boot_loader_entry(
return sd_bus_message_append(reply, "s", v);
}
static int boot_loader_entry_exists(const char *id) {
static int boot_loader_entry_exists(Manager *m, const char *id) {
_cleanup_(boot_config_free) BootConfig config = {};
int r;
assert(m);
assert(id);
r = boot_entries_load_config_auto(NULL, NULL, &config);
if (r < 0 && r != -ENOKEY) /* don't complain if no GPT is found, hence skip ENOKEY */
return r;
(void) boot_entries_augment_from_loader(&config, true);
r = manager_read_efi_boot_loader_entries(m);
if (r >= 0)
(void) boot_entries_augment_from_loader(&config, m->efi_boot_loader_entries, true);
return boot_config_has_entry(&config, id);
}
@ -3004,7 +3007,7 @@ static int method_set_reboot_to_boot_loader_entry(
if (isempty(v))
v = NULL;
else if (efi_loader_entry_name_valid(v)) {
r = boot_loader_entry_exists(v);
r = boot_loader_entry_exists(m, v);
if (r < 0)
return r;
if (r == 0)
@ -3123,18 +3126,21 @@ static int property_get_boot_loader_entries(
sd_bus_error *error) {
_cleanup_(boot_config_free) BootConfig config = {};
Manager *m = userdata;
size_t i;
int r;
assert(bus);
assert(reply);
assert(userdata);
assert(m);
r = boot_entries_load_config_auto(NULL, NULL, &config);
if (r < 0 && r != -ENOKEY) /* don't complain if there's no GPT found */
return r;
(void) boot_entries_augment_from_loader(&config, true);
r = manager_read_efi_boot_loader_entries(m);
if (r >= 0)
(void) boot_entries_augment_from_loader(&config, m->efi_boot_loader_entries, true);
r = sd_bus_message_open_container(reply, 'a', "s");
if (r < 0)

View File

@ -166,6 +166,8 @@ static Manager* manager_unref(Manager *m) {
free(m->wall_message);
free(m->action_job);
strv_free(m->efi_boot_loader_entries);
return mfree(m);
}

View File

@ -123,6 +123,9 @@ struct Manager {
uint64_t runtime_dir_inodes;
uint64_t sessions_max;
uint64_t inhibitors_max;
char **efi_boot_loader_entries;
bool efi_boot_loader_entries_set;
};
void manager_reset_config(Manager *m);
@ -168,3 +171,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tmpfs_size);
int manager_setup_wall_message_timer(Manager *m);
bool logind_wall_tty_filter(const char *tty, void *userdata);
int manager_read_efi_boot_loader_entries(Manager *m);

View File

@ -735,9 +735,12 @@ int boot_entries_load_config_auto(
return boot_entries_load_config(esp_where, xbootldr_where, config);
}
#if ENABLE_EFI
int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
static const char * const title_table[] = {
int boot_entries_augment_from_loader(
BootConfig *config,
char **found_by_loader,
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",
@ -746,22 +749,14 @@ int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
"auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
};
_cleanup_strv_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) {
@ -803,7 +798,6 @@ int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
return 0;
}
#endif
/********************************************************************************/

View File

@ -76,13 +76,7 @@ static inline BootEntry* boot_config_default_entry(BootConfig *config) {
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);
#if ENABLE_EFI
int boot_entries_augment_from_loader(BootConfig *config, bool only_auto);
#else
static inline int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
return -EOPNOTSUPP;
}
#endif
int boot_entries_augment_from_loader(BootConfig *config, char **list, bool only_auto);
static inline const char* boot_entry_title(const BootEntry *entry) {
return entry->show_title ?: entry->title ?: entry->id;

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-util.h"
@ -11,6 +12,7 @@
#include "io-util.h"
#include "parse-util.h"
#include "sort-util.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "utf8.h"
@ -28,10 +30,11 @@
#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff
#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001
#define boot_option__contents { \
uint32_t attr; \
uint16_t path_len; \
uint16_t title[]; \
#define boot_option__contents \
{ \
uint32_t attr; \
uint16_t path_len; \
uint16_t title[]; \
}
struct boot_option boot_option__contents;
@ -49,33 +52,39 @@ struct drive_path {
uint8_t signature_type;
} _packed_;
#define device_path__contents { \
uint8_t type; \
uint8_t sub_type; \
uint16_t length; \
union { \
uint16_t path[0]; \
struct drive_path drive; \
}; \
#define device_path__contents \
{ \
uint8_t type; \
uint8_t sub_type; \
uint16_t length; \
union { \
uint16_t path[0]; \
struct drive_path drive; \
}; \
}
struct device_path device_path__contents;
struct device_path__packed device_path__contents _packed_;
assert_cc(sizeof(struct device_path) == sizeof(struct device_path__packed));
int efi_reboot_to_firmware_supported(void) {
_cleanup_free_ void *v = NULL;
static int cache = -1;
uint64_t b;
size_t s;
int r;
if (!is_efi_boot())
if (cache > 0)
return 0;
if (cache == 0)
return -EOPNOTSUPP;
if (!is_efi_boot())
goto not_supported;
r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndicationsSupported", NULL, &v, &s);
if (r == -ENOENT) /* variable doesn't exist? it's not supported then */
return -EOPNOTSUPP;
if (r == -ENOENT)
goto not_supported; /* variable doesn't exist? it's not supported then */
if (r < 0)
return r;
if (s != sizeof(uint64_t))
@ -83,36 +92,68 @@ int efi_reboot_to_firmware_supported(void) {
b = *(uint64_t*) v;
if (!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI))
return -EOPNOTSUPP; /* bit unset? it's not supported then */
goto not_supported; /* bit unset? it's not supported then */
cache = 1;
return 0;
not_supported:
cache = 0;
return -EOPNOTSUPP;
}
static int get_os_indications(uint64_t *os_indication) {
static int get_os_indications(uint64_t *ret) {
static struct stat cache_stat = {};
_cleanup_free_ void *v = NULL;
_cleanup_free_ char *fn = NULL;
static uint64_t cache;
struct stat new_stat;
size_t s;
int r;
assert(ret);
/* Let's verify general support first */
r = efi_reboot_to_firmware_supported();
if (r < 0)
return r;
fn = efi_variable_path(EFI_VENDOR_GLOBAL, "OsIndications");
if (!fn)
return -ENOMEM;
/* stat() the EFI variable, to see if the mtime changed. If it did we need to cache again. */
if (stat(fn, &new_stat) < 0) {
if (errno != ENOENT)
return -errno;
/* Doesn't exist? Then we can exit early (also see below) */
*ret = 0;
return 0;
} else if (stat_inode_unmodified(&new_stat, &cache_stat)) {
/* inode didn't change, we can return the cached value */
*ret = cache;
return 0;
}
r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s);
if (r == -ENOENT) {
/* Some firmware implementations that do support OsIndications and report that with
* OsIndicationsSupported will remove the OsIndications variable when it is unset. Let's pretend it's 0
* then, to hide this implementation detail. Note that this call will return -ENOENT then only if the
* support for OsIndications is missing entirely, as determined by efi_reboot_to_firmware_supported()
* above. */
*os_indication = 0;
* OsIndicationsSupported will remove the OsIndications variable when it is unset. Let's
* pretend it's 0 then, to hide this implementation detail. Note that this call will return
* -ENOENT then only if the support for OsIndications is missing entirely, as determined by
* efi_reboot_to_firmware_supported() above. */
*ret = 0;
return 0;
} else if (r < 0)
}
if (r < 0)
return r;
else if (s != sizeof(uint64_t))
if (s != sizeof(uint64_t))
return -EINVAL;
*os_indication = *(uint64_t *)v;
cache_stat = new_stat;
*ret = cache = *(uint64_t *)v;
return 0;
}