diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index ddbcac9200..482189f989 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -45,12 +45,20 @@ #include "verbs.h" #include "virt.h" -static char *arg_path = NULL; -static bool arg_print_path = false; +static char *arg_esp_path = NULL; +static char *arg_xbootldr_path = NULL; +static bool arg_print_esp_path = false; +static bool arg_print_dollar_boot_path = false; static bool arg_touch_variables = true; static PagerFlags arg_pager_flags = 0; -STATIC_DESTRUCTOR_REGISTER(arg_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); + +static 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; +} static int acquire_esp( bool unprivileged_mode, @@ -62,24 +70,44 @@ static int acquire_esp( 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 --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). */ + /* 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_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid); + r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid); if (r == -ENOKEY) return log_error_errno(r, "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n" - "Alternatively, use --path= to specify path to mount point."); + "Alternatively, use --esp-path= to specify path to mount point."); if (r < 0) return r; - free_and_replace(arg_path, np); + free_and_replace(arg_esp_path, np); + log_debug("Using EFI System Partition at %s.", arg_esp_path); - log_debug("Using EFI System Partition at %s.", arg_path); + return 1; +} - return 0; +static int acquire_xbootldr(bool unprivileged_mode, sd_id128_t *ret_uuid) { + char *np; + int r; + + r = find_xbootldr_and_warn(arg_xbootldr_path, unprivileged_mode, &np, ret_uuid); + if (r == -ENOKEY) { + log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; + 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; } /* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ @@ -134,10 +162,13 @@ finish: } static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { - char *p; _cleanup_closedir_ DIR *d = NULL; struct dirent *de; - int r = 0, c = 0; + int c = 0, r; + char *p; + + assert(esp_path); + assert(path); p = strjoina(esp_path, "/", path); d = opendir(p); @@ -149,8 +180,8 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char } FOREACH_DIRENT(de, d, break) { - _cleanup_close_ int fd = -1; _cleanup_free_ char *v = NULL; + _cleanup_close_ int fd = -1; if (!endswith_no_case(de->d_name, ".efi")) continue; @@ -169,6 +200,7 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char printf(" File: %s/%s/%s (%s%s%s)\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name, ansi_highlight(), v, ansi_normal()); else printf(" File: %s/%s/%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name); + c++; } @@ -191,16 +223,16 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { printf("\n"); r = enumerate_binaries(esp_path, "EFI/systemd", NULL); + if (r < 0) + return r; if (r == 0) log_info("systemd-boot not installed in ESP."); - else if (r < 0) - return r; r = enumerate_binaries(esp_path, "EFI/BOOT", "boot"); + if (r < 0) + return r; if (r == 0) log_info("No default/fallback boot loader installed in ESP."); - else if (r < 0) - return r; printf("\n"); @@ -319,11 +351,11 @@ static int boot_entry_show(const BootEntry *e, bool show_as_default) { return 0; } -static int status_entries(const char *esp_path, sd_id128_t partition) { +static int status_entries(const char *esp_path, const char *xbootldr_path) { _cleanup_(boot_config_free) BootConfig config = {}; int r; - r = boot_entries_load_config(esp_path, NULL, &config); + r = boot_entries_load_config(esp_path, xbootldr_path, &config); if (r < 0) return r; @@ -469,20 +501,21 @@ static int mkdir_one(const char *prefix, const char *suffix) { return 0; } -static const char *efi_subdirs[] = { +static const char *const esp_subdirs[] = { "EFI", "EFI/systemd", "EFI/BOOT", "loader", - "loader/entries", + /* Note that "/loader/entries" is not listed here, since it should be placed in $BOOT, which might + * not necessarily be the ESP */ NULL }; -static int create_dirs(const char *esp_path) { - const char **i; +static int create_esp_subdirs(const char *esp_path) { + const char *const *i; int r; - STRV_FOREACH(i, efi_subdirs) { + STRV_FOREACH(i, esp_subdirs) { r = mkdir_one(esp_path, *i); if (r < 0) return r; @@ -521,17 +554,6 @@ static int install_binaries(const char *esp_path, bool force) { _cleanup_closedir_ DIR *d = NULL; int r = 0; - if (force) { - /* Don't create any of these directories when we are - * just updating. When we update we'll drop-in our - * files (unless there are newer ones already), but we - * won't create the directories for them in the first - * place. */ - r = create_dirs(esp_path); - if (r < 0) - return r; - } - d = opendir(BOOTLIBDIR); if (!d) return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); @@ -752,18 +774,36 @@ static int rmdir_one(const char *prefix, const char *suffix) { p = strjoina(prefix, "/", suffix); if (rmdir(p) < 0) { - if (!IN_SET(errno, ENOENT, ENOTEMPTY)) - return log_error_errno(errno, "Failed to remove \"%s\": %m", p); + bool ignore = IN_SET(errno, ENOENT, ENOTEMPTY); + + log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, errno, + "Failed to remove directory \"%s\": %m", p); + if (!ignore) + return -errno; } else log_info("Removed \"%s\".", p); return 0; } +static int remove_esp_subdirs(const char *esp_path) { + size_t i; + int r = 0; + + for (i = ELEMENTSOF(esp_subdirs)-1; i > 0; i--) { + int q; + + q = rmdir_one(esp_path, esp_subdirs[i-1]); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + static int remove_binaries(const char *esp_path) { char *p; int r, q; - unsigned i; p = strjoina(esp_path, "/EFI/systemd"); r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); @@ -772,15 +812,31 @@ static int remove_binaries(const char *esp_path) { if (q < 0 && r == 0) r = q; - for (i = ELEMENTSOF(efi_subdirs)-1; i > 0; i--) { - q = rmdir_one(esp_path, efi_subdirs[i-1]); - if (q < 0 && r == 0) - r = q; - } - return r; } +static int remove_loader_config(const char *esp_path) { + const char *p; + + assert(esp_path); + + p = strjoina(esp_path, "/loader/loader.conf"); + if (unlink(p) < 0) { + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to unlink file \"%s\": %m", p); + if (errno != ENOENT) + return -errno; + } else + log_info("Removed \"%s\".", p); + + return 0; +} + +static int remove_entries_directory(const char *dollar_boot_path) { + assert(dollar_boot_path); + + return rmdir_one(dollar_boot_path, "/loader/entries"); +} + static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { uint16_t slot; int r; @@ -803,7 +859,6 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { } static int install_loader_config(const char *esp_path) { - char machine_string[SD_ID128_STRING_MAX]; _cleanup_(unlink_and_freep) char *t = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -811,15 +866,14 @@ static int install_loader_config(const char *esp_path) { const char *p; int r, fd; + p = strjoina(esp_path, "/loader/loader.conf"); + if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ + return 0; + r = sd_id128_get_machine(&machine_id); if (r < 0) return log_error_errno(r, "Failed to get machine id: %m"); - p = strjoina(esp_path, "/loader/loader.conf"); - - if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ - return 0; - fd = open_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t); if (fd < 0) return log_error_errno(fd, "Failed to open \"%s\" for writing: %m", p); @@ -845,10 +899,15 @@ static int install_loader_config(const char *esp_path) { return log_error_errno(r, "Failed to move \"%s\" into place: %m", p); t = mfree(t); - return 1; } +static int install_entries_directory(const char *dollar_boot_path) { + assert(dollar_boot_path); + + return mkdir_one(dollar_boot_path, "/loader/entries"); +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -859,21 +918,23 @@ static int help(int argc, char *argv[], void *userdata) { printf("%s [COMMAND] [OPTIONS...]\n\n" "Install, update or remove the systemd-boot EFI boot manager.\n\n" - " -h --help Show this help\n" - " --version Print version\n" - " --path=PATH Path to the EFI System Partition (ESP)\n" - " -p --print-path Print path to the EFI partition\n" - " --no-variables Don't touch EFI variables\n" - " --no-pager Do not pipe output into a pager\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" + " -p --print-esp-path Print path to the EFI System Partition\n" + " --print-boot-path Print path to the $BOOT partition\n" + " --no-variables Don't touch EFI variables\n" + " --no-pager Do not pipe output into a pager\n" "\nBoot Loader Commands:\n" - " status Show status of installed systemd-boot and EFI variables\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" + " status Show status of installed systemd-boot and EFI variables\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" "\nBoot Loader Entries Commands:\n" - " list List boot loader entries\n" - " set-default ID Set default boot loader entry\n" - " set-oneshot ID Set default boot loader entry, for next boot only\n" + " list List boot loader entries\n" + " set-default ID Set default boot loader entry\n" + " set-oneshot ID Set default boot loader entry, for next boot only\n" "\nSee the %s for details.\n" , program_invocation_short_name , link); @@ -883,19 +944,25 @@ static int help(int argc, char *argv[], void *userdata) { static int parse_argv(int argc, char *argv[]) { enum { - ARG_PATH = 0x100, + ARG_ESP_PATH = 0x100, + ARG_BOOT_PATH, + ARG_PRINT_BOOT_PATH, ARG_VERSION, ARG_NO_VARIABLES, ARG_NO_PAGER, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "path", required_argument, NULL, ARG_PATH }, - { "print-path", no_argument, NULL, 'p' }, - { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "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 }, + { "print-esp-path", no_argument, NULL, 'p' }, + { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ + { "print-boot-path", no_argument, NULL, ARG_PRINT_BOOT_PATH }, + { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, {} }; @@ -914,14 +981,24 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); - case ARG_PATH: - r = free_and_strdup(&arg_path, optarg); + 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 'p': - arg_print_path = true; + arg_print_esp_path = true; + break; + + case ARG_PRINT_BOOT_PATH: + arg_print_dollar_boot_path = true; break; case ARG_NO_VARIABLES: @@ -951,23 +1028,33 @@ static void read_loader_efi_var(const char *name, char **var) { } static int verb_status(int argc, char *argv[], void *userdata) { - - sd_id128_t uuid = SD_ID128_NULL; + sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL; int r, k; - r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &uuid); - - if (arg_print_path) { + r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &esp_uuid); + 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: %m"); + return log_error_errno(r, "Failed to determine ESP location: %m"); if (r < 0) return r; - puts(arg_path); - return 0; + puts(arg_esp_path); } + r = acquire_xbootldr(geteuid() != 0, &xbootldr_uuid); + if (arg_print_dollar_boot_path) { + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR location: %m"); + if (r < 0) + return r; + + puts(arg_dollar_boot_path()); + } + + if (arg_print_esp_path || arg_print_dollar_boot_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 */ @@ -1038,8 +1125,8 @@ static int verb_status(int argc, char *argv[], void *userdata) { } else printf("System:\n Not booted with EFI\n\n"); - if (arg_path) { - k = status_binaries(arg_path, uuid); + if (arg_esp_path) { + k = status_binaries(arg_esp_path, esp_uuid); if (k < 0) r = k; } @@ -1050,8 +1137,8 @@ static int verb_status(int argc, char *argv[], void *userdata) { r = k; } - if (arg_path) { - k = status_entries(arg_path, uuid); + if (arg_esp_path || arg_xbootldr_path) { + k = status_entries(arg_esp_path, arg_xbootldr_path); if (k < 0) r = k; } @@ -1062,20 +1149,25 @@ static int verb_status(int argc, char *argv[], void *userdata) { static int verb_list(int argc, char *argv[], void *userdata) { _cleanup_(boot_config_free) BootConfig config = {}; _cleanup_free_ char **found_by_loader = NULL; - sd_id128_t uuid = SD_ID128_NULL; 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(geteuid() != 0, NULL, NULL, NULL, &uuid); + r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, NULL); 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: %m"); if (r < 0) return r; - r = boot_entries_load_config(arg_path, NULL, &config); + r = acquire_xbootldr(geteuid() != 0, NULL); + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); + if (r < 0) + return r; + + r = boot_entries_load_config(arg_esp_path, arg_xbootldr_path, &config); if (r < 0) return r; @@ -1115,24 +1207,25 @@ static int verb_list(int argc, char *argv[], void *userdata) { return 0; } -static int sync_esp(void) { - _cleanup_close_ int fd = -1; +static int sync_everything(void) { + int ret = 0, k; - if (!arg_path) - return 0; + if (arg_esp_path) { + k = syncfs_path(AT_FDCWD, arg_esp_path); + if (k < 0) + ret = log_error_errno(k, "Failed to synchronize the ESP '%s': %m", arg_esp_path); + } - fd = open(arg_path, O_CLOEXEC|O_DIRECTORY|O_RDONLY); - if (fd < 0) - return log_error_errno(errno, "Couldn't open ESP '%s' for synchronization: %m", arg_path); + if (arg_xbootldr_path) { + k = syncfs_path(AT_FDCWD, arg_xbootldr_path); + if (k < 0) + ret = log_error_errno(k, "Failed to synchronize $BOOT '%s': %m", arg_xbootldr_path); + } - if (syncfs(fd) < 0) - return log_error_errno(errno, "Failed to synchronize the ESP '%s': %m", arg_path); - - return 1; + return ret; } static int verb_install(int argc, char *argv[], void *userdata) { - sd_id128_t uuid = SD_ID128_NULL; uint64_t pstart = 0, psize = 0; uint32_t part = 0; @@ -1143,24 +1236,41 @@ static int verb_install(int argc, char *argv[], void *userdata) { if (r < 0) return r; + r = acquire_xbootldr(false, NULL); + if (r < 0) + return r; + install = streq(argv[0], "install"); RUN_WITH_UMASK(0002) { - r = install_binaries(arg_path, install); + if (install) { + /* Don't create any of these directories when we are just updating. When we update + * we'll drop-in our files (unless there are newer ones already), but we won't create + * the directories for them in the first place. */ + r = create_esp_subdirs(arg_esp_path); + if (r < 0) + return r; + } + + r = install_binaries(arg_esp_path, install); if (r < 0) return r; if (install) { - r = install_loader_config(arg_path); + r = install_loader_config(arg_esp_path); + if (r < 0) + return r; + + r = install_entries_directory(arg_dollar_boot_path()); if (r < 0) return r; } } - (void) sync_esp(); + (void) sync_everything(); if (arg_touch_variables) - r = install_variables(arg_path, + r = install_variables(arg_esp_path, part, pstart, psize, uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", install); @@ -1170,21 +1280,35 @@ static int verb_install(int argc, char *argv[], void *userdata) { static int verb_remove(int argc, char *argv[], void *userdata) { sd_id128_t uuid = SD_ID128_NULL; - int r; + int r, q; r = acquire_esp(false, NULL, NULL, NULL, &uuid); if (r < 0) return r; - r = remove_binaries(arg_path); + r = acquire_xbootldr(false, NULL); + if (r < 0) + return r; - (void) sync_esp(); + r = remove_binaries(arg_esp_path); + + q = remove_loader_config(arg_esp_path); + if (q < 0 && r >= 0) + r = q; + + q = remove_entries_directory(arg_dollar_boot_path()); + if (q < 0 && r >= 0) + r = q; + + q = remove_esp_subdirs(arg_esp_path); + if (q < 0 && r >= 0) + r = q; + + (void) sync_everything(); if (arg_touch_variables) { - int q; - q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); - if (q < 0 && r == 0) + if (q < 0 && r >= 0) r = q; }