Move bootctl to its own source subdirectory
It's been split into a bunch of files and deserves its own subdirectory similarly to systemctl.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_install(int argc, char *argv[], void *userdata);
|
||||
int verb_remove(int argc, char *argv[], void *userdata);
|
||||
int verb_is_installed(int argc, char *argv[], void *userdata);
|
||||
@@ -0,0 +1,239 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bootctl.h"
|
||||
#include "bootctl-random-seed.h"
|
||||
#include "bootctl-util.h"
|
||||
#include "efi-api.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "find-esp.h"
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "io-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "path-util.h"
|
||||
#include "random-util.h"
|
||||
#include "sha256.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "umask-util.h"
|
||||
|
||||
static int random_seed_verify_permissions(int fd, mode_t expected_type) {
|
||||
_cleanup_free_ char *full_path = NULL;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
r = fd_get_path(fd, &full_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to determine full path of random seed fd: %m");
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_error_errno(errno, "Unable to stat %s: %m", full_path);
|
||||
|
||||
if (((st.st_mode ^ expected_type) & S_IFMT) != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADF),
|
||||
"Unexpected inode type when validating random seed access mode on %s: %m", full_path);
|
||||
|
||||
if ((st.st_mode & 0007) == 0) /* All world bits are off? Then all is good */
|
||||
return 0;
|
||||
|
||||
if (S_ISREG(expected_type))
|
||||
log_warning("%s Random seed file '%s' is world accessible, which is a security hole! %s",
|
||||
special_glyph(SPECIAL_GLYPH_WARNING_SIGN), full_path, special_glyph(SPECIAL_GLYPH_WARNING_SIGN));
|
||||
else {
|
||||
assert(S_ISDIR(expected_type));
|
||||
log_warning("%s Mount point '%s' which backs the random seed file is world accessible, which is a security hole! %s",
|
||||
special_glyph(SPECIAL_GLYPH_WARNING_SIGN), full_path, special_glyph(SPECIAL_GLYPH_WARNING_SIGN));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int set_system_token(void) {
|
||||
uint8_t buffer[RANDOM_EFI_SEED_SIZE];
|
||||
size_t token_size;
|
||||
int r;
|
||||
|
||||
if (!arg_touch_variables)
|
||||
return 0;
|
||||
|
||||
if (arg_root) {
|
||||
log_warning("Acting on %s, skipping EFI variable setup.",
|
||||
arg_image ? "image" : "root directory");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!is_efi_boot()) {
|
||||
log_notice("Not booted with EFI, skipping EFI variable setup.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = getenv_bool("SYSTEMD_WRITE_SYSTEM_TOKEN");
|
||||
if (r < 0) {
|
||||
if (r != -ENXIO)
|
||||
log_warning_errno(r, "Failed to parse $SYSTEMD_WRITE_SYSTEM_TOKEN, ignoring.");
|
||||
} else if (r == 0) {
|
||||
log_notice("Not writing system token, because $SYSTEMD_WRITE_SYSTEM_TOKEN is set to false.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderSystemToken"), NULL, NULL, &token_size);
|
||||
if (r == -ENODATA)
|
||||
log_debug_errno(r, "LoaderSystemToken EFI variable is invalid (too short?), replacing.");
|
||||
else if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to test system token validity: %m");
|
||||
} else {
|
||||
if (token_size >= sizeof(buffer)) {
|
||||
/* Let's avoid writes if we can, and initialize this only once. */
|
||||
log_debug("System token already written, not updating.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sizeof(buffer));
|
||||
}
|
||||
|
||||
r = crypto_random_bytes(buffer, sizeof(buffer));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire random seed: %m");
|
||||
|
||||
/* Let's write this variable with an umask in effect, so that unprivileged users can't see the token
|
||||
* and possibly get identification information or too much insight into the kernel's entropy pool
|
||||
* state. */
|
||||
WITH_UMASK(0077) {
|
||||
r = efi_set_variable(EFI_LOADER_VARIABLE_STR("LoaderSystemToken"), buffer, sizeof(buffer));
|
||||
if (r < 0) {
|
||||
if (!arg_graceful)
|
||||
return log_error_errno(r, "Failed to write 'LoaderSystemToken' EFI variable: %m");
|
||||
|
||||
if (r == -EINVAL)
|
||||
log_notice_errno(r, "Unable to write 'LoaderSystemToken' EFI variable (firmware problem?), ignoring: %m");
|
||||
else
|
||||
log_notice_errno(r, "Unable to write 'LoaderSystemToken' EFI variable, ignoring: %m");
|
||||
} else
|
||||
log_info("Successfully initialized system token in EFI variable with %zu bytes.", sizeof(buffer));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int install_random_seed(const char *esp) {
|
||||
_cleanup_close_ int esp_fd = -EBADF, loader_dir_fd = -EBADF, fd = -EBADF;
|
||||
_cleanup_free_ char *tmp = NULL;
|
||||
uint8_t buffer[RANDOM_EFI_SEED_SIZE];
|
||||
struct sha256_ctx hash_state;
|
||||
bool refreshed, warned = false;
|
||||
int r;
|
||||
|
||||
assert(esp);
|
||||
|
||||
assert_cc(RANDOM_EFI_SEED_SIZE == SHA256_DIGEST_SIZE);
|
||||
|
||||
esp_fd = open(esp, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
|
||||
if (esp_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open ESP directory '%s': %m", esp);
|
||||
|
||||
(void) random_seed_verify_permissions(esp_fd, S_IFDIR);
|
||||
|
||||
loader_dir_fd = open_mkdir_at(esp_fd, "loader", O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOFOLLOW, 0775);
|
||||
if (loader_dir_fd < 0)
|
||||
return log_error_errno(loader_dir_fd, "Failed to open loader directory '%s/loader': %m", esp);
|
||||
|
||||
r = crypto_random_bytes(buffer, sizeof(buffer));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire random seed: %m");
|
||||
|
||||
sha256_init_ctx(&hash_state);
|
||||
sha256_process_bytes_and_size(buffer, sizeof(buffer), &hash_state);
|
||||
|
||||
fd = openat(loader_dir_fd, "random-seed", O_NOFOLLOW|O_CLOEXEC|O_RDONLY|O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
if (errno != ENOENT)
|
||||
return log_error_errno(errno, "Failed to open old random seed file: %m");
|
||||
|
||||
sha256_process_bytes(&(const ssize_t) { 0 }, sizeof(ssize_t), &hash_state);
|
||||
refreshed = false;
|
||||
} else {
|
||||
ssize_t n;
|
||||
|
||||
warned = random_seed_verify_permissions(fd, S_IFREG) > 0;
|
||||
|
||||
/* Hash the old seed in so that we never regress in entropy. */
|
||||
|
||||
n = read(fd, buffer, sizeof(buffer));
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to read old random seed file: %m");
|
||||
|
||||
sha256_process_bytes_and_size(buffer, n, &hash_state);
|
||||
|
||||
fd = safe_close(fd);
|
||||
refreshed = n > 0;
|
||||
}
|
||||
|
||||
sha256_finish_ctx(&hash_state, buffer);
|
||||
|
||||
if (tempfn_random("random-seed", "bootctl", &tmp) < 0)
|
||||
return log_oom();
|
||||
|
||||
fd = openat(loader_dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|O_WRONLY|O_CLOEXEC, 0600);
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to open random seed file for writing: %m");
|
||||
|
||||
if (!warned) /* only warn once per seed file */
|
||||
(void) random_seed_verify_permissions(fd, S_IFREG);
|
||||
|
||||
r = loop_write(fd, buffer, sizeof(buffer));
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to write random seed file: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (fsync(fd) < 0 || fsync(loader_dir_fd) < 0) {
|
||||
r = log_error_errno(errno, "Failed to sync random seed file: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (renameat(loader_dir_fd, tmp, loader_dir_fd, "random-seed") < 0) {
|
||||
r = log_error_errno(errno, "Failed to move random seed file into place: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
tmp = mfree(tmp);
|
||||
|
||||
if (syncfs(fd) < 0)
|
||||
return log_error_errno(errno, "Failed to sync ESP file system: %m");
|
||||
|
||||
log_info("Random seed file %s/loader/random-seed successfully %s (%zu bytes).", esp, refreshed ? "refreshed" : "written", sizeof(buffer));
|
||||
|
||||
return set_system_token();
|
||||
|
||||
fail:
|
||||
assert(tmp);
|
||||
(void) unlinkat(loader_dir_fd, tmp, 0);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int verb_random_seed(int argc, char *argv[], void *userdata) {
|
||||
int r;
|
||||
|
||||
r = find_esp_and_warn(arg_root, arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL);
|
||||
if (r == -ENOKEY) {
|
||||
/* find_esp_and_warn() doesn't warn about ENOKEY, so let's do that on our own */
|
||||
if (!arg_graceful)
|
||||
return log_error_errno(r, "Unable to find ESP.");
|
||||
|
||||
log_notice("No ESP found, not initializing random seed.");
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = install_random_seed(arg_esp_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int install_random_seed(const char *esp);
|
||||
|
||||
int verb_random_seed(int argc, char *argv[], void *userdata);
|
||||
@@ -0,0 +1,75 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "bootctl-reboot-to-firmware.h"
|
||||
#include "efi-api.h"
|
||||
#include "errno-util.h"
|
||||
#include "parse-util.h"
|
||||
|
||||
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 0; /* success */
|
||||
}
|
||||
if (r == 0) {
|
||||
puts("supported");
|
||||
return 1; /* recognizable error #1 */
|
||||
}
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int vl_method_set_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "state", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, 0, 0 },
|
||||
{}
|
||||
};
|
||||
bool b;
|
||||
int r;
|
||||
|
||||
r = sd_varlink_dispatch(link, parameters, dispatch_table, &b);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = efi_set_reboot_to_firmware(b);
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return sd_varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_varlink_reply(link, NULL);
|
||||
}
|
||||
|
||||
int vl_method_get_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
int r;
|
||||
|
||||
if (sd_json_variant_elements(parameters) > 0)
|
||||
return sd_varlink_error_invalid_parameter(link, parameters);
|
||||
|
||||
r = efi_get_reboot_to_firmware();
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return sd_varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("state", r));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-varlink.h"
|
||||
|
||||
int verb_reboot_to_firmware(int argc, char *argv[], void *userdata);
|
||||
|
||||
int vl_method_set_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
|
||||
int vl_method_get_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
|
||||
@@ -0,0 +1,171 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <uchar.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "bootctl.h"
|
||||
#include "bootctl-set-efivar.h"
|
||||
#include "efivars.h"
|
||||
#include "efi-loader.h"
|
||||
#include "stdio-util.h"
|
||||
#include "utf8.h"
|
||||
#include "virt.h"
|
||||
|
||||
static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_timeout_size) {
|
||||
char utf8[DECIMAL_STR_MAX(usec_t)];
|
||||
char16_t *encoded;
|
||||
usec_t timeout;
|
||||
bool menu_disabled = false;
|
||||
int r;
|
||||
|
||||
assert(arg1);
|
||||
assert(ret_timeout);
|
||||
assert(ret_timeout_size);
|
||||
|
||||
assert_cc(STRLEN("menu-disabled") < ELEMENTSOF(utf8));
|
||||
|
||||
/* Note: Since there is no way to query if the bootloader supports the string tokens, we explicitly
|
||||
* set their numerical value(s) instead. This means that some of the sd-boot internal ABI has leaked
|
||||
* although the ship has sailed and the side-effects are self-contained.
|
||||
*/
|
||||
if (streq(arg1, "menu-force"))
|
||||
timeout = USEC_INFINITY;
|
||||
else if (streq(arg1, "menu-hidden"))
|
||||
timeout = 0;
|
||||
else if (streq(arg1, "menu-disabled")) {
|
||||
uint64_t loader_features = 0;
|
||||
|
||||
(void) efi_loader_get_features(&loader_features);
|
||||
if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) {
|
||||
if (!arg_graceful)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled'.");
|
||||
|
||||
log_warning("Loader does not support 'menu-disabled', setting anyway.");
|
||||
}
|
||||
menu_disabled = true;
|
||||
} else {
|
||||
r = parse_time(arg1, &timeout, USEC_PER_SEC);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse timeout '%s': %m", arg1);
|
||||
if (timeout != USEC_INFINITY && timeout > UINT32_MAX * USEC_PER_SEC)
|
||||
log_warning("Timeout is too long and will be treated as 'menu-force' instead.");
|
||||
}
|
||||
|
||||
if (menu_disabled)
|
||||
xsprintf(utf8, "menu-disabled");
|
||||
else
|
||||
xsprintf(utf8, USEC_FMT, MIN(timeout / USEC_PER_SEC, UINT32_MAX));
|
||||
|
||||
encoded = utf8_to_utf16(utf8, SIZE_MAX);
|
||||
if (!encoded)
|
||||
return log_oom();
|
||||
|
||||
*ret_timeout = encoded;
|
||||
*ret_timeout_size = char16_strlen(encoded) * 2 + 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target, size_t *ret_target_size) {
|
||||
char16_t *encoded = NULL;
|
||||
int r;
|
||||
|
||||
assert(arg1);
|
||||
assert(ret_target);
|
||||
assert(ret_target_size);
|
||||
|
||||
if (streq(arg1, "@current")) {
|
||||
r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), NULL, (void *) ret_target, ret_target_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get EFI variable 'LoaderEntrySelected': %m");
|
||||
|
||||
} else if (streq(arg1, "@oneshot")) {
|
||||
r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), NULL, (void *) ret_target, ret_target_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryOneShot': %m");
|
||||
|
||||
} else if (streq(arg1, "@default")) {
|
||||
r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), NULL, (void *) ret_target, ret_target_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryDefault': %m");
|
||||
|
||||
} else if (arg1[0] == '@' && !streq(arg1, "@saved"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported special entry identifier: %s", arg1);
|
||||
else {
|
||||
encoded = utf8_to_utf16(arg1, SIZE_MAX);
|
||||
if (!encoded)
|
||||
return log_oom();
|
||||
|
||||
*ret_target = encoded;
|
||||
*ret_target_size = char16_strlen(encoded) * 2 + 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int verb_set_efivar(int argc, char *argv[], void *userdata) {
|
||||
int r;
|
||||
|
||||
if (arg_root)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Acting on %s, skipping EFI variable setup.",
|
||||
arg_image ? "image" : "root directory");
|
||||
|
||||
if (!is_efi_boot())
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Not booted with UEFI.");
|
||||
|
||||
if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderInfo")), F_OK) < 0) {
|
||||
if (errno == ENOENT) {
|
||||
log_error_errno(errno, "Not booted with a supported boot loader.");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return log_error_errno(errno, "Failed to detect whether boot loader supports '%s' operation: %m", argv[0]);
|
||||
}
|
||||
|
||||
if (detect_container() > 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"'%s' operation not supported in a container.",
|
||||
argv[0]);
|
||||
|
||||
if (!arg_touch_variables)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"'%s' operation cannot be combined with --no-variables.",
|
||||
argv[0]);
|
||||
|
||||
const char *variable;
|
||||
int (* arg_parser)(const char *, char16_t **, size_t *);
|
||||
|
||||
if (streq(argv[0], "set-default")) {
|
||||
variable = EFI_LOADER_VARIABLE_STR("LoaderEntryDefault");
|
||||
arg_parser = parse_loader_entry_target_arg;
|
||||
} else if (streq(argv[0], "set-oneshot")) {
|
||||
variable = EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot");
|
||||
arg_parser = parse_loader_entry_target_arg;
|
||||
} else if (streq(argv[0], "set-timeout")) {
|
||||
variable = EFI_LOADER_VARIABLE_STR("LoaderConfigTimeout");
|
||||
arg_parser = parse_timeout;
|
||||
} else if (streq(argv[0], "set-timeout-oneshot")) {
|
||||
variable = EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot");
|
||||
arg_parser = parse_timeout;
|
||||
} else
|
||||
assert_not_reached();
|
||||
|
||||
if (isempty(argv[1])) {
|
||||
r = efi_set_variable(variable, NULL, 0);
|
||||
if (r < 0 && r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to remove EFI variable '%s': %m", variable);
|
||||
} else {
|
||||
_cleanup_free_ char16_t *value = NULL;
|
||||
size_t value_size = 0;
|
||||
|
||||
r = arg_parser(argv[1], &value, &value_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = efi_set_variable(variable, value, value_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update EFI variable '%s': %m", variable);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_set_efivar(int argc, char *argv[], void *userdata);
|
||||
@@ -0,0 +1,930 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "bootctl.h"
|
||||
#include "bootctl-status.h"
|
||||
#include "bootctl-util.h"
|
||||
#include "bootspec.h"
|
||||
#include "chase.h"
|
||||
#include "devnum-util.h"
|
||||
#include "dirent-util.h"
|
||||
#include "efi-api.h"
|
||||
#include "efi-loader.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "find-esp.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "terminal-util.h"
|
||||
#include "tpm2-util.h"
|
||||
|
||||
static int boot_config_load_and_select(
|
||||
BootConfig *config,
|
||||
const char *esp_path,
|
||||
dev_t esp_devid,
|
||||
const char *xbootldr_path,
|
||||
dev_t xbootldr_devid) {
|
||||
|
||||
int r;
|
||||
|
||||
/* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would
|
||||
* find the same entries twice. */
|
||||
bool same = esp_path && xbootldr_path && devnum_set_and_equal(esp_devid, xbootldr_devid);
|
||||
|
||||
r = boot_config_load(config, esp_path, same ? NULL : xbootldr_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!arg_root) {
|
||||
_cleanup_strv_free_ char **efi_entries = NULL;
|
||||
|
||||
r = efi_loader_get_entries(&efi_entries);
|
||||
if (r == -ENOENT || ERRNO_IS_NEG_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_config_augment_from_loader(config, efi_entries, /* only_auto= */ false);
|
||||
}
|
||||
|
||||
return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root);
|
||||
}
|
||||
|
||||
static int status_entries(
|
||||
const BootConfig *config,
|
||||
const char *esp_path,
|
||||
sd_id128_t esp_partition_uuid,
|
||||
const char *xbootldr_path,
|
||||
sd_id128_t xbootldr_partition_uuid) {
|
||||
|
||||
sd_id128_t dollar_boot_partition_uuid;
|
||||
const char *dollar_boot_path;
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
assert(esp_path || xbootldr_path);
|
||||
|
||||
if (xbootldr_path) {
|
||||
dollar_boot_path = xbootldr_path;
|
||||
dollar_boot_partition_uuid = xbootldr_partition_uuid;
|
||||
} else {
|
||||
dollar_boot_path = esp_path;
|
||||
dollar_boot_partition_uuid = esp_partition_uuid;
|
||||
}
|
||||
|
||||
printf("%sBoot Loader Entries:%s\n"
|
||||
" $BOOT: %s", ansi_underline(), ansi_normal(), dollar_boot_path);
|
||||
if (!sd_id128_is_null(dollar_boot_partition_uuid))
|
||||
printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")",
|
||||
SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid));
|
||||
if (settle_entry_token() >= 0)
|
||||
printf("\n token: %s", arg_entry_token);
|
||||
printf("\n\n");
|
||||
|
||||
if (config->default_entry < 0)
|
||||
printf("%zu entries, no entry could be determined as default.\n", config->n_entries);
|
||||
else {
|
||||
printf("%sDefault Boot Loader Entry:%s\n", ansi_underline(), ansi_normal());
|
||||
|
||||
r = show_boot_entry(
|
||||
boot_config_default_entry(config),
|
||||
&config->global_addons,
|
||||
/* show_as_default= */ false,
|
||||
/* show_as_selected= */ false,
|
||||
/* show_discovered= */ false);
|
||||
if (r > 0)
|
||||
/* < 0 is already logged by the function itself, let's just emit an extra warning if
|
||||
the default entry is broken */
|
||||
printf("\nWARNING: default boot entry is broken\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int print_efi_option(uint16_t id, int *n_printed, bool in_order) {
|
||||
_cleanup_free_ char *title = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
sd_id128_t partition;
|
||||
bool active;
|
||||
int r;
|
||||
|
||||
assert(n_printed);
|
||||
|
||||
r = efi_get_boot_option(id, &title, &partition, &path, &active);
|
||||
if (r == -ENOENT) {
|
||||
log_debug_errno(r, "Boot option 0x%04X referenced but missing, ignoring: %m", id);
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read boot option 0x%04X: %m", id);
|
||||
|
||||
/* print only configured entries with partition information */
|
||||
if (!path || sd_id128_is_null(partition)) {
|
||||
log_debug("Ignoring boot entry 0x%04X without partition information.", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
efi_tilt_backslashes(path);
|
||||
|
||||
if (*n_printed == 0) /* Print section title before first entry */
|
||||
printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal());
|
||||
|
||||
printf(" Title: %s%s%s\n", ansi_highlight(), strna(title), ansi_normal());
|
||||
printf(" ID: 0x%04X\n", id);
|
||||
printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : "");
|
||||
printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n",
|
||||
SD_ID128_FORMAT_VAL(partition));
|
||||
printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path);
|
||||
printf("\n");
|
||||
|
||||
(*n_printed)++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int status_variables(void) {
|
||||
_cleanup_free_ uint16_t *options = NULL, *order = NULL;
|
||||
int n_options, n_order, n_printed = 0;
|
||||
|
||||
n_options = efi_get_boot_options(&options);
|
||||
if (n_options == -ENOENT)
|
||||
return log_error_errno(n_options,
|
||||
"Failed to access EFI variables, efivarfs"
|
||||
" needs to be available at /sys/firmware/efi/efivars/.");
|
||||
if (n_options < 0)
|
||||
return log_error_errno(n_options, "Failed to read EFI boot entries: %m");
|
||||
|
||||
n_order = efi_get_boot_order(&order);
|
||||
if (n_order == -ENOENT)
|
||||
n_order = 0;
|
||||
else if (n_order < 0)
|
||||
return log_error_errno(n_order, "Failed to read EFI boot order: %m");
|
||||
|
||||
/* print entries in BootOrder first */
|
||||
for (int i = 0; i < n_order; i++)
|
||||
(void) print_efi_option(order[i], &n_printed, /* in_order= */ true);
|
||||
|
||||
/* print remaining entries */
|
||||
for (int i = 0; i < n_options; i++) {
|
||||
for (int j = 0; j < n_order; j++)
|
||||
if (options[i] == order[j])
|
||||
goto next_option;
|
||||
|
||||
(void) print_efi_option(options[i], &n_printed, /* in_order= */ false);
|
||||
|
||||
next_option:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n_printed == 0)
|
||||
printf("No boot loaders listed in EFI Variables.\n\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enumerate_binaries(
|
||||
const char *esp_path,
|
||||
const char *path,
|
||||
char **previous,
|
||||
bool *is_first) {
|
||||
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
int c = 0, r;
|
||||
|
||||
assert(esp_path);
|
||||
assert(path);
|
||||
assert(previous);
|
||||
assert(is_first);
|
||||
|
||||
r = chase_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read \"%s/%s\": %m", esp_path, path);
|
||||
|
||||
FOREACH_DIRENT(de, d, break) {
|
||||
_cleanup_free_ char *v = NULL, *filename = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
if (!endswith_no_case(de->d_name, ".efi"))
|
||||
continue;
|
||||
|
||||
filename = path_join(p, de->d_name);
|
||||
if (!filename)
|
||||
return log_oom();
|
||||
LOG_SET_PREFIX(filename);
|
||||
|
||||
fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open file for reading: %m");
|
||||
|
||||
r = get_file_version(fd, &v);
|
||||
|
||||
if (r < 0 && r != -ESRCH)
|
||||
return r;
|
||||
|
||||
if (*previous) { /* Let's output the previous entry now, since now we know that there will be
|
||||
* one more, and can draw the tree glyph properly. */
|
||||
printf(" %s %s%s\n",
|
||||
*is_first ? "File:" : " ",
|
||||
special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous);
|
||||
*is_first = false;
|
||||
*previous = mfree(*previous);
|
||||
}
|
||||
|
||||
/* Do not output this entry immediately, but store what should be printed in a state
|
||||
* variable, because we only will know the tree glyph to print (branch or final edge) once we
|
||||
* read one more entry */
|
||||
if (r == -ESRCH) /* No systemd-owned file but still interesting to print */
|
||||
r = asprintf(previous, "/%s/%s", path, de->d_name);
|
||||
else /* if (r >= 0) */
|
||||
r = asprintf(previous, "/%s/%s (%s%s%s)", path, de->d_name, ansi_highlight(), v, ansi_normal());
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
c++;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static int status_binaries(const char *esp_path, sd_id128_t partition) {
|
||||
_cleanup_free_ char *last = NULL;
|
||||
bool is_first = true;
|
||||
int r, k;
|
||||
|
||||
printf("%sAvailable Boot Loaders on ESP:%s\n", ansi_underline(), ansi_normal());
|
||||
|
||||
if (!esp_path) {
|
||||
printf(" ESP: Cannot find or access mount point of ESP.\n\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
printf(" ESP: %s", esp_path);
|
||||
if (!sd_id128_is_null(partition))
|
||||
printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition));
|
||||
printf("\n");
|
||||
|
||||
r = enumerate_binaries(esp_path, "EFI/systemd", &last, &is_first);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
k = enumerate_binaries(esp_path, "EFI/BOOT", &last, &is_first);
|
||||
if (k < 0) {
|
||||
r = k;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (last) /* let's output the last entry now, since now we know that there will be no more, and can draw the tree glyph properly */
|
||||
printf(" %s %s%s\n",
|
||||
is_first ? "File:" : " ",
|
||||
special_glyph(SPECIAL_GLYPH_TREE_RIGHT), last);
|
||||
|
||||
if (r == 0 && !arg_quiet)
|
||||
log_info("systemd-boot not installed in ESP.");
|
||||
if (k == 0 && !arg_quiet)
|
||||
log_info("No default/fallback boot loader installed in ESP.");
|
||||
|
||||
printf("\n");
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
errno = -r;
|
||||
printf(" File: (can't access %s: %m)\n\n", esp_path);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int efi_get_variable_string_and_warn(const char *variable, char **ret) {
|
||||
int r;
|
||||
|
||||
r = efi_get_variable_string(variable, ret);
|
||||
if (r < 0 && r != -ENOENT)
|
||||
return log_warning_errno(r, "Failed to read EFI variable '%s', ignoring: %m", variable);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int efi_get_variable_path_and_warn(const char *variable, char **ret) {
|
||||
int r;
|
||||
|
||||
r = efi_get_variable_path(variable, ret);
|
||||
if (r < 0 && r != -ENOENT)
|
||||
return log_warning_errno(r, "Failed to read EFI variable '%s', ignoring: %m", variable);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void print_yes_no_line(bool first, bool good, const char *name) {
|
||||
printf("%s%s %s\n",
|
||||
first ? " Features: " : " ",
|
||||
COLOR_MARK_BOOL(good),
|
||||
name);
|
||||
}
|
||||
|
||||
int verb_status(int argc, char *argv[], void *userdata) {
|
||||
sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL;
|
||||
dev_t esp_devid = 0, xbootldr_devid = 0;
|
||||
int r, k;
|
||||
|
||||
r = acquire_esp(/* unprivileged_mode= */ -1,
|
||||
/* graceful= */ false,
|
||||
/* ret_part= */ NULL,
|
||||
/* ret_pstart= */ NULL,
|
||||
/* ret_psize= */ NULL,
|
||||
&esp_uuid,
|
||||
&esp_devid);
|
||||
if (arg_print_esp_path) {
|
||||
if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only
|
||||
* error the find_esp_and_warn() won't log on its own) */
|
||||
return log_error_errno(r, "Failed to determine ESP location: %m");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
puts(arg_esp_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = acquire_xbootldr(
|
||||
/* unprivileged_mode= */ -1,
|
||||
&xbootldr_uuid,
|
||||
&xbootldr_devid);
|
||||
if (arg_print_dollar_boot_path) {
|
||||
if (r == -EACCES)
|
||||
return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
const char *path = arg_dollar_boot_path();
|
||||
if (!path)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Failed to determine XBOOTLDR location.");
|
||||
|
||||
puts(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = 0; /* If we couldn't determine the path, then don't consider that a problem from here on, just
|
||||
* show what we can show */
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
if (!arg_root && is_efi_boot()) {
|
||||
static const struct {
|
||||
uint64_t flag;
|
||||
const char *name;
|
||||
} loader_flags[] = {
|
||||
{ EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" },
|
||||
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" },
|
||||
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" },
|
||||
{ EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" },
|
||||
{ EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" },
|
||||
{ EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" },
|
||||
{ EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" },
|
||||
{ EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" },
|
||||
{ EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" },
|
||||
{ EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" },
|
||||
{ EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" },
|
||||
{ EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" },
|
||||
{ EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" },
|
||||
{ EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" },
|
||||
{ EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" },
|
||||
};
|
||||
static const struct {
|
||||
uint64_t flag;
|
||||
const char *name;
|
||||
} stub_flags[] = {
|
||||
{ EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets loader partition information" },
|
||||
{ EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" },
|
||||
{ EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" },
|
||||
{ EFI_STUB_FEATURE_PICK_UP_CONFEXTS, "Picks up configuration extension images from boot partition" },
|
||||
{ EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" },
|
||||
{ EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" },
|
||||
{ EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" },
|
||||
{ EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" },
|
||||
{ EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" },
|
||||
{ EFI_STUB_FEATURE_MULTI_PROFILE_UKI, "Stub understands profile selector" },
|
||||
{ EFI_STUB_FEATURE_REPORT_STUB_PARTITION, "Stub sets stub partition information" },
|
||||
};
|
||||
_cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL,
|
||||
*current_entry = NULL, *oneshot_entry = NULL, *default_entry = NULL;
|
||||
uint64_t loader_features = 0, stub_features = 0;
|
||||
int have;
|
||||
|
||||
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderFirmwareType"), &fw_type);
|
||||
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderFirmwareInfo"), &fw_info);
|
||||
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderInfo"), &loader);
|
||||
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("StubInfo"), &stub);
|
||||
(void) efi_get_variable_path_and_warn(EFI_LOADER_VARIABLE_STR("LoaderImageIdentifier"), &loader_path);
|
||||
(void) efi_get_variable_path_and_warn(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &stub_path);
|
||||
(void) efi_loader_get_features(&loader_features);
|
||||
(void) efi_stub_get_features(&stub_features);
|
||||
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), ¤t_entry);
|
||||
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &oneshot_entry);
|
||||
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &default_entry);
|
||||
|
||||
SecureBootMode secure = efi_get_secure_boot_mode();
|
||||
printf("%sSystem:%s\n", ansi_underline(), ansi_normal());
|
||||
printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal());
|
||||
printf(" Firmware Arch: %s\n", get_efi_arch());
|
||||
printf(" Secure Boot: %s%s%s",
|
||||
IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED) ? ansi_highlight_green() : ansi_normal(),
|
||||
enabled_disabled(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)),
|
||||
ansi_normal());
|
||||
|
||||
if (secure != SECURE_BOOT_DISABLED)
|
||||
printf(" (%s)\n", secure_boot_mode_to_string(secure));
|
||||
else
|
||||
printf("\n");
|
||||
|
||||
Tpm2Support s = tpm2_support_full(TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER);
|
||||
printf(" TPM2 Support: %s%s%s\n",
|
||||
FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? ansi_highlight_green() :
|
||||
(s & (TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER)) != 0 ? ansi_highlight_red() : ansi_highlight_yellow(),
|
||||
FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? "yes" :
|
||||
(s & TPM2_SUPPORT_FIRMWARE) ? "firmware only, driver unavailable" :
|
||||
(s & TPM2_SUPPORT_DRIVER) ? "driver only, firmware unavailable" : "no",
|
||||
ansi_normal());
|
||||
|
||||
k = efi_measured_uki(LOG_DEBUG);
|
||||
if (k > 0)
|
||||
printf(" Measured UKI: %syes%s\n", ansi_highlight_green(), ansi_normal());
|
||||
else if (k == 0)
|
||||
printf(" Measured UKI: no\n");
|
||||
else {
|
||||
errno = -k;
|
||||
printf(" Measured UKI: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal());
|
||||
}
|
||||
|
||||
k = efi_get_reboot_to_firmware();
|
||||
if (k > 0)
|
||||
printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal());
|
||||
else if (k == 0)
|
||||
printf(" Boot into FW: supported\n");
|
||||
else if (k == -EOPNOTSUPP)
|
||||
printf(" Boot into FW: not supported\n");
|
||||
else {
|
||||
errno = -k;
|
||||
printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal());
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
if (loader) {
|
||||
printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal());
|
||||
printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal());
|
||||
for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++)
|
||||
print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name);
|
||||
|
||||
sd_id128_t loader_partition_uuid;
|
||||
bool have_loader_partition_uuid = efi_loader_get_device_part_uuid(&loader_partition_uuid) >= 0;
|
||||
|
||||
print_yes_no_line(false, have_loader_partition_uuid, "Boot loader set ESP information");
|
||||
|
||||
if (current_entry)
|
||||
printf("Current Entry: %s\n", current_entry);
|
||||
if (default_entry)
|
||||
printf("Default Entry: %s\n", default_entry);
|
||||
if (oneshot_entry && !streq_ptr(oneshot_entry, default_entry))
|
||||
printf("OneShot Entry: %s\n", oneshot_entry);
|
||||
|
||||
if (have_loader_partition_uuid && !sd_id128_is_null(esp_uuid) && !sd_id128_equal(esp_uuid, loader_partition_uuid))
|
||||
printf("WARNING: The boot loader reports a different partition UUID than the detected ESP ("SD_ID128_UUID_FORMAT_STR" vs. "SD_ID128_UUID_FORMAT_STR")!\n",
|
||||
SD_ID128_FORMAT_VAL(loader_partition_uuid), SD_ID128_FORMAT_VAL(esp_uuid));
|
||||
|
||||
if (!sd_id128_is_null(loader_partition_uuid))
|
||||
printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n",
|
||||
SD_ID128_FORMAT_VAL(loader_partition_uuid));
|
||||
else
|
||||
printf(" Partition: n/a\n");
|
||||
printf(" Loader: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (stub) {
|
||||
printf("%sCurrent Stub:%s\n", ansi_underline(), ansi_normal());
|
||||
printf(" Product: %s%s%s\n", ansi_highlight(), stub, ansi_normal());
|
||||
for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++)
|
||||
print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name);
|
||||
|
||||
sd_id128_t stub_partition_uuid;
|
||||
bool have_stub_partition_uuid = efi_stub_get_device_part_uuid(&stub_partition_uuid) >= 0;
|
||||
|
||||
if (have_stub_partition_uuid && (!(!sd_id128_is_null(esp_uuid) && sd_id128_equal(esp_uuid, stub_partition_uuid)) &&
|
||||
!(!sd_id128_is_null(xbootldr_uuid) && sd_id128_equal(xbootldr_uuid, stub_partition_uuid))))
|
||||
printf("WARNING: The stub loader reports a different UUID than the detected ESP or XBOOTDLR partition ("SD_ID128_UUID_FORMAT_STR" vs. "SD_ID128_UUID_FORMAT_STR"/"SD_ID128_UUID_FORMAT_STR")!\n",
|
||||
SD_ID128_FORMAT_VAL(stub_partition_uuid), SD_ID128_FORMAT_VAL(esp_uuid), SD_ID128_FORMAT_VAL(xbootldr_uuid));
|
||||
if (!sd_id128_is_null(stub_partition_uuid))
|
||||
printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n",
|
||||
SD_ID128_FORMAT_VAL(stub_partition_uuid));
|
||||
else
|
||||
printf(" Partition: n/a\n");
|
||||
printf(" Stub: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(stub_path));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal());
|
||||
have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderSystemToken")), F_OK) >= 0;
|
||||
printf(" System Token: %s\n", have ? "set" : "not set");
|
||||
|
||||
if (arg_esp_path) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
p = path_join(arg_esp_path, "/loader/random-seed");
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
have = access(p, F_OK) >= 0;
|
||||
printf(" Exists: %s\n", yes_no(have));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
} else
|
||||
printf("%sSystem:%s\n"
|
||||
"Not booted with EFI\n\n",
|
||||
ansi_underline(), ansi_normal());
|
||||
|
||||
if (arg_esp_path)
|
||||
RET_GATHER(r, status_binaries(arg_esp_path, esp_uuid));
|
||||
|
||||
if (!arg_root && is_efi_boot())
|
||||
RET_GATHER(r, status_variables());
|
||||
|
||||
if (arg_esp_path || arg_xbootldr_path) {
|
||||
_cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
|
||||
|
||||
k = boot_config_load_and_select(&config,
|
||||
arg_esp_path, esp_devid,
|
||||
arg_xbootldr_path, xbootldr_devid);
|
||||
RET_GATHER(r, k);
|
||||
|
||||
if (k >= 0)
|
||||
RET_GATHER(r,
|
||||
status_entries(&config,
|
||||
arg_esp_path, esp_uuid,
|
||||
arg_xbootldr_path, xbootldr_uuid));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int ref_file(Hashmap *known_files, const char *fn, int increment) {
|
||||
char *k = NULL;
|
||||
int n, r;
|
||||
|
||||
assert(known_files);
|
||||
|
||||
/* just gracefully ignore this. This way the caller doesn't
|
||||
have to verify whether the bootloader entry is relevant */
|
||||
if (!fn)
|
||||
return 0;
|
||||
|
||||
n = PTR_TO_INT(hashmap_get2(known_files, fn, (void**)&k));
|
||||
n += increment;
|
||||
|
||||
assert(n >= 0);
|
||||
|
||||
if (n == 0) {
|
||||
(void) hashmap_remove(known_files, fn);
|
||||
free(k);
|
||||
} else if (!k) {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
|
||||
t = strdup(fn);
|
||||
if (!t)
|
||||
return -ENOMEM;
|
||||
r = hashmap_put(known_files, t, INT_TO_PTR(n));
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PTR(t);
|
||||
} else {
|
||||
r = hashmap_update(known_files, fn, INT_TO_PTR(n));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *root) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
int r;
|
||||
|
||||
assert(known_files);
|
||||
|
||||
/* just gracefully ignore this. This way the caller doesn't
|
||||
have to verify whether the bootloader entry is relevant */
|
||||
if (!fn || !root)
|
||||
return;
|
||||
|
||||
r = ref_file(known_files, fn, -1);
|
||||
if (r < 0)
|
||||
return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn);
|
||||
if (r > 0)
|
||||
return;
|
||||
|
||||
if (arg_dry_run) {
|
||||
r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path);
|
||||
if (r < 0)
|
||||
log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn);
|
||||
else
|
||||
log_info("Would remove \"%s\"", path);
|
||||
return;
|
||||
}
|
||||
|
||||
r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, 0, &path);
|
||||
if (r >= 0)
|
||||
log_info("Removed \"%s\"", path);
|
||||
else if (r != -ENOENT)
|
||||
return (void) log_warning_errno(r, "Failed to remove \"%s\", ignoring: %m", fn);
|
||||
|
||||
_cleanup_free_ char *d = NULL;
|
||||
if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) {
|
||||
r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, AT_REMOVEDIR, NULL);
|
||||
if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT))
|
||||
log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d);
|
||||
}
|
||||
}
|
||||
|
||||
static int count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files) {
|
||||
_cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
assert(ret_known_files);
|
||||
|
||||
known_files = hashmap_new(&path_hash_ops);
|
||||
if (!known_files)
|
||||
return -ENOMEM;
|
||||
|
||||
for (size_t i = 0; i < config->n_entries; i++) {
|
||||
const BootEntry *e = config->entries + i;
|
||||
|
||||
if (!path_equal(e->root, root))
|
||||
continue;
|
||||
|
||||
r = ref_file(known_files, e->kernel, +1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = ref_file(known_files, e->efi, +1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
STRV_FOREACH(s, e->initrd) {
|
||||
r = ref_file(known_files, *s, +1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
r = ref_file(known_files, e->device_tree, +1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
STRV_FOREACH(s, e->device_tree_overlay) {
|
||||
r = ref_file(known_files, *s, +1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
*ret_known_files = TAKE_PTR(known_files);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) {
|
||||
assert(config);
|
||||
|
||||
if (!root || !id)
|
||||
return -ENOENT;
|
||||
|
||||
for (size_t i = 0; i < config->n_entries; i++)
|
||||
if (path_equal(config->entries[i].root, root) &&
|
||||
fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
|
||||
return i;
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int unlink_entry(const BootConfig *config, const char *root, const char *id) {
|
||||
_cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
|
||||
const BootEntry *e = NULL;
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
|
||||
r = count_known_files(config, root, &known_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to count files in %s: %m", root);
|
||||
|
||||
r = boot_config_find_in(config, root, id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (r == config->default_entry)
|
||||
log_warning("%s is the default boot entry", id);
|
||||
if (r == config->selected_entry)
|
||||
log_warning("%s is the selected boot entry", id);
|
||||
|
||||
e = &config->entries[r];
|
||||
|
||||
deref_unlink_file(known_files, e->kernel, e->root);
|
||||
deref_unlink_file(known_files, e->efi, e->root);
|
||||
STRV_FOREACH(s, e->initrd)
|
||||
deref_unlink_file(known_files, *s, e->root);
|
||||
deref_unlink_file(known_files, e->device_tree, e->root);
|
||||
STRV_FOREACH(s, e->device_tree_overlay)
|
||||
deref_unlink_file(known_files, *s, e->root);
|
||||
|
||||
if (arg_dry_run)
|
||||
log_info("Would remove \"%s\"", e->path);
|
||||
else {
|
||||
r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS, 0, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to remove \"%s\": %m", e->path);
|
||||
|
||||
log_info("Removed %s", e->path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int list_remove_orphaned_file(
|
||||
RecurseDirEvent event,
|
||||
const char *path,
|
||||
int dir_fd,
|
||||
int inode_fd,
|
||||
const struct dirent *de,
|
||||
const struct statx *sx,
|
||||
void *userdata) {
|
||||
|
||||
Hashmap *known_files = userdata;
|
||||
|
||||
assert(path);
|
||||
assert(known_files);
|
||||
|
||||
if (event != RECURSE_DIR_ENTRY)
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
|
||||
if (hashmap_get(known_files, path))
|
||||
return RECURSE_DIR_CONTINUE; /* keep! */
|
||||
|
||||
if (arg_dry_run)
|
||||
log_info("Would remove %s", path);
|
||||
else if (unlinkat(dir_fd, de->d_name, 0) < 0)
|
||||
log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", path);
|
||||
else
|
||||
log_info("Removed %s", path);
|
||||
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
}
|
||||
|
||||
static int cleanup_orphaned_files(
|
||||
const BootConfig *config,
|
||||
const char *root) {
|
||||
|
||||
_cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
|
||||
_cleanup_free_ char *full = NULL, *p = NULL;
|
||||
_cleanup_close_ int dir_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
assert(root);
|
||||
|
||||
log_info("Cleaning %s", root);
|
||||
|
||||
r = settle_entry_token();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = count_known_files(config, root, &known_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to count files in %s: %m", root);
|
||||
|
||||
dir_fd = chase_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
|
||||
O_DIRECTORY|O_CLOEXEC, &full);
|
||||
if (dir_fd == -ENOENT)
|
||||
return 0;
|
||||
if (dir_fd < 0)
|
||||
return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, arg_entry_token);
|
||||
|
||||
p = path_join("/", arg_entry_token);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
r = recurse_dir(dir_fd, p, 0, UINT_MAX, RECURSE_DIR_SORT, list_remove_orphaned_file, known_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to cleanup %s: %m", full);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int verb_list(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
|
||||
dev_t esp_devid = 0, xbootldr_devid = 0;
|
||||
int r;
|
||||
|
||||
/* If we lack privileges we invoke find_esp_and_warn() in "unprivileged mode" here, which does two
|
||||
* things: turn off logging about access errors and turn off potentially privileged device probing.
|
||||
* Here we're interested in the latter but not the former, hence request the mode, and log about
|
||||
* EACCES. */
|
||||
|
||||
r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, NULL, &esp_devid);
|
||||
if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */
|
||||
return log_error_errno(r, "Failed to determine ESP location: %m");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = acquire_xbootldr(/* unprivileged_mode= */ -1, NULL, &xbootldr_devid);
|
||||
if (r == -EACCES)
|
||||
return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (config.n_entries == 0 && !sd_json_format_enabled(arg_json_format_flags)) {
|
||||
log_info("No boot loader entries found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (streq(argv[0], "list")) {
|
||||
pager_open(arg_pager_flags);
|
||||
return show_boot_entries(&config, arg_json_format_flags);
|
||||
} else if (streq(argv[0], "cleanup")) {
|
||||
if (arg_xbootldr_path && xbootldr_devid != esp_devid)
|
||||
cleanup_orphaned_files(&config, arg_xbootldr_path);
|
||||
return cleanup_orphaned_files(&config, arg_esp_path);
|
||||
} else {
|
||||
assert(streq(argv[0], "unlink"));
|
||||
if (arg_xbootldr_path && xbootldr_devid != esp_devid) {
|
||||
r = unlink_entry(&config, arg_xbootldr_path, argv[1]);
|
||||
if (r == 0 || r != -ENOENT)
|
||||
return r;
|
||||
}
|
||||
return unlink_entry(&config, arg_esp_path, argv[1]);
|
||||
}
|
||||
}
|
||||
|
||||
int verb_unlink(int argc, char *argv[], void *userdata) {
|
||||
return verb_list(argc, argv, userdata);
|
||||
}
|
||||
|
||||
int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
_cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
|
||||
dev_t esp_devid = 0, xbootldr_devid = 0;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
if (sd_json_variant_elements(parameters) > 0)
|
||||
return sd_varlink_error_invalid_parameter(link, parameters);
|
||||
|
||||
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
|
||||
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL);
|
||||
|
||||
r = acquire_esp(/* unprivileged_mode= */ false,
|
||||
/* graceful= */ false,
|
||||
/* ret_part= */ NULL,
|
||||
/* ret_pstart= */ NULL,
|
||||
/* ret_psize= */ NULL,
|
||||
/* ret_uuid=*/ NULL,
|
||||
&esp_devid);
|
||||
if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */
|
||||
return log_error_errno(r, "Failed to determine ESP location: %m");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = acquire_xbootldr(
|
||||
/* unprivileged_mode= */ false,
|
||||
/* ret_uuid= */ NULL,
|
||||
&xbootldr_devid);
|
||||
if (r == -EACCES)
|
||||
return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *previous = NULL;
|
||||
for (size_t i = 0; i < config.n_entries; i++) {
|
||||
if (previous) {
|
||||
r = sd_varlink_notifybo(link, SD_JSON_BUILD_PAIR_VARIANT("entry", previous));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
previous = sd_json_variant_unref(previous);
|
||||
}
|
||||
|
||||
r = boot_entry_to_json(&config, i, &previous);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!previous)
|
||||
return sd_varlink_error(link, "io.systemd.BootControl.NoSuchBootEntry", NULL);
|
||||
|
||||
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_VARIANT("entry", previous));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-varlink.h"
|
||||
|
||||
int verb_status(int argc, char *argv[], void *userdata);
|
||||
int verb_list(int argc, char *argv[], void *userdata);
|
||||
int verb_unlink(int argc, char *argv[], void *userdata);
|
||||
|
||||
int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
|
||||
@@ -0,0 +1,43 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bootctl.h"
|
||||
#include "bootctl-systemd-efi-options.h"
|
||||
#include "efi-loader.h"
|
||||
|
||||
int verb_systemd_efi_options(int argc, char *argv[], void *userdata) {
|
||||
int r;
|
||||
|
||||
/* This is obsolete and subject to removal */
|
||||
|
||||
if (!arg_quiet)
|
||||
log_notice("Use of the SystemdOptions EFI variable is deprecated.");
|
||||
|
||||
if (argc == 1) {
|
||||
_cleanup_free_ char *line = NULL, *new = NULL;
|
||||
|
||||
r = systemd_efi_options_variable(&line);
|
||||
if (r == -ENODATA)
|
||||
log_debug("No SystemdOptions EFI variable present in cache.");
|
||||
else if (r < 0)
|
||||
return log_error_errno(r, "Failed to read SystemdOptions EFI variable from cache: %m");
|
||||
else
|
||||
puts(line);
|
||||
|
||||
r = systemd_efi_options_efivarfs_if_newer(&new);
|
||||
if (r == -ENODATA) {
|
||||
if (line)
|
||||
log_notice("Note: SystemdOptions EFI variable has been removed since boot.");
|
||||
} else if (r < 0)
|
||||
log_warning_errno(r, "Failed to check SystemdOptions EFI variable in efivarfs, ignoring: %m");
|
||||
else if (new && !streq_ptr(line, new))
|
||||
log_notice("Note: SystemdOptions EFI variable has been modified since boot. New value: %s",
|
||||
new);
|
||||
} else {
|
||||
r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions"), argv[1]);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set SystemdOptions EFI variable: %m");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_systemd_efi_options(int argc, char *argv[], void *userdata);
|
||||
@@ -0,0 +1,39 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bootctl-uki.h"
|
||||
#include "kernel-image.h"
|
||||
|
||||
int verb_kernel_identify(int argc, char *argv[], void *userdata) {
|
||||
KernelImageType t;
|
||||
int r;
|
||||
|
||||
r = inspect_kernel(AT_FDCWD, argv[1], &t, NULL, NULL, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
puts(kernel_image_type_to_string(t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int verb_kernel_inspect(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *cmdline = NULL, *uname = NULL, *pname = NULL;
|
||||
KernelImageType t;
|
||||
int r;
|
||||
|
||||
r = inspect_kernel(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("Kernel Type: %s\n", kernel_image_type_to_string(t));
|
||||
if (cmdline)
|
||||
printf(" Cmdline: %s\n", cmdline);
|
||||
if (uname)
|
||||
printf(" Version: %s\n", uname);
|
||||
if (pname)
|
||||
printf(" OS: %s\n", pname);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
int verb_kernel_identify(int argc, char *argv[], void *userdata);
|
||||
int verb_kernel_inspect(int argc, char *argv[], void *userdata);
|
||||
@@ -0,0 +1,132 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "bootctl.h"
|
||||
#include "bootctl-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fileio.h"
|
||||
#include "stat-util.h"
|
||||
#include "sync-util.h"
|
||||
|
||||
int sync_everything(void) {
|
||||
int r = 0, k;
|
||||
|
||||
if (arg_esp_path) {
|
||||
k = syncfs_path(AT_FDCWD, arg_esp_path);
|
||||
if (k < 0)
|
||||
RET_GATHER(r, log_error_errno(k, "Failed to synchronize the ESP '%s': %m", arg_esp_path));
|
||||
}
|
||||
|
||||
if (arg_xbootldr_path) {
|
||||
k = syncfs_path(AT_FDCWD, arg_xbootldr_path);
|
||||
if (k < 0)
|
||||
RET_GATHER(r, log_error_errno(k, "Failed to synchronize $BOOT '%s': %m", arg_xbootldr_path));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
const char* get_efi_arch(void) {
|
||||
/* Detect EFI firmware architecture of the running system. On mixed mode systems, it could be 32-bit
|
||||
* while the kernel is running in 64-bit. */
|
||||
|
||||
#ifdef __x86_64__
|
||||
_cleanup_free_ char *platform_size = NULL;
|
||||
int r;
|
||||
|
||||
r = read_one_line_file("/sys/firmware/efi/fw_platform_size", &platform_size);
|
||||
if (r == -ENOENT)
|
||||
return EFI_MACHINE_TYPE_NAME;
|
||||
if (r < 0) {
|
||||
log_warning_errno(r,
|
||||
"Error reading EFI firmware word size, assuming machine type '%s': %m",
|
||||
EFI_MACHINE_TYPE_NAME);
|
||||
return EFI_MACHINE_TYPE_NAME;
|
||||
}
|
||||
|
||||
if (streq(platform_size, "64"))
|
||||
return EFI_MACHINE_TYPE_NAME;
|
||||
if (streq(platform_size, "32"))
|
||||
return "ia32";
|
||||
|
||||
log_warning(
|
||||
"Unknown EFI firmware word size '%s', using machine type '%s'.",
|
||||
platform_size,
|
||||
EFI_MACHINE_TYPE_NAME);
|
||||
#endif
|
||||
|
||||
return EFI_MACHINE_TYPE_NAME;
|
||||
}
|
||||
|
||||
/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */
|
||||
int get_file_version(int fd, char **ret) {
|
||||
struct stat st;
|
||||
char *buf;
|
||||
const char *s, *e;
|
||||
char *marker = NULL;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat EFI binary: %m");
|
||||
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "EFI binary is not a regular file, assuming no version information: %m");
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ESRCH),
|
||||
"EFI binary size too %s: %"PRIi64,
|
||||
st.st_size < 27 ? "small" : "large", st.st_size);
|
||||
|
||||
buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (buf == MAP_FAILED)
|
||||
return log_error_errno(errno, "Failed to mmap EFI binary: %m");
|
||||
|
||||
s = mempmem_safe(buf, st.st_size - 8, "#### LoaderInfo: ", 17);
|
||||
if (!s) {
|
||||
r = log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no LoaderInfo marker.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
e = memmem_safe(s, st.st_size - (s - buf), " ####", 5);
|
||||
if (!e || e - s < 3) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "EFI binary has malformed LoaderInfo marker.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
marker = strndup(s, e - s);
|
||||
if (!marker) {
|
||||
r = log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
log_debug("EFI binary LoaderInfo marker: \"%s\"", marker);
|
||||
r = 0;
|
||||
*ret = marker;
|
||||
finish:
|
||||
(void) munmap(buf, st.st_size);
|
||||
return r;
|
||||
}
|
||||
|
||||
int settle_entry_token(void) {
|
||||
int r;
|
||||
|
||||
r = boot_entry_token_ensure(
|
||||
arg_root,
|
||||
getenv("KERNEL_INSTALL_CONF_ROOT"),
|
||||
arg_machine_id,
|
||||
/* machine_id_is_random = */ false,
|
||||
&arg_entry_token_type,
|
||||
&arg_entry_token);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("Using entry token: %s", arg_entry_token);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int sync_everything(void);
|
||||
|
||||
const char* get_efi_arch(void);
|
||||
|
||||
int get_file_version(int fd, char **ret);
|
||||
|
||||
int settle_entry_token(void);
|
||||
@@ -0,0 +1,719 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "blockdev-util.h"
|
||||
#include "bootctl.h"
|
||||
#include "bootctl-install.h"
|
||||
#include "bootctl-random-seed.h"
|
||||
#include "bootctl-reboot-to-firmware.h"
|
||||
#include "bootctl-set-efivar.h"
|
||||
#include "bootctl-status.h"
|
||||
#include "bootctl-systemd-efi-options.h"
|
||||
#include "bootctl-uki.h"
|
||||
#include "build.h"
|
||||
#include "devnum-util.h"
|
||||
#include "dissect-image.h"
|
||||
#include "efi-loader.h"
|
||||
#include "escape.h"
|
||||
#include "find-esp.h"
|
||||
#include "main-func.h"
|
||||
#include "mount-util.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "utf8.h"
|
||||
#include "varlink-io.systemd.BootControl.h"
|
||||
#include "verbs.h"
|
||||
#include "virt.h"
|
||||
|
||||
/* EFI_BOOT_OPTION_DESCRIPTION_MAX sets the maximum length for the boot option description
|
||||
* stored in NVRAM. The UEFI spec does not specify a minimum or maximum length for this
|
||||
* string, but we limit the length to something reasonable to prevent from the firmware
|
||||
* having to deal with a potentially too long string. */
|
||||
#define EFI_BOOT_OPTION_DESCRIPTION_MAX ((size_t) 255)
|
||||
|
||||
char *arg_esp_path = NULL;
|
||||
char *arg_xbootldr_path = NULL;
|
||||
bool arg_print_esp_path = false;
|
||||
bool arg_print_dollar_boot_path = false;
|
||||
bool arg_print_loader_path = false;
|
||||
bool arg_print_stub_path = false;
|
||||
unsigned arg_print_root_device = 0;
|
||||
bool arg_touch_variables = true;
|
||||
bool arg_install_random_seed = true;
|
||||
PagerFlags arg_pager_flags = 0;
|
||||
bool arg_graceful = false;
|
||||
bool arg_quiet = false;
|
||||
int arg_make_entry_directory = false; /* tri-state: < 0 for automatic logic */
|
||||
sd_id128_t arg_machine_id = SD_ID128_NULL;
|
||||
char *arg_install_layout = NULL;
|
||||
BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
|
||||
char *arg_entry_token = NULL;
|
||||
sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
|
||||
bool arg_arch_all = false;
|
||||
char *arg_root = NULL;
|
||||
char *arg_image = NULL;
|
||||
InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO;
|
||||
char *arg_efi_boot_option_description = NULL;
|
||||
bool arg_dry_run = false;
|
||||
ImagePolicy *arg_image_policy = NULL;
|
||||
bool arg_varlink = false;
|
||||
bool arg_secure_boot_auto_enroll = false;
|
||||
char *arg_certificate = NULL;
|
||||
char *arg_private_key = NULL;
|
||||
KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
|
||||
char *arg_private_key_source = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_install_layout, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
|
||||
|
||||
int acquire_esp(
|
||||
int unprivileged_mode,
|
||||
bool graceful,
|
||||
uint32_t *ret_part,
|
||||
uint64_t *ret_pstart,
|
||||
uint64_t *ret_psize,
|
||||
sd_id128_t *ret_uuid,
|
||||
dev_t *ret_devid) {
|
||||
|
||||
char *np;
|
||||
int r;
|
||||
|
||||
/* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on
|
||||
* its own, except for ENOKEY (which is good, we want to show our own message in that case,
|
||||
* suggesting use of --esp-path=) and EACCESS (only when we request unprivileged mode; in this case
|
||||
* we simply eat up the error here, so that --list and --status work too, without noise about
|
||||
* this). */
|
||||
|
||||
r = find_esp_and_warn(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid);
|
||||
if (r == -ENOKEY) {
|
||||
if (graceful)
|
||||
return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r,
|
||||
"Couldn't find EFI system partition, skipping.");
|
||||
|
||||
return log_error_errno(r,
|
||||
"Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n"
|
||||
"Alternatively, use --esp-path= to specify path to mount point.");
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(arg_esp_path, np);
|
||||
log_debug("Using EFI System Partition at %s.", arg_esp_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int acquire_xbootldr(
|
||||
int unprivileged_mode,
|
||||
sd_id128_t *ret_uuid,
|
||||
dev_t *ret_devid) {
|
||||
|
||||
char *np;
|
||||
int r;
|
||||
|
||||
r = find_xbootldr_and_warn(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid);
|
||||
if (r == -ENOKEY) {
|
||||
log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT.");
|
||||
arg_xbootldr_path = mfree(arg_xbootldr_path);
|
||||
|
||||
if (ret_uuid)
|
||||
*ret_uuid = SD_ID128_NULL;
|
||||
if (ret_devid)
|
||||
*ret_devid = 0;
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(arg_xbootldr_path, np);
|
||||
log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int print_loader_or_stub_path(void) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
sd_id128_t uuid;
|
||||
int r;
|
||||
|
||||
if (arg_print_loader_path) {
|
||||
r = efi_loader_get_device_part_uuid(&uuid);
|
||||
if (r == -ENOENT)
|
||||
return log_error_errno(r, "No loader partition UUID passed.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to determine loader partition UUID: %m");
|
||||
|
||||
r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("LoaderImageIdentifier"), &p);
|
||||
if (r == -ENOENT)
|
||||
return log_error_errno(r, "No loader EFI binary path passed.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to determine loader EFI binary path: %m");
|
||||
} else {
|
||||
assert(arg_print_stub_path);
|
||||
|
||||
r = efi_stub_get_device_part_uuid(&uuid);
|
||||
if (r == -ENOENT)
|
||||
return log_error_errno(r, "No stub partition UUID passed.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to determine stub partition UUID: %m");
|
||||
|
||||
r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &p);
|
||||
if (r == -ENOENT)
|
||||
return log_error_errno(r, "No stub EFI binary path passed.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to determine stub EFI binary path: %m");
|
||||
}
|
||||
|
||||
sd_id128_t esp_uuid;
|
||||
r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false,
|
||||
/* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL,
|
||||
&esp_uuid, /* ret_devid= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
const char *found_path = NULL;
|
||||
if (sd_id128_equal(esp_uuid, uuid))
|
||||
found_path = arg_esp_path;
|
||||
else if (arg_print_stub_path) { /* In case of the stub, also look for things in the xbootldr partition */
|
||||
sd_id128_t xbootldr_uuid;
|
||||
|
||||
r = acquire_xbootldr(/* unprivileged_mode= */ false, &xbootldr_uuid, /* ret_devid= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sd_id128_equal(xbootldr_uuid, uuid))
|
||||
found_path = arg_xbootldr_path;
|
||||
}
|
||||
|
||||
if (!found_path)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to discover partition " SD_ID128_FORMAT_STR " among mounted boot partitions.", SD_ID128_FORMAT_VAL(uuid));
|
||||
|
||||
_cleanup_free_ char *j = path_join(found_path, p);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
puts(j);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
r = terminal_urlify_man("bootctl", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n"
|
||||
"\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n"
|
||||
" status Show status of installed boot loader and EFI variables\n"
|
||||
" reboot-to-firmware [BOOL]\n"
|
||||
" Query or set reboot-to-firmware EFI flag\n"
|
||||
"\n%3$sBoot Loader Specification Commands:%4$s\n"
|
||||
" list List boot loader entries\n"
|
||||
" unlink ID Remove boot loader entry\n"
|
||||
" cleanup Remove files in ESP not referenced in any boot entry\n"
|
||||
"\n%3$sBoot Loader Interface Commands:%4$s\n"
|
||||
" set-default ID Set default boot loader entry\n"
|
||||
" set-oneshot ID Set default boot loader entry, for next boot only\n"
|
||||
" set-timeout SECONDS Set the menu timeout\n"
|
||||
" set-timeout-oneshot SECONDS\n"
|
||||
" Set the menu timeout for the next boot only\n"
|
||||
"\n%3$ssystemd-boot Commands:%4$s\n"
|
||||
" install Install systemd-boot to the ESP and EFI variables\n"
|
||||
" update Update systemd-boot in the ESP and EFI variables\n"
|
||||
" 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 or refresh random seed in ESP and EFI\n"
|
||||
" variables\n"
|
||||
"\n%3$sKernel Image Commands:%4$s\n"
|
||||
" kernel-identify Identify kernel image type\n"
|
||||
" kernel-inspect Prints details about the kernel image\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Print version\n"
|
||||
" --esp-path=PATH Path to the EFI System Partition (ESP)\n"
|
||||
" --boot-path=PATH Path to the $BOOT partition\n"
|
||||
" --root=PATH Operate on an alternate filesystem root\n"
|
||||
" --image=PATH Operate on disk image as filesystem root\n"
|
||||
" --image-policy=POLICY\n"
|
||||
" Specify disk image dissection policy\n"
|
||||
" --install-source=auto|image|host\n"
|
||||
" Where to pick files when using --root=/--image=\n"
|
||||
" -p --print-esp-path Print path to the EFI System Partition mount point\n"
|
||||
" -x --print-boot-path Print path to the $BOOT partition mount point\n"
|
||||
" --print-loader-path\n"
|
||||
" Print path to currently booted boot loader binary\n"
|
||||
" --print-stub-path Print path to currently booted unified kernel binary\n"
|
||||
" -R --print-root-device\n"
|
||||
" Print path to the block device node backing the\n"
|
||||
" root file system (returns e.g. /dev/nvme0n1p5)\n"
|
||||
" -RR Print path to the whole disk block device node\n"
|
||||
" backing the root FS (returns e.g. /dev/nvme0n1)\n"
|
||||
" --no-variables Don't touch EFI variables\n"
|
||||
" --random-seed=yes|no\n"
|
||||
" Whether to create random-seed file during install\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --graceful Don't fail when the ESP cannot be found or EFI\n"
|
||||
" variables cannot be written\n"
|
||||
" -q --quiet Suppress output\n"
|
||||
" --make-entry-directory=yes|no|auto\n"
|
||||
" Create $BOOT/ENTRY-TOKEN/ directory\n"
|
||||
" --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
|
||||
" Entry token to use for this installation\n"
|
||||
" --json=pretty|short|off\n"
|
||||
" Generate JSON output\n"
|
||||
" --all-architectures\n"
|
||||
" Install all supported EFI architectures\n"
|
||||
" --efi-boot-option-description=DESCRIPTION\n"
|
||||
" Description of the entry in the boot option list\n"
|
||||
" --dry-run Dry run (unlink and cleanup)\n"
|
||||
" --secure-boot-auto-enroll\n"
|
||||
" Set up secure boot auto-enrollment\n"
|
||||
" --private-key=PATH|URI\n"
|
||||
" Private key to use when setting up secure boot\n"
|
||||
" auto-enrollment or an engine or provider specific\n"
|
||||
" designation if --private-key-source= is used\n"
|
||||
" --private-key-source=file|provider:PROVIDER|engine:ENGINE\n"
|
||||
" Specify how to use KEY for --private-key=. Allows\n"
|
||||
" an OpenSSL engine/provider to be used when setting\n"
|
||||
" up secure boot auto-enrollment\n"
|
||||
" --certificate=PATH\n"
|
||||
" PEM certificate to use when setting up secure boot\n"
|
||||
" auto-enrollment\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_ESP_PATH = 0x100,
|
||||
ARG_BOOT_PATH,
|
||||
ARG_ROOT,
|
||||
ARG_IMAGE,
|
||||
ARG_IMAGE_POLICY,
|
||||
ARG_INSTALL_SOURCE,
|
||||
ARG_VERSION,
|
||||
ARG_NO_VARIABLES,
|
||||
ARG_RANDOM_SEED,
|
||||
ARG_NO_PAGER,
|
||||
ARG_GRACEFUL,
|
||||
ARG_MAKE_ENTRY_DIRECTORY,
|
||||
ARG_ENTRY_TOKEN,
|
||||
ARG_JSON,
|
||||
ARG_ARCH_ALL,
|
||||
ARG_EFI_BOOT_OPTION_DESCRIPTION,
|
||||
ARG_DRY_RUN,
|
||||
ARG_PRINT_LOADER_PATH,
|
||||
ARG_PRINT_STUB_PATH,
|
||||
ARG_SECURE_BOOT_AUTO_ENROLL,
|
||||
ARG_CERTIFICATE,
|
||||
ARG_PRIVATE_KEY,
|
||||
ARG_PRIVATE_KEY_SOURCE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "esp-path", required_argument, NULL, ARG_ESP_PATH },
|
||||
{ "path", required_argument, NULL, ARG_ESP_PATH }, /* Compatibility alias */
|
||||
{ "boot-path", required_argument, NULL, ARG_BOOT_PATH },
|
||||
{ "root", required_argument, NULL, ARG_ROOT },
|
||||
{ "image", required_argument, NULL, ARG_IMAGE },
|
||||
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
|
||||
{ "install-source", required_argument, NULL, ARG_INSTALL_SOURCE },
|
||||
{ "print-esp-path", no_argument, NULL, 'p' },
|
||||
{ "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */
|
||||
{ "print-boot-path", no_argument, NULL, 'x' },
|
||||
{ "print-loader-path", no_argument, NULL, ARG_PRINT_LOADER_PATH },
|
||||
{ "print-stub-path", no_argument, NULL, ARG_PRINT_STUB_PATH },
|
||||
{ "print-root-device", no_argument, NULL, 'R' },
|
||||
{ "no-variables", no_argument, NULL, ARG_NO_VARIABLES },
|
||||
{ "random-seed", required_argument, NULL, ARG_RANDOM_SEED },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
|
||||
{ "quiet", no_argument, NULL, 'q' },
|
||||
{ "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY },
|
||||
{ "make-machine-id-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, /* Compatibility alias */
|
||||
{ "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "all-architectures", no_argument, NULL, ARG_ARCH_ALL },
|
||||
{ "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION },
|
||||
{ "dry-run", no_argument, NULL, ARG_DRY_RUN },
|
||||
{ "secure-boot-auto-enroll", required_argument, NULL, ARG_SECURE_BOOT_AUTO_ENROLL },
|
||||
{ "certificate", required_argument, NULL, ARG_CERTIFICATE },
|
||||
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
|
||||
{ "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hpxRq", options, NULL)) >= 0)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
help(0, NULL, NULL);
|
||||
return 0;
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_ESP_PATH:
|
||||
r = free_and_strdup(&arg_esp_path, optarg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
case ARG_BOOT_PATH:
|
||||
r = free_and_strdup(&arg_xbootldr_path, optarg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
case ARG_ROOT:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE_POLICY:
|
||||
r = parse_image_policy_argument(optarg, &arg_image_policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_INSTALL_SOURCE:
|
||||
if (streq(optarg, "auto"))
|
||||
arg_install_source = ARG_INSTALL_SOURCE_AUTO;
|
||||
else if (streq(optarg, "image"))
|
||||
arg_install_source = ARG_INSTALL_SOURCE_IMAGE;
|
||||
else if (streq(optarg, "host"))
|
||||
arg_install_source = ARG_INSTALL_SOURCE_HOST;
|
||||
else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Unexpected parameter for --install-source=: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
arg_print_esp_path = true;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
arg_print_dollar_boot_path = true;
|
||||
break;
|
||||
|
||||
case ARG_PRINT_LOADER_PATH:
|
||||
arg_print_loader_path = true;
|
||||
break;
|
||||
|
||||
case ARG_PRINT_STUB_PATH:
|
||||
arg_print_stub_path = true;
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
arg_print_root_device++;
|
||||
break;
|
||||
|
||||
case ARG_NO_VARIABLES:
|
||||
arg_touch_variables = false;
|
||||
break;
|
||||
|
||||
case ARG_RANDOM_SEED:
|
||||
r = parse_boolean_argument("--random-seed=", optarg, &arg_install_random_seed);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_GRACEFUL:
|
||||
arg_graceful = true;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
arg_quiet = true;
|
||||
break;
|
||||
|
||||
case ARG_ENTRY_TOKEN:
|
||||
r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_MAKE_ENTRY_DIRECTORY:
|
||||
if (streq(optarg, "auto")) /* retained for backwards compatibility */
|
||||
arg_make_entry_directory = -1; /* yes if machine-id is permanent */
|
||||
else {
|
||||
r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_make_entry_directory = r;
|
||||
}
|
||||
break;
|
||||
|
||||
case ARG_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_ARCH_ALL:
|
||||
arg_arch_all = true;
|
||||
break;
|
||||
|
||||
case ARG_EFI_BOOT_OPTION_DESCRIPTION:
|
||||
if (isempty(optarg) || !(string_is_safe(optarg) && utf8_is_valid(optarg))) {
|
||||
_cleanup_free_ char *escaped = NULL;
|
||||
|
||||
escaped = cescape(optarg);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid --efi-boot-option-description=: %s", strna(escaped));
|
||||
}
|
||||
if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--efi-boot-option-description= too long: %zu > %zu",
|
||||
strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
|
||||
r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_DRY_RUN:
|
||||
arg_dry_run = true;
|
||||
break;
|
||||
|
||||
case ARG_SECURE_BOOT_AUTO_ENROLL:
|
||||
r = parse_boolean_argument("--secure-boot-auto-enroll=", optarg, &arg_secure_boot_auto_enroll);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_CERTIFICATE: {
|
||||
r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_certificate);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_PRIVATE_KEY: {
|
||||
r = free_and_strdup_warn(&arg_private_key, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_PRIVATE_KEY_SOURCE:
|
||||
r = parse_openssl_key_source_argument(
|
||||
optarg,
|
||||
&arg_private_key_source,
|
||||
&arg_private_key_source_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R, --print-loader-path, --print-stub-path cannot be combined.");
|
||||
|
||||
if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list",
|
||||
"install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Options --root= and --image= are not supported with verb %s.",
|
||||
argv[optind]);
|
||||
|
||||
if (arg_root && arg_image)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
|
||||
|
||||
if (arg_install_source != ARG_INSTALL_SOURCE_AUTO && !arg_root && !arg_image)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=.");
|
||||
|
||||
if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry is only supported with --unlink or --cleanup");
|
||||
|
||||
if (arg_secure_boot_auto_enroll && !arg_certificate)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Secure boot auto-enrollment requested but no certificate provided");
|
||||
|
||||
if (arg_secure_boot_auto_enroll && !arg_private_key)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Secure boot auto-enrollment requested but no private key provided");
|
||||
|
||||
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
|
||||
if (r > 0) {
|
||||
arg_varlink = true;
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int bootctl_main(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, help },
|
||||
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
|
||||
{ "install", VERB_ANY, 1, 0, verb_install },
|
||||
{ "update", VERB_ANY, 1, 0, verb_install },
|
||||
{ "remove", VERB_ANY, 1, 0, verb_remove },
|
||||
{ "is-installed", VERB_ANY, 1, 0, verb_is_installed },
|
||||
{ "kernel-identify", 2, 2, 0, verb_kernel_identify },
|
||||
{ "kernel-inspect", 2, 2, 0, verb_kernel_inspect },
|
||||
{ "list", VERB_ANY, 1, 0, verb_list },
|
||||
{ "unlink", 2, 2, 0, verb_unlink },
|
||||
{ "cleanup", VERB_ANY, 1, 0, verb_list },
|
||||
{ "set-default", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-oneshot", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-timeout", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-timeout-oneshot", 2, 2, 0, verb_set_efivar },
|
||||
{ "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 },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
||||
_cleanup_(umount_and_freep) char *mounted_dir = NULL;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
/* If we run in a container, automatically turn off EFI file system access */
|
||||
if (detect_container() > 0)
|
||||
arg_touch_variables = false;
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (arg_varlink) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
|
||||
|
||||
/* Invocation as Varlink service */
|
||||
|
||||
r = sd_varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate Varlink server: %m");
|
||||
|
||||
r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_BootControl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add Varlink interface: %m");
|
||||
|
||||
r = sd_varlink_server_bind_method_many(
|
||||
varlink_server,
|
||||
"io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries,
|
||||
"io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware,
|
||||
"io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind Varlink methods: %m");
|
||||
|
||||
r = sd_varlink_server_loop_auto(varlink_server);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run Varlink event loop: %m");
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (arg_print_root_device > 0) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
dev_t devno;
|
||||
|
||||
r = blockdev_get_root(LOG_ERR, &devno);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
log_error("Root file system not backed by a (single) whole block device.");
|
||||
return 80; /* some recognizable error code */
|
||||
}
|
||||
|
||||
if (arg_print_root_device > 1) {
|
||||
r = block_get_whole_disk(devno, &devno);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Unable to find whole block device for root block device, ignoring: %m");
|
||||
}
|
||||
|
||||
r = device_path_make_canonical(S_IFBLK, devno, &path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r,
|
||||
"Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
|
||||
DEVNUM_FORMAT_VAL(devno));
|
||||
|
||||
puts(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg_print_loader_path || arg_print_stub_path)
|
||||
return print_loader_or_stub_path();
|
||||
|
||||
/* Open up and mount the image */
|
||||
if (arg_image) {
|
||||
assert(!arg_root);
|
||||
|
||||
r = mount_image_privately_interactively(
|
||||
arg_image,
|
||||
arg_image_policy,
|
||||
DISSECT_IMAGE_GENERIC_ROOT |
|
||||
DISSECT_IMAGE_RELAX_VAR_CHECK |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
|
||||
&mounted_dir,
|
||||
/* ret_dir_fd= */ NULL,
|
||||
&loop_device);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_root = strdup(mounted_dir);
|
||||
if (!arg_root)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
return bootctl_main(argc, argv);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
|
||||
@@ -0,0 +1,54 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-id128.h"
|
||||
#include "sd-json.h"
|
||||
|
||||
#include "boot-entry.h"
|
||||
#include "image-policy.h"
|
||||
#include "openssl-util.h"
|
||||
#include "pager.h"
|
||||
|
||||
typedef enum InstallSource {
|
||||
ARG_INSTALL_SOURCE_IMAGE,
|
||||
ARG_INSTALL_SOURCE_HOST,
|
||||
ARG_INSTALL_SOURCE_AUTO,
|
||||
} InstallSource;
|
||||
|
||||
extern char *arg_esp_path;
|
||||
extern char *arg_xbootldr_path;
|
||||
extern bool arg_print_esp_path;
|
||||
extern bool arg_print_dollar_boot_path;
|
||||
extern unsigned arg_print_root_device;
|
||||
extern bool arg_touch_variables;
|
||||
extern bool arg_install_random_seed;
|
||||
extern PagerFlags arg_pager_flags;
|
||||
extern bool arg_graceful;
|
||||
extern bool arg_quiet;
|
||||
extern int arg_make_entry_directory; /* tri-state: < 0 for automatic logic */
|
||||
extern sd_id128_t arg_machine_id;
|
||||
extern char *arg_install_layout;
|
||||
extern BootEntryTokenType arg_entry_token_type;
|
||||
extern char *arg_entry_token;
|
||||
extern sd_json_format_flags_t arg_json_format_flags;
|
||||
extern bool arg_arch_all;
|
||||
extern char *arg_root;
|
||||
extern char *arg_image;
|
||||
extern InstallSource arg_install_source;
|
||||
extern char *arg_efi_boot_option_description;
|
||||
extern bool arg_dry_run;
|
||||
extern ImagePolicy *arg_image_policy;
|
||||
extern bool arg_varlink;
|
||||
extern bool arg_secure_boot_auto_enroll;
|
||||
extern char *arg_certificate;
|
||||
extern char *arg_private_key;
|
||||
extern KeySourceType arg_private_key_source_type;
|
||||
extern char *arg_private_key_source;
|
||||
|
||||
static inline const char* arg_dollar_boot_path(void) {
|
||||
/* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
|
||||
return arg_xbootldr_path ?: arg_esp_path;
|
||||
}
|
||||
|
||||
int acquire_esp(int unprivileged_mode, bool graceful, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
|
||||
int acquire_xbootldr(int unprivileged_mode, sd_id128_t *ret_uuid, dev_t *ret_devid);
|
||||
@@ -0,0 +1,26 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
bootctl_sources = files(
|
||||
'bootctl-install.c',
|
||||
'bootctl-random-seed.c',
|
||||
'bootctl-reboot-to-firmware.c',
|
||||
'bootctl-set-efivar.c',
|
||||
'bootctl-status.c',
|
||||
'bootctl-systemd-efi-options.c',
|
||||
'bootctl-uki.c',
|
||||
'bootctl-util.c',
|
||||
'bootctl.c',
|
||||
)
|
||||
|
||||
executables += [
|
||||
executable_template + {
|
||||
'name' : 'bootctl',
|
||||
'public' : true,
|
||||
'conditions' : [
|
||||
'HAVE_BLKID',
|
||||
],
|
||||
'sources' : bootctl_sources,
|
||||
'link_with' : boot_link_with,
|
||||
'dependencies' : [libblkid, libopenssl],
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user