From 80a2381d5c156cffedf41a3e7b95f7a00045a0fd Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Jun 2022 00:01:02 +0100 Subject: [PATCH 1/3] bootctl: add --root and --image Operate on image/directory, and also take files to install from it --- man/bootctl.xml | 21 +++ shell-completion/bash/bootctl | 6 +- shell-completion/zsh/_bootctl | 2 + src/boot/bless-boot.c | 4 +- src/boot/bootctl.c | 164 +++++++++++++++++++----- src/fuzz/fuzz-bootspec.c | 2 +- src/shared/bootspec.c | 12 +- src/shared/bootspec.h | 2 +- src/shared/find-esp.c | 133 +++++++++++++------ src/shared/find-esp.h | 4 +- src/systemctl/systemctl-start-special.c | 2 +- 11 files changed, 265 insertions(+), 87 deletions(-) diff --git a/man/bootctl.xml b/man/bootctl.xml index 8c88238383..839fa79efd 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -237,6 +237,27 @@ Loader partition to /boot/, if possible. + + + Takes a directory path as an argument. All + paths will be prefixed with the given alternate + root path, including config search + paths. + + + + + + Takes a path to a disk image file or block device node. If specified all operations + are applied to file system in the indicated disk image. This is similar to + but operates on file systems stored in disk images or block devices. The disk image should either + contain just a file system or a set of file systems within a GPT partition table, following the + Discoverable Partitions + Specification. For further information on supported disk images, see + systemd-nspawn1's + switch of the same name. + + diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl index 9214af5350..fd71cffe3f 100644 --- a/shell-completion/bash/bootctl +++ b/shell-completion/bash/bootctl @@ -32,7 +32,7 @@ _bootctl() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local -A OPTS=( [STANDALONE]='-h --help -p --print-esp-path -x --print-boot-path --version --no-variables --no-pager --graceful' - [ARG]='--esp-path --boot-path --make-machine-id-directory' + [ARG]='--esp-path --boot-path --make-machine-id-directory --root --image' ) if __contains_word "$prev" ${OPTS[ARG]}; then @@ -48,6 +48,10 @@ _bootctl() { --make-machine-id-directory) comps="yes no auto" ;; + --image|--root) + compopt -o nospace + comps=$( compgen -A file -- "$cur" ) + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index eef48d71d9..fbd62bc0fd 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -73,4 +73,6 @@ _arguments \ '--no-variables[Do not touch EFI variables]' \ '--no-pager[Do not pipe output into a pager]' \ '--graceful[Do not fail when locating ESP or writing fails]' \ + '--root=[Operate under the specified directory]:PATH' \ + '--image=[Operate on the specified image]:PATH' \ '*::bootctl command:_bootctl_commands' diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c index d9c901d73b..315a1a37ed 100644 --- a/src/boot/bless-boot.c +++ b/src/boot/bless-boot.c @@ -108,11 +108,11 @@ static int acquire_path(void) { if (!strv_isempty(arg_path)) return 0; - r = find_esp_and_warn(NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid); if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */ return r; - r = find_xbootldr_and_warn(NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index e50f96cb2f..6cb0f42539 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -15,9 +15,11 @@ #include "alloc-util.h" #include "blkid-util.h" #include "bootspec.h" +#include "chase-symlinks.h" #include "copy.h" #include "devnum-util.h" #include "dirent-util.h" +#include "dissect-image.h" #include "efi-api.h" #include "efi-loader.h" #include "efivars.h" @@ -31,6 +33,7 @@ #include "glyph-util.h" #include "main-func.h" #include "mkdir.h" +#include "mount-util.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -75,11 +78,15 @@ static enum { static char *arg_entry_token = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static bool arg_arch_all = false; +static char *arg_root = NULL; +static char *arg_image = 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 const char *arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ @@ -104,7 +111,7 @@ static int acquire_esp( * we simply eat up the error here, so that --list and --status work too, without noise about * this). */ - r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + 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, @@ -131,7 +138,7 @@ static int acquire_xbootldr( char *np; int r; - r = find_xbootldr_and_warn(arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); + 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); @@ -596,7 +603,6 @@ static int boot_config_load_and_select( const char *xbootldr_path, dev_t xbootldr_devid) { - _cleanup_strv_free_ char **efi_entries = NULL; int r; /* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would @@ -607,15 +613,19 @@ static int boot_config_load_and_select( if (r < 0) return r; - 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_config_augment_from_loader(config, efi_entries, /* only_auto= */ false); + if (!arg_root) { + _cleanup_strv_free_ char **efi_entries = NULL; - return boot_config_select_special_entries(config); + 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_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( @@ -833,48 +843,74 @@ static int create_subdirs(const char *root, const char * const *subdirs) { } static int copy_one_file(const char *esp_path, const char *name, bool force) { + _cleanup_free_ char *source_path = NULL, *dest_path = NULL, *p = NULL, *q = NULL; const char *e; - char *p, *q, *dest_name, *s; - int r; + char *dest_name, *s; + int r, ret; dest_name = strdupa_safe(name); s = endswith_no_case(dest_name, ".signed"); if (s) *s = 0; - p = strjoina(BOOTLIBDIR "/", name); - q = strjoina(esp_path, "/EFI/systemd/", dest_name); - r = copy_file_with_version_check(p, q, force); + p = path_join(BOOTLIBDIR, name); + if (!p) + return log_oom(); + + r = chase_symlinks(p, arg_root, CHASE_PREFIX_ROOT, &source_path, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + p, + arg_root ? " under directory " : "", + arg_root ? arg_root : ""); + + q = path_join("/EFI/systemd/", dest_name); + if (!q) + return log_oom(); + + r = chase_symlinks(q, esp_path, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &dest_path, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path); + + /* Note that if this fails we do the second copy anyway, but return this error code, + * so we stash it away in a separate variable. */ + ret = copy_file_with_version_check(source_path, dest_path, force); e = startswith(dest_name, "systemd-boot"); if (e) { - int k; + _cleanup_free_ char *default_dest_path = NULL; char *v; /* Create the EFI default boot loader name (specified for removable devices) */ - v = strjoina(esp_path, "/EFI/BOOT/BOOT", e); + v = strjoina("/EFI/BOOT/BOOT", e); ascii_strupper(strrchr(v, '/') + 1); - k = copy_file_with_version_check(p, v, force); - if (k < 0 && r == 0) - r = k; + r = chase_symlinks(v, esp_path, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &default_dest_path, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path); + + r = copy_file_with_version_check(source_path, default_dest_path, force); + if (r < 0 && ret == 0) + ret = r; } - return r; + return ret; } static int install_binaries(const char *esp_path, const char *arch, bool force) { _cleanup_closedir_ DIR *d = NULL; - int r = 0; + _cleanup_free_ char *path = NULL; + int r; - d = opendir(BOOTLIBDIR); - if (!d) - return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); + r = chase_symlinks_and_opendir(BOOTLIBDIR, arg_root, CHASE_PREFIX_ROOT, &path, &d); + if (r < 0) + return log_error_errno(r, "Failed to open boot loader directory %s: %m", BOOTLIBDIR); const char *suffix = strjoina(arch, ".efi"); const char *suffix_signed = strjoina(arch, ".efi.signed"); - FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \""BOOTLIBDIR"\": %m")) { + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \"%s\": %m", path)) { int k; if (!endswith_no_case(de->d_name, suffix) && !endswith_no_case(de->d_name, suffix_signed)) @@ -1022,6 +1058,12 @@ static int install_variables(const char *esp_path, uint16_t slot; int r; + if (arg_root) { + log_info("Acting on %s, skipping EFI variable setup.", + arg_image ? "image" : "root directory"); + return 0; + } + if (!is_efi_boot()) { log_warning("Not booted with EFI, skipping EFI variable setup."); return 0; @@ -1178,7 +1220,7 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { uint16_t slot; int r; - if (!is_efi_boot()) + if (arg_root || !is_efi_boot()) return 0; r = find_slot(uuid, path, &slot); @@ -1350,6 +1392,8 @@ static int help(int argc, char *argv[], void *userdata) { " --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" " -p --print-esp-path Print path to the EFI System Partition\n" " -x --print-boot-path Print path to the $BOOT partition\n" " --no-variables Don't touch EFI variables\n" @@ -1380,6 +1424,8 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_ESP_PATH = 0x100, ARG_BOOT_PATH, + ARG_ROOT, + ARG_IMAGE, ARG_VERSION, ARG_NO_VARIABLES, ARG_NO_PAGER, @@ -1396,6 +1442,8 @@ static int parse_argv(int argc, char *argv[]) { { "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 }, { "print-esp-path", no_argument, NULL, 'p' }, { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ { "print-boot-path", no_argument, NULL, 'x' }, @@ -1439,6 +1487,18 @@ static int parse_argv(int argc, char *argv[]) { 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 'p': if (arg_print_dollar_boot_path) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1523,6 +1583,15 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list", + "install", "update", "remove", "is-installed", "random-seed")) + 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."); + return 1; } @@ -1608,7 +1677,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { pager_open(arg_pager_flags); - if (is_efi_boot()) { + if (!arg_root && is_efi_boot()) { static const struct { uint64_t flag; const char *name; @@ -1722,7 +1791,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { r = k; } - if (is_efi_boot()) { + if (!arg_root && is_efi_boot()) { k = status_variables(); if (k < 0) r = k; @@ -1846,6 +1915,12 @@ static int install_random_seed(const char *esp) { return 0; } + if (arg_root) { + log_warning("Acting on %s, skipping EFI variable setup.", + arg_image ? "image" : "root directory"); + return 0; + } + r = getenv_bool("SYSTEMD_WRITE_SYSTEM_TOKEN"); if (r < 0) { if (r != -ENXIO) @@ -2204,6 +2279,11 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target static 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."); @@ -2267,7 +2347,7 @@ static int verb_set_efivar(int argc, char *argv[], void *userdata) { static int verb_random_seed(int argc, char *argv[], void *userdata) { int r; - r = find_esp_and_warn(arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL); + 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) @@ -2375,6 +2455,9 @@ static int bootctl_main(int argc, char *argv[]) { } static int run(int argc, char *argv[]) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL; int r; log_parse_environment(); @@ -2388,6 +2471,25 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + /* Open up and mount the image */ + if (arg_image) { + assert(!arg_root); + + r = mount_image_privately_interactively( + arg_image, + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK, + &unlink_dir, + &loop_device, + &decrypted_image); + if (r < 0) + return r; + + arg_root = strdup(unlink_dir); + if (!arg_root) + return log_oom(); + } + return bootctl_main(argc, argv); } diff --git a/src/fuzz/fuzz-bootspec.c b/src/fuzz/fuzz-bootspec.c index c26cc94db9..c08f76c14a 100644 --- a/src/fuzz/fuzz-bootspec.c +++ b/src/fuzz/fuzz-bootspec.c @@ -104,7 +104,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(boot_config_finalize(&config) >= 0); - (void) boot_config_select_special_entries(&config); + (void) boot_config_select_special_entries(&config, /* skip_efivars= */ false); _cleanup_close_ int orig_stdout_fd = -1; if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0) { diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 2c0ec6c272..1a47fff167 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -840,12 +840,12 @@ static int boot_entries_select_selected(const BootConfig *config) { return boot_config_find(config, config->entry_selected); } -static int boot_load_efi_entry_pointers(BootConfig *config) { +static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) { int r; assert(config); - if (!is_efi_boot()) + if (skip_efivars || !is_efi_boot()) return 0; /* Loads the three "pointers" to boot loader entries from their EFI variables */ @@ -871,12 +871,12 @@ static int boot_load_efi_entry_pointers(BootConfig *config) { return 1; } -int boot_config_select_special_entries(BootConfig *config) { +int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) { int r; assert(config); - r = boot_load_efi_entry_pointers(config); + r = boot_load_efi_entry_pointers(config, skip_efivars); if (r < 0) return r; @@ -967,11 +967,11 @@ int boot_config_load_auto( "Failed to determine whether /run/boot-loader-entries/ exists: %m"); } - r = find_esp_and_warn(override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn(NULL, override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid); if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */ return r; - r = find_xbootldr_and_warn(override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn(NULL, override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */ diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index ff54cc2e84..36a3489e87 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -100,7 +100,7 @@ int boot_config_load(BootConfig *config, const char *esp_path, const char *xboot int boot_config_load_auto(BootConfig *config, const char *override_esp_path, const char *override_xbootldr_path); int boot_config_augment_from_loader(BootConfig *config, char **list, bool only_auto); -int boot_config_select_special_entries(BootConfig *config); +int boot_config_select_special_entries(BootConfig *config, bool skip_efivars); static inline const char* boot_entry_title(const BootEntry *entry) { assert(entry); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index ea27d7911a..87ac9d167e 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "blkid-util.h" +#include "chase-symlinks.h" #include "device-util.h" #include "devnum-util.h" #include "env-util.h" @@ -20,6 +21,12 @@ #include "string-util.h" #include "virt.h" +typedef enum VerifyESPFlags { + VERIFY_ESP_SEARCHING = 1 << 0, /* Downgrade various "not found" logs to debug level */ + VERIFY_ESP_UNPRIVILEGED_MODE = 1 << 1, /* Call into udev rather than blkid */ + VERIFY_ESP_RELAX_CHECKS = 1 << 2, /* Do not validate ESP partition */ +} VerifyESPFlags; + static int verify_esp_blkid( dev_t devid, bool searching, @@ -298,15 +305,15 @@ static int verify_fsroot_dir( static int verify_esp( const char *p, - bool searching, - bool unprivileged_mode, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, - dev_t *ret_devid) { + dev_t *ret_devid, + VerifyESPFlags flags) { - bool relax_checks; + bool relax_checks, searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), + unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); dev_t devid; int r; @@ -319,7 +326,7 @@ static int verify_esp( * -EACESS → if 'unprivileged_mode' is set, and we have trouble accessing the thing */ - relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0; + relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0 || FLAGS_SET(flags, VERIFY_ESP_RELAX_CHECKS); /* Non-root user can only check the status, so if an error occurred in the following, it does not cause any * issues. Let's also, silence the error messages. */ @@ -381,6 +388,7 @@ finish: } int find_esp_and_warn( + const char *root, const char *path, bool unprivileged_mode, char **ret_path, @@ -390,6 +398,9 @@ int find_esp_and_warn( sd_id128_t *ret_uuid, dev_t *ret_devid) { + VerifyESPFlags flags = (unprivileged_mode ? VERIFY_ESP_UNPRIVILEGED_MODE : 0) | + (root ? VERIFY_ESP_RELAX_CHECKS : 0); + _cleanup_free_ char *p = NULL; int r; /* This logs about all errors except: @@ -399,7 +410,15 @@ int find_esp_and_warn( */ if (path) { - r = verify_esp(path, /* searching= */ false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags); if (r < 0) return r; @@ -410,19 +429,27 @@ int find_esp_and_warn( if (path) { struct stat st; - if (!path_is_valid(path) || !path_is_absolute(path)) + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + if (!path_is_valid(p) || !path_is_absolute(p)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s", - path); + p); /* Note: when the user explicitly configured things with an env var we won't validate the * path beyond checking it refers to a directory. After all we want this to be useful for * testing. */ - if (stat(path, &st) < 0) - return log_error_errno(errno, "Failed to stat '%s': %m", path); + if (stat(p, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", p); if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", path); + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", p); if (ret_part) *ret_part = 0; @@ -438,29 +465,33 @@ int find_esp_and_warn( goto found; } - FOREACH_STRING(_path, "/efi", "/boot", "/boot/efi") { - path = _path; + FOREACH_STRING(dir, "/efi", "/boot", "/boot/efi") { + r = chase_symlinks(dir, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + dir, + root ? " under directory " : "", + root ?: ""); - r = verify_esp(path, /* searching= */ true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, + flags | VERIFY_ESP_SEARCHING); if (r >= 0) goto found; if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ return r; + + p = mfree(p); } /* No logging here */ return -ENOKEY; found: - if (ret_path) { - char *c; - - c = strdup(path); - if (!c) - return log_oom(); - - *ret_path = c; - } + if (ret_path) + *ret_path = TAKE_PTR(p); return 0; } @@ -662,18 +693,28 @@ finish: } int find_xbootldr_and_warn( + const char *root, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid) { + _cleanup_free_ char *p = NULL; int r; /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */ if (path) { - r = verify_xbootldr(path, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid); + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + r = verify_xbootldr(p, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid); if (r < 0) return r; @@ -684,15 +725,23 @@ int find_xbootldr_and_warn( if (path) { struct stat st; - if (!path_is_valid(path) || !path_is_absolute(path)) + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + if (!path_is_valid(p) || !path_is_absolute(p)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s", - path); + p); - if (stat(path, &st) < 0) - return log_error_errno(errno, "Failed to stat '%s': %m", path); + if (stat(p, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", p); if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", path); + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", p); if (ret_uuid) *ret_uuid = SD_ID128_NULL; @@ -702,26 +751,26 @@ int find_xbootldr_and_warn( goto found; } - r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid, ret_devid); - if (r >= 0) { - path = "/boot"; + r = chase_symlinks("/boot", root, CHASE_PREFIX_ROOT, &p, NULL); + if (r == -ENOENT) + return -ENOKEY; + if (r < 0) + return log_error_errno(r, + "Failed to resolve path /boot%s%s: %m", + root ? " under directory " : "", + root ?: ""); + + r = verify_xbootldr(p, true, unprivileged_mode, ret_uuid, ret_devid); + if (r >= 0) goto found; - } if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ return r; return -ENOKEY; found: - if (ret_path) { - char *c; - - c = strdup(path); - if (!c) - return log_oom(); - - *ret_path = c; - } + if (ret_path) + *ret_path = TAKE_PTR(p); return 0; } diff --git a/src/shared/find-esp.h b/src/shared/find-esp.h index e4e65ac3e2..78d7f4551e 100644 --- a/src/shared/find-esp.h +++ b/src/shared/find-esp.h @@ -8,5 +8,5 @@ #include "sd-id128.h" -int find_esp_and_warn(const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_xbootldr_and_warn(const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn(const char *root, const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_xbootldr_and_warn(const char *root, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index cfd0078964..9363764cd7 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -43,7 +43,7 @@ static int load_kexec_kernel(void) { if (r < 0) return r; - r = boot_config_select_special_entries(&config); + r = boot_config_select_special_entries(&config, /* skip_efivars= */ false); if (r < 0) return r; From 02d06ba18023efd21dfdcdc4545a0c99d01dd639 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Jun 2022 00:21:41 +0100 Subject: [PATCH 2/3] bootctl: add --install-source=auto|image|host When using --root=/--image= the binaries to install/update will be picked from the directory/image. Add an option to let the caller choose. By default (auto) the image is tried first, and if nothing is found then the host. The other options allow to strictly try the image or host and ignore the other. --- man/bootctl.xml | 10 ++++++++ shell-completion/bash/bootctl | 5 +++- shell-completion/zsh/_bootctl | 1 + src/boot/bootctl.c | 43 +++++++++++++++++++++++++++++++---- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/man/bootctl.xml b/man/bootctl.xml index 839fa79efd..1664f68157 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -258,6 +258,16 @@ switch of the same name. + + + When installing binaries with or + , selects where to source them from. Takes one of auto + (the default), image or host. With auto + binaries will be picked from the specified directory or image, and if not found they will be picked + from the host. With image or host no fallback search will be + performed if the binaries are not found in the selected source. + + diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl index fd71cffe3f..0b7cef7871 100644 --- a/shell-completion/bash/bootctl +++ b/shell-completion/bash/bootctl @@ -32,7 +32,7 @@ _bootctl() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local -A OPTS=( [STANDALONE]='-h --help -p --print-esp-path -x --print-boot-path --version --no-variables --no-pager --graceful' - [ARG]='--esp-path --boot-path --make-machine-id-directory --root --image' + [ARG]='--esp-path --boot-path --make-machine-id-directory --root --image --install-source' ) if __contains_word "$prev" ${OPTS[ARG]}; then @@ -52,6 +52,9 @@ _bootctl() { compopt -o nospace comps=$( compgen -A file -- "$cur" ) ;; + --install-source) + comps="image host auto" + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index fbd62bc0fd..8634e8b9bc 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -75,4 +75,5 @@ _arguments \ '--graceful[Do not fail when locating ESP or writing fails]' \ '--root=[Operate under the specified directory]:PATH' \ '--image=[Operate on the specified image]:PATH' \ + '--install-source[Where to pick files when using --root=/--image=]:options:(image host auto)' \ '*::bootctl command:_bootctl_commands' diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 6cb0f42539..c09c305b3f 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -80,6 +80,11 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static bool arg_arch_all = false; static char *arg_root = NULL; static char *arg_image = NULL; +static enum { + ARG_INSTALL_SOURCE_IMAGE, + ARG_INSTALL_SOURCE_HOST, + ARG_INSTALL_SOURCE_AUTO, +} arg_install_source = ARG_INSTALL_SOURCE_AUTO; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -843,6 +848,7 @@ static int create_subdirs(const char *root, const char * const *subdirs) { } static int copy_one_file(const char *esp_path, const char *name, bool force) { + char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; _cleanup_free_ char *source_path = NULL, *dest_path = NULL, *p = NULL, *q = NULL; const char *e; char *dest_name, *s; @@ -857,13 +863,16 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { if (!p) return log_oom(); - r = chase_symlinks(p, arg_root, CHASE_PREFIX_ROOT, &source_path, NULL); + r = chase_symlinks(p, root, CHASE_PREFIX_ROOT, &source_path, NULL); + /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */ + if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO) + r = chase_symlinks(p, NULL, CHASE_PREFIX_ROOT, &source_path, NULL); if (r < 0) return log_error_errno(r, "Failed to resolve path %s%s%s: %m", p, - arg_root ? " under directory " : "", - arg_root ? arg_root : ""); + root ? " under directory " : "", + root ?: ""); q = path_join("/EFI/systemd/", dest_name); if (!q) @@ -899,13 +908,17 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { } static int install_binaries(const char *esp_path, const char *arch, bool force) { + char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *path = NULL; int r; - r = chase_symlinks_and_opendir(BOOTLIBDIR, arg_root, CHASE_PREFIX_ROOT, &path, &d); + r = chase_symlinks_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT, &path, &d); + /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */ + if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO) + r = chase_symlinks_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT, &path, &d); if (r < 0) - return log_error_errno(r, "Failed to open boot loader directory %s: %m", BOOTLIBDIR); + return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", root ?: "", BOOTLIBDIR); const char *suffix = strjoina(arch, ".efi"); const char *suffix_signed = strjoina(arch, ".efi.signed"); @@ -1394,6 +1407,8 @@ static int help(int argc, char *argv[], void *userdata) { " --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" + " --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\n" " -x --print-boot-path Print path to the $BOOT partition\n" " --no-variables Don't touch EFI variables\n" @@ -1426,6 +1441,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_BOOT_PATH, ARG_ROOT, ARG_IMAGE, + ARG_INSTALL_SOURCE, ARG_VERSION, ARG_NO_VARIABLES, ARG_NO_PAGER, @@ -1444,6 +1460,7 @@ static int parse_argv(int argc, char *argv[]) { { "boot-path", required_argument, NULL, ARG_BOOT_PATH }, { "root", required_argument, NULL, ARG_ROOT }, { "image", required_argument, NULL, ARG_IMAGE }, + { "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' }, @@ -1499,6 +1516,19 @@ static int parse_argv(int argc, char *argv[]) { 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': if (arg_print_dollar_boot_path) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1592,6 +1622,9 @@ static int parse_argv(int argc, char *argv[]) { 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=."); + return 1; } From d5bf74f9e23f1c0b144a8665b18d2e102e5b56c8 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Jun 2022 00:22:46 +0100 Subject: [PATCH 3/3] Update TODO --- TODO | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index 5aeaaddb3e..ebd887d714 100644 --- a/TODO +++ b/TODO @@ -751,8 +751,8 @@ Features: mapping to $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such * All tools that support --root= should also learn --image= so that they can - operate on disk images directly. Specifically: bootctl, systemctl, - coredumpctl. (Already done: systemd-nspawn, systemd-firstboot, + operate on disk images directly. Specifically: systemctl, coredumpctl. + (Already done: bootctl, systemd-nspawn, systemd-firstboot, systemd-repart, systemd-tmpfiles, systemd-sysusers, journalctl) * seccomp: by default mask x32 ABI system wide on x86-64. it's on its way out