From e5a8b4b593e7da1823f3258f7b4e7f2d370a7c3c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Jul 2021 15:16:52 +0200 Subject: bootctl: tweak "bootctl update" to be a NOP when boot loader is already current and --graceful is given Previously, the "bootctl update" logic would refrain from downrgading a boot loader, but if the boot loader that is installed already matched the version we could install we'd install it anyway, under the assumption this was effectively without effect. This behaviour was handy while developing boot loaders, since installing a modified boot loader didn't require a version bump. However, outside of the systems of boot loader developers I don't think this behaviour makes much sense: we should always emphasize doing minimal changes to the ESP, hence when an update is supposedly not necessary, then don't do it. Only update if it really makes sense, to minimize writes to the ESP. Updating the boot loader is a good thing after all, but doing so redundantly is not. Also, downgrade the message about this to LOG_NOTICE, given this shouldn't be a reason to log. Finally, exit cleanly in this cases (or if another boot loader is detected) --- man/bootctl.xml | 5 +++-- src/boot/bootctl.c | 49 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/man/bootctl.xml b/man/bootctl.xml index d05c3f34d0..a958cde7df 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -233,8 +233,9 @@ - Ignore failure when the EFI System Partition cannot be found, or when EFI variables - cannot be written. Currently only applies to random seed operations. + Ignore failure when the EFI System Partition cannot be found, when EFI variables + cannot be written, or a different or newer boot loader is already installed. Currently only applies + to random seed and update operations. diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index fa8c600321..5d126f4bea 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -65,6 +65,7 @@ static const char *arg_dollar_boot_path(void) { static int acquire_esp( bool unprivileged_mode, + bool graceful, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, @@ -80,10 +81,14 @@ static int acquire_esp( * this). */ r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid); - if (r == -ENOKEY) + if (r == -ENOKEY) { + if (graceful) + return log_info_errno(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; @@ -500,7 +505,7 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t if (r < 0) return r; if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE), "Source file \"%s\" does not carry version information!", from); @@ -508,12 +513,15 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t if (r < 0) return r; if (r == 0 || compare_product(a, b) != 0) - return log_notice_errno(SYNTHETIC_ERRNO(EEXIST), + return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE), "Skipping \"%s\", since it's owned by another boot loader.", to); - if (compare_version(a, b) < 0) - return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since a newer boot loader version exists already.", to); + r = compare_version(a, b); + if (r < 0) + return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since newer boot loader version in place already.", to); + else if (r == 0) + return log_info_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since same boot loader version in place already.", to); return 0; } @@ -665,6 +673,10 @@ static int install_binaries(const char *esp_path, bool force) { continue; k = copy_one_file(esp_path, de->d_name, force); + /* Don't propagate an error code if no update necessary, installed version already equal or + * newer version, or other boot loader in place. */ + if (arg_graceful && IN_SET(k, -ESTALE, -EREMOTE)) + continue; if (k < 0 && r == 0) r = k; } @@ -1243,7 +1255,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL; int r, k; - r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &esp_uuid); + r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, 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) */ @@ -1254,7 +1266,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { puts(arg_esp_path); } - r = acquire_xbootldr(geteuid() != 0, &xbootldr_uuid); + r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, &xbootldr_uuid); if (arg_print_dollar_boot_path) { if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR location: %m"); @@ -1402,13 +1414,13 @@ static int verb_list(int argc, char *argv[], void *userdata) { * 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, NULL); + r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, 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 = acquire_xbootldr(geteuid() != 0, NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, NULL); if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); if (r < 0) @@ -1600,21 +1612,24 @@ 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; - bool install; + bool install, graceful; int r; - r = acquire_esp(false, &part, &pstart, &psize, &uuid); + install = streq(argv[0], "install"); + graceful = !install && arg_graceful; /* support graceful mode for updates */ + + r = acquire_esp(/* unprivileged_mode= */ false, graceful, &part, &pstart, &psize, &uuid); + if (graceful && r == -ENOKEY) + return 0; /* If --graceful is specified and we can't find an ESP, handle this cleanly */ if (r < 0) return r; - r = acquire_xbootldr(false, NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL); if (r < 0) return r; settle_make_machine_id_directory(); - install = streq(argv[0], "install"); - RUN_WITH_UMASK(0002) { if (install) { /* Don't create any of these directories when we are just updating. When we update @@ -1663,11 +1678,11 @@ static int verb_remove(int argc, char *argv[], void *userdata) { sd_id128_t uuid = SD_ID128_NULL; int r, q; - r = acquire_esp(false, NULL, NULL, NULL, &uuid); + r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, &uuid); if (r < 0) return r; - r = acquire_xbootldr(false, NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL); if (r < 0) return r; @@ -1726,7 +1741,7 @@ static int verb_is_installed(int argc, char *argv[], void *userdata) { _cleanup_free_ char *p = NULL; int r; - r = acquire_esp(false, NULL, NULL, NULL, NULL); + r = acquire_esp(/* privileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, NULL); if (r < 0) return r; -- cgit v1.2.3 From 71c8bf28378958a5ab2348e9ec586fbe78c71dfd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Jul 2021 15:04:10 +0200 Subject: boot: optionally update sd-boot on boot Boot loaders are software like any other, and hence muse be updated in regular intervals. Let's add a simple (optional) service that updates sd-boot automatically from the host if it is found installed but out-of-date in the ESP. Note that traditional distros probably should invoke "bootctl update" directly from the package scripts whenver they update the sd-boot package. This new service is primarily intended for image-based update systems, i.e. where the rootfs or /usr are atomically updated in A/B style and where the current boot loader should be synced into the ESP from the currently booted image every now and then. It can also act as safety net if the packaging scripts in classic systems are't doing the bootctl update stuff themselves. Since updating boot loaders mit be a tiny bit risky (even though we try really hard to make them robust, by fsck'ing the ESP and mounting it only on demand, by doing updates mostly as single file updates and by fsync()ing heavily) this is an optional feature, i.e. subject to "systemctl enable". However, since it's the right thing to do I think, it's enabled by default via the preset logic. Note that the updating logic is implemented gracefully: i.e. it's a NOP if the boot loader is already new enough, or was never installed. --- presets/90-systemd.preset | 1 + units/systemd-boot-update.service | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 units/systemd-boot-update.service diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index d26087445c..8a1a08210c 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -22,6 +22,7 @@ enable systemd-resolved.service enable systemd-homed.service enable systemd-userdbd.socket enable systemd-pstore.service +enable systemd-boot-update.service disable console-getty.service disable debug-shell.service diff --git a/units/systemd-boot-update.service b/units/systemd-boot-update.service new file mode 100644 index 0000000000..61ff12762a --- /dev/null +++ b/units/systemd-boot-update.service @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Automatic Boot Loader Update +Documentation=man:bootctl(1) +DefaultDependencies=no +Conflicts=shutdown.target +After=local-fs.target +Before=sysinit.target shutdown.target systemd-update-done.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=bootctl --no-variables --graceful update + +[Install] +WantedBy=sysinit.target -- cgit v1.2.3 From adc0733c2c5be8a15d322b7f6348e76ad625ea93 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 5 Jul 2021 14:37:04 +0200 Subject: update TODO --- TODO | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TODO b/TODO index 17dff7331d..16a91fa023 100644 --- a/TODO +++ b/TODO @@ -1019,10 +1019,6 @@ Features: * bootctl,sd-boot: actually honour the "architecture" key -* sd-boot: add service that automatically runs "bootctl update" on every boot, - in a graceful way, so that updated /usr trees automatically propagate into - updated boot loaders on reboot. - * bootctl: - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host -- cgit v1.2.3