From 42e18088904d35f39eba5900b0b4e04965879f78 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jun 2018 17:16:18 +0200 Subject: [PATCH 01/15] update TODO --- TODO | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index 07d6f3500e..e657ced55b 100644 --- a/TODO +++ b/TODO @@ -52,6 +52,23 @@ Features: show state of these flags, and optionally trigger such a factory reset on next boot by setting the flag. +* sd-boot: search drop-ins in $BOOT, too + +* sd-boot: add "oneshot boot timeout" variable support + +* sd-boot: automatically load EFI modules from some drop-in dir, so that people + can add in file system drivers and such + +* esp generator: also mount $BOOT if found + +* sd-boot: optionally, show boot menu when previous default boot item has + non-zero "tries done" count + +* logind: add "boot into bootmenu" API, and possibly even "boot into windows" + and "boot into macos". + +* bootspec.c: also enumerate EFI unified kernel images. + * maybe extend .path units to expose fanotify() per-mount change events * Add a "systemctl list-units --by-slice" mode or so, which rearranges the @@ -175,9 +192,6 @@ Features: * calenderspec: add support for week numbers and day numbers within a year. This would allow us to define "bi-weekly" triggers safely. -* add bpf-based implementation of devices cgroup controller logic for compat - with cgroupsv2 as supported by newest kernel - * sd-bus: add vtable flag, that may be used to request client creds implicitly and asynchronously before dispatching the operation From 329d20db3cb02d789473b8f7e4a59526fcbf5728 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jun 2018 12:52:28 +0200 Subject: [PATCH 02/15] units: add generic boot-complete.target --- units/boot-complete.target | 14 ++++++++++++++ units/meson.build | 1 + 2 files changed, 15 insertions(+) create mode 100644 units/boot-complete.target diff --git a/units/boot-complete.target b/units/boot-complete.target new file mode 100644 index 0000000000..f0b9e57e7c --- /dev/null +++ b/units/boot-complete.target @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# 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=Boot Completion Check +Documentation=man:systemd.special(7) +Requires=sysinit.target +After=sysinit.target diff --git a/units/meson.build b/units/meson.build index 3cc86b3e92..70eabe5227 100644 --- a/units/meson.build +++ b/units/meson.build @@ -3,6 +3,7 @@ units = [ ['basic.target', ''], ['bluetooth.target', ''], + ['boot-complete.target', ''], ['cryptsetup-pre.target', 'HAVE_LIBCRYPTSETUP'], ['cryptsetup.target', 'HAVE_LIBCRYPTSETUP', 'sysinit.target.wants/'], From 82ea38258c0f4964c2f3ad3691c6e4554c4f0bb0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 17:21:34 +0200 Subject: [PATCH 03/15] man: document new "boot-complete.target" unit --- man/systemd.special.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 0d25e40d03..fd5639ba03 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -29,6 +29,7 @@ cryptsetup-pre.target, cryptsetup.target, ctrl-alt-del.target, + boot-complete.target, default.target, emergency.target, exit.target, @@ -144,7 +145,28 @@ bootup7 for details on the targets involved. + + + + boot-complete.target + + This target is intended as generic synchronization point for services that shall determine or act on + whether the boot process completed successfully. Order units that are required to succeed for a boot process + to be considered successful before this unit, and add a Requires= dependency from the + target unit to them. Order units that shall only run when the boot process is considered successful after the + target unit and pull in the target from it, also with Requires=. Note that by default this + target unit is not part of the initial boot transaction, but is supposed to be pulled in only if required by + units that want to run only on successful boots. + See + systemd-boot-check-no-failures.service8 + for a service that implements a generic system health check and orders itself before + boot-complete.target. + + See + systemd-bless-boot.service8 + for a service that propagates boot success information to the boot loader, and orders itself after + boot-complete.target. From 36695e880aff9c1525ed4efd5e42df1c62932a80 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 17:24:09 +0200 Subject: [PATCH 04/15] add new systemd-bless-boot.service that marks boots as successful This is the counterpiece to the boot counting implemented in systemd-boot: if a boot is detected as successful we mark drop the counter again from the booted snippet or kernel image. --- meson.build | 9 + src/basic/fs-util.c | 28 ++ src/basic/fs-util.h | 1 + src/boot/bless-boot.c | 476 ++++++++++++++++++++++++++++ units/meson.build | 1 + units/systemd-bless-boot.service.in | 22 ++ 6 files changed, 537 insertions(+) create mode 100644 src/boot/bless-boot.c create mode 100644 units/systemd-bless-boot.service.in diff --git a/meson.build b/meson.build index a47d7f9370..35d0968b7e 100644 --- a/meson.build +++ b/meson.build @@ -1796,6 +1796,15 @@ if conf.get('ENABLE_EFI') == 1 and conf.get('HAVE_BLKID') == 1 install_rpath : rootlibexecdir, install : true) public_programs += exe + + executable('systemd-bless-boot', + 'src/boot/bless-boot.c', + include_directories : includes, + link_with : [libshared], + dependencies : [libblkid], + install_rpath : rootlibexecdir, + install : true, + install_dir : rootlibexecdir) endif exe = executable('systemd-socket-activate', 'src/activate/activate.c', diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 3d83fc9b10..e1628ddba0 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1235,6 +1235,34 @@ int fsync_directory_of_file(int fd) { return 0; } +int fsync_path_at(int at_fd, const char *path) { + _cleanup_close_ int opened_fd = -1; + int fd; + + if (isempty(path)) { + if (at_fd == AT_FDCWD) { + opened_fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (opened_fd < 0) + return -errno; + + fd = opened_fd; + } else + fd = at_fd; + } else { + + opened_fd = openat(at_fd, path, O_RDONLY|O_CLOEXEC); + if (opened_fd < 0) + return -errno; + + fd = opened_fd; + } + + if (fsync(fd) < 0) + return -errno; + + return 0; +} + int open_parent(const char *path, int flags, mode_t mode) { _cleanup_free_ char *parent = NULL; int fd; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index bc753d5920..955b146a6a 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -105,5 +105,6 @@ void unlink_tempfilep(char (*p)[]); int unlinkat_deallocate(int fd, const char *name, int flags); int fsync_directory_of_file(int fd); +int fsync_path_at(int at_fd, const char *path); int open_parent(const char *path, int flags, mode_t mode); diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c new file mode 100644 index 0000000000..84ac9e39e4 --- /dev/null +++ b/src/boot/bless-boot.c @@ -0,0 +1,476 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include + +#include "alloc-util.h" +#include "bootspec.h" +#include "efivars.h" +#include "fd-util.h" +#include "fs-util.h" +#include "log.h" +#include "parse-util.h" +#include "path-util.h" +#include "util.h" +#include "verbs.h" +#include "virt.h" + +static char *arg_path = NULL; + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [COMMAND] [OPTIONS...]\n" + "\n" + "Mark the boot process as good or bad.\n\n" + " -h --help Show this help\n" + " --version Print version\n" + " --path=PATH Path to the EFI System Partition (ESP)\n" + "\n" + "Commands:\n" + " good Mark this boot as good\n" + " bad Mark this boot as bad\n" + " indeterminate Undo any marking as good or bad\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_PATH = 0x100, + ARG_VERSION, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "path", required_argument, NULL, ARG_PATH }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(0, NULL, NULL); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_PATH: + r = free_and_strdup(&arg_path, optarg); + if (r < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option"); + } + + return 1; +} + +static int acquire_esp(void) { + _cleanup_free_ char *np = NULL; + int r; + + r = find_esp_and_warn(arg_path, false, &np, NULL, NULL, NULL, NULL); + if (r == -ENOKEY) /* find_esp_and_warn() doesn't warn in this one error case, but in all others */ + 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."); + if (r < 0) + return r; + + free_and_replace(arg_path, np); + log_debug("Using EFI System Partition at %s.", arg_path); + + return 0; +} + +static int parse_counter( + const char *path, + const char **p, + uint64_t *ret_left, + uint64_t *ret_done) { + + uint64_t left, done; + const char *z, *e; + size_t k; + int r; + + assert(path); + assert(p); + + e = *p; + assert(e); + assert(*e == '+'); + + e++; + + k = strspn(e, DIGITS); + if (k == 0) { + log_error("Can't parse empty 'tries left' counter from LoaderBootCountPath: %s", path); + return -EINVAL; + } + + z = strndupa(e, k); + r = safe_atou64(z, &left); + if (r < 0) + return log_error_errno(r, "Failed to parse 'tries left' counter from LoaderBootCountPath: %s", path); + + e += k; + + if (*e == '-') { + e++; + + k = strspn(e, DIGITS); + if (k == 0) { /* If there's a "-" there also needs to be at least one digit */ + log_error("Can't parse empty 'tries done' counter from LoaderBootCountPath: %s", path); + return -EINVAL; + } + + z = strndupa(e, k); + r = safe_atou64(z, &done); + if (r < 0) + return log_error_errno(r, "Failed to parse 'tries done' counter from LoaderBootCountPath: %s", path); + + e += k; + } else + done = 0; + + if (done == 0) + log_warning("The 'tries done' counter is currently at zero. This can't really be, after all we are running, and this boot must hence count as one. Proceeding anyway."); + + *p = e; + + if (ret_left) + *ret_left = left; + + if (ret_done) + *ret_done = done; + + return 0; +} + +static int acquire_boot_count_path( + char **ret_path, + char **ret_prefix, + uint64_t *ret_left, + uint64_t *ret_done, + char **ret_suffix) { + + _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL; + const char *last, *e; + uint64_t left, done; + int r; + + r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderBootCountPath", &path); + if (r == -ENOENT) + return -EUNATCH; /* in this case, let the caller print a message */ + if (r < 0) + return log_error_errno(r, "Failed to read LoaderBootCountPath EFI variable: %m"); + + efi_tilt_backslashes(path); + + if (!path_is_normalized(path)) { + log_error("Path read from LoaderBootCountPath is not normalized, refusing: %s", path); + return -EINVAL; + } + + if (!path_is_absolute(path)) { + log_error("Path read from LoaderBootCountPath is not absolute, refusing: %s", path); + return -EINVAL; + } + + last = last_path_component(path); + e = strrchr(last, '+'); + if (!e) { + log_error("Path read from LoaderBootCountPath does not contain a counter, refusing: %s", path); + return -EINVAL; + } + + if (ret_prefix) { + prefix = strndup(path, e - path); + if (!prefix) + return log_oom(); + } + + r = parse_counter(path, &e, &left, &done); + if (r < 0) + return r; + + if (ret_suffix) { + suffix = strdup(e); + if (!suffix) + return log_oom(); + + *ret_suffix = TAKE_PTR(suffix); + } + + if (ret_path) + *ret_path = TAKE_PTR(path); + if (ret_prefix) + *ret_prefix = TAKE_PTR(prefix); + if (ret_left) + *ret_left = left; + if (ret_done) + *ret_done = done; + + return 0; +} + +static int make_good(const char *prefix, const char *suffix, char **ret) { + _cleanup_free_ char *good = NULL; + + assert(prefix); + assert(suffix); + assert(ret); + + /* Generate the path we'd use on good boots. This one is easy. If we are successful, we simple drop the counter + * pair entirely from the name. After all, we know all is good, and the logs will contain information about the + * tries we needed to come here, hence it's safe to drop the counters from the name. */ + + good = strjoin(prefix, suffix); + if (!good) + return -ENOMEM; + + *ret = TAKE_PTR(good); + return 0; +} + +static int make_bad(const char *prefix, uint64_t done, const char *suffix, char **ret) { + _cleanup_free_ char *bad = NULL; + + assert(prefix); + assert(suffix); + assert(ret); + + /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done' + * counter. The information might be interesting to boot loaders, after all. */ + + if (done == 0) { + bad = strjoin(prefix, "+0", suffix); + if (!bad) + return -ENOMEM; + } else { + if (asprintf(&bad, "%s+0-%" PRIu64 "%s", prefix, done, suffix) < 0) + return -ENOMEM; + } + + *ret = TAKE_PTR(bad); + return 0; +} + +static const char *skip_slash(const char *path) { + assert(path); + assert(path[0] == '/'); + + return path + 1; +} + +static int verb_status(int argc, char *argv[], void *userdata) { + + _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; + _cleanup_close_ int fd = -1; + uint64_t left, done; + int r; + + r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix); + if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */ + puts("clean"); + return 0; + } + if (r < 0) + return r; + + r = acquire_esp(); + if (r < 0) + return r; + + r = make_good(prefix, suffix, &good); + if (r < 0) + return log_oom(); + + r = make_bad(prefix, done, suffix, &bad); + if (r < 0) + return log_oom(); + + log_debug("Booted file: %s%s\n" + "The same modified for 'good': %s%s\n" + "The same modified for 'bad': %s%s\n", + arg_path, path, + arg_path, good, + arg_path, bad); + + log_debug("Tries left: %" PRIu64"\n" + "Tries done: %" PRIu64"\n", + left, done); + + fd = open(arg_path, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + if (fd < 0) + return log_error_errno(errno, "Failed to open ESP '%s': %m", arg_path); + + if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) { + puts("indeterminate"); + return 0; + } + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", path); + + if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) { + puts("good"); + return 0; + } + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", good); + + if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) { + puts("bad"); + return 0; + } + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad); + + return log_error_errno(errno, "Couldn't determine boot state: %m"); +} + +static int verb_set(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL, *parent = NULL; + const char *target, *source1, *source2; + _cleanup_close_ int fd = -1; + uint64_t done; + int r; + + r = acquire_boot_count_path(&path, &prefix, NULL, &done, &suffix); + if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */ + return log_error_errno(r, "Not booted with boot counting in effect."); + if (r < 0) + return r; + + r = acquire_esp(); + if (r < 0) + return r; + + r = make_good(prefix, suffix, &good); + if (r < 0) + return log_oom(); + + r = make_bad(prefix, done, suffix, &bad); + if (r < 0) + return log_oom(); + + fd = open(arg_path, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + if (fd < 0) + return log_error_errno(errno, "Failed to open ESP '%s': %m", arg_path); + + /* Figure out what rename to what */ + if (streq(argv[0], "good")) { + target = good; + source1 = path; + source2 = bad; /* Maybe this boot was previously marked as 'bad'? */ + } else if (streq(argv[0], "bad")) { + target = bad; + source1 = path; + source2 = good; /* Maybe this boot was previously marked as 'good'? */ + } else { + assert(streq(argv[0], "indeterminate")); + target = path; + source1 = good; + source2 = bad; + } + + r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target)); + if (r == -EEXIST) + goto exists; + else if (r == -ENOENT) { + + r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target)); + if (r == -EEXIST) + goto exists; + else if (r == -ENOENT) { + + if (access(target, F_OK) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */ + goto exists; + + return log_error_errno(r, "Can't find boot counter source file for '%s': %m", target); + } else if (r < 0) + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target); + else + log_debug("Successfully renamed '%s' to '%s'.", source2, target); + + } else if (r < 0) + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target); + else + log_debug("Successfully renamed '%s' to '%s'.", source1, target); + + /* First, fsync() the directory these files are located in */ + parent = dirname_malloc(path); + if (!parent) + return log_oom(); + + r = fsync_path_at(fd, skip_slash(parent)); + if (r < 0) + log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m"); + + /* Secondly, syncfs() the whole file system these files are located in */ + if (syncfs(fd) < 0) + log_debug_errno(errno, "Failed to synchronize ESP, ignoring: %m"); + + log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done); + + return 1; + +exists: + log_debug("Operation already executed before, not doing anything."); + return 0; +} + +int main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, + { "good", VERB_ANY, 1, VERB_MUST_BE_ROOT, verb_set }, + { "bad", VERB_ANY, 1, VERB_MUST_BE_ROOT, verb_set }, + { "indeterminate", VERB_ANY, 1, VERB_MUST_BE_ROOT, verb_set }, + {} + }; + + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (detect_container() > 0) { + log_error("Marking a boot is not supported in containers."); + r = -EOPNOTSUPP; + goto finish; + } + + if (!is_efi_boot()) { + log_error("Marking a boot is only supported on EFI systems."); + r = -EOPNOTSUPP; + goto finish; + } + + r = dispatch_verb(argc, argv, verbs, NULL); + +finish: + free(arg_path); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/units/meson.build b/units/meson.build index 70eabe5227..26a725b575 100644 --- a/units/meson.build +++ b/units/meson.build @@ -136,6 +136,7 @@ in_units = [ ['systemd-backlight@.service', 'ENABLE_BACKLIGHT'], ['systemd-binfmt.service', 'ENABLE_BINFMT', 'sysinit.target.wants/'], + ['systemd-bless-boot.service', 'ENABLE_EFI HAVE_BLKID'], ['systemd-coredump@.service', 'ENABLE_COREDUMP'], ['systemd-firstboot.service', 'ENABLE_FIRSTBOOT', 'sysinit.target.wants/'], diff --git a/units/systemd-bless-boot.service.in b/units/systemd-bless-boot.service.in new file mode 100644 index 0000000000..511d991d3b --- /dev/null +++ b/units/systemd-bless-boot.service.in @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# 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=Mark the Current Boot Loader Entry as Good +Documentation=man:systemd-bless-boot.service(8) +DefaultDependencies=no +Requires=boot-complete.target +After=local-fs.target boot-complete.target +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-bless-boot good From 8d16ed0785769bf35903970808678f7973fee273 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jun 2018 17:00:47 +0200 Subject: [PATCH 05/15] add "systemd-bless-boot-generator" This generator automatically pulls in "systemd-bless-boot.service" if a boot with boot counting is detected. --- meson.build | 8 ++++ src/boot/bless-boot-generator.c | 75 +++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/boot/bless-boot-generator.c diff --git a/meson.build b/meson.build index 35d0968b7e..059a261813 100644 --- a/meson.build +++ b/meson.build @@ -1805,6 +1805,14 @@ if conf.get('ENABLE_EFI') == 1 and conf.get('HAVE_BLKID') == 1 install_rpath : rootlibexecdir, install : true, install_dir : rootlibexecdir) + + executable('systemd-bless-boot-generator', + 'src/boot/bless-boot-generator.c', + include_directories : includes, + link_with : [libshared], + install_rpath : rootlibexecdir, + install : true, + install_dir : systemgeneratordir) endif exe = executable('systemd-socket-activate', 'src/activate/activate.c', diff --git a/src/boot/bless-boot-generator.c b/src/boot/bless-boot-generator.c new file mode 100644 index 0000000000..139f65d25c --- /dev/null +++ b/src/boot/bless-boot-generator.c @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include + +#include "efivars.h" +#include "log.h" +#include "mkdir.h" +#include "special.h" +#include "string-util.h" +#include "util.h" +#include "virt.h" + +/* This generator pulls systemd-bless-boot.service into the initial transaction if the "LoaderBootCountPath" EFI + * variable is set, i.e. the system boots up with boot counting in effect, which means we should mark the boot as + * "good" if we manage to boot up far enough. */ + +static const char *arg_dest = "/tmp"; + +int main(int argc, char *argv[]) { + const char *p; + + log_set_prohibit_ipc(true); + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[2]; + + if (in_initrd() > 0) { + log_debug("Skipping generator, running in the initrd."); + return EXIT_SUCCESS; + } + + if (detect_container() > 0) { + log_debug("Skipping generator, running in a container."); + return EXIT_SUCCESS; + } + + if (!is_efi_boot()) { + log_debug("Skipping generator, not an EFI boot."); + return EXIT_SUCCESS; + } + + if (access("/sys/firmware/efi/efivars/LoaderBootCountPath-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f", F_OK) < 0) { + + if (errno == ENOENT) { + log_debug_errno(errno, "Skipping generator, not booted with boot counting in effect."); + return EXIT_SUCCESS; + } + + log_error_errno(errno, "Failed to check if LoaderBootCountPath EFI variable exists: %m"); + return EXIT_FAILURE; + } + + /* We pull this in from basic.target so that it ends up in all "regular" boot ups, but not in rescue.target or + * even emergency.target. */ + p = strjoina(arg_dest, "/" SPECIAL_BASIC_TARGET ".wants/systemd-bless-boot.service"); + (void) mkdir_parents(p, 0755); + if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-bless-boot.service", p) < 0) { + log_error_errno(errno, "Failed to create symlink '%s': %m", p); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} From bf7b6d28c3252cb97df228df185ad0be7609b31b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jun 2018 17:35:53 +0200 Subject: [PATCH 06/15] kernel-install: teach bot loader spec entry generator to generate entries with "tries" This makes two changes: 1. When called for "remove" any drop-ins with "+" suffix are removed too, so that the logic works for entries with boot counting enabled too and we don't lose track of configuration snippets created that way. 2. When called for "add" we optionally generate a "+" suffix, based on the data in /etc/kernel/tries if it exists. This basically means after "echo 5 > /etc/kernel/tries" any installed kernels will automatically set up for 5 boot tries before older kernels will be tried. --- src/kernel-install/90-loaderentry.install | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/kernel-install/90-loaderentry.install b/src/kernel-install/90-loaderentry.install index a271cdb8a0..39ec8a69c6 100644 --- a/src/kernel-install/90-loaderentry.install +++ b/src/kernel-install/90-loaderentry.install @@ -19,10 +19,11 @@ MACHINE_ID=$KERNEL_INSTALL_MACHINE_ID BOOT_DIR="/$MACHINE_ID/$KERNEL_VERSION" BOOT_ROOT=${BOOT_DIR_ABS%$BOOT_DIR} -LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf" if [[ $COMMAND == remove ]]; then - exec rm -f "$LOADER_ENTRY" + rm -f "$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf" + rm -f "$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION+"*".conf" + exit 0 fi if ! [[ $COMMAND == add ]]; then @@ -63,6 +64,17 @@ if ! [[ ${BOOT_OPTIONS[*]} ]]; then exit 1 fi +if [[ -f /etc/kernel/tries ]]; then + read -r TRIES &2 + exit 1 + fi + LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION+$TRIES.conf" +else + LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf" +fi + cp "$KERNEL_IMAGE" "$BOOT_DIR_ABS/linux" && chown root:root "$BOOT_DIR_ABS/linux" && chmod 0644 "$BOOT_DIR_ABS/linux" || { From 4b2d80bb0a9725a871aaec1d0abeeb4fee2c6eda Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jun 2018 18:14:22 +0200 Subject: [PATCH 07/15] man: update kernel-install(8) documentation Many general updates, but most importantly, document the /etc/kernel/tries logic briefly. --- man/kernel-install.xml | 92 +++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/man/kernel-install.xml b/man/kernel-install.xml index 884b25da9b..83e50c8d70 100644 --- a/man/kernel-install.xml +++ b/man/kernel-install.xml @@ -63,49 +63,61 @@ add KERNEL-VERSION KERNEL-IMAGE - kernel-install creates the directory + This command expects a kernel version string and a path to a kernel image file as + arguments. kernel-install creates the directory /boot/MACHINE-ID/KERNEL-VERSION/ - and calls executables from - /usr/lib/kernel/install.d/*.install and - /etc/kernel/install.d/*.install with - the arguments - add KERNEL-VERSION \ - /boot/MACHINE-ID/KERNEL-VERSION/ KERNEL-IMAGE + and calls the executables from /usr/lib/kernel/install.d/*.install and + /etc/kernel/install.d/*.install with the following arguments: + + add KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ KERNEL-IMAGE - The kernel-install plugin 50-depmod.install runs depmod for the KERNEL-VERSION. + Two default plugins execute the following operations in this case: - The kernel-install plugin - 90-loaderentry.install copies - KERNEL-IMAGE to - /boot/MACHINE-ID/KERNEL-VERSION/linux. - It also creates a boot loader entry according to the boot - loader specification in - /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. - The title of the entry is the - PRETTY_NAME parameter specified - in /etc/os-release or - /usr/lib/os-release (if the former is - missing), or "Linux - KERNEL-VERSION", if unset. If - the file initrd is found next to the - linux file, the initrd will be added to - the configuration. + + + 50-depmod.install runs + depmod8 for the + KERNEL-VERSION. + + 90-loaderentry.install copies KERNEL-IMAGE + to + /boot/MACHINE-ID/KERNEL-VERSION/linux. + It also creates a boot loader entry according to the Boot Loader Specification in + /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. + The title of the entry is the PRETTY_NAME parameter specified in + /etc/os-release or /usr/lib/os-release (if the former is + missing), or "Linux KERNEL-VERSION", if unset. If the file + initrd is found next to the kernel image file, the initrd will be added to the + configuration. + remove KERNEL-VERSION - Calls executables from /usr/lib/kernel/install.d/*.install - and /etc/kernel/install.d/*.install with the arguments + This command expects a kernel version string as single argument. This calls executables from + /usr/lib/kernel/install.d/*.install and + /etc/kernel/install.d/*.install with the following arguments: + remove KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ - kernel-install removes the entire directory - /boot/MACHINE-ID/KERNEL-VERSION/ afterwards. + Afterwards, kernel-install removes the directory + /boot/MACHINE-ID/KERNEL-VERSION/ + and its contents. + + Two default plugins execute the following operations in this case: + + + + 50-depmod.install removes the files generated by depmod for this kernel again. + + 90-loaderentry.install removes the file + /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. + - The kernel-install plugin 90-loaderentry.install removes the file - /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. @@ -136,8 +148,22 @@ /proc/cmdline - The content of the file /etc/kernel/cmdline specifies the kernel command line to use. - If that file does not exist, /proc/cmdline is used. + Read by 90-loaderentry.install. The content of the file + /etc/kernel/cmdline specifies the kernel command line to use. If that file does not + exist, /proc/cmdline is used. + + + + + /etc/kernel/tries + + + Read by 90-loaderentry.install. If this file exists a numeric value is read from + it and the naming of the generated entry file is slightly altered to include it as + /boot/loader/entries/MACHINE-ID-KERNEL-VERSION+TRIES.conf. This + is useful for boot loaders such as + systemd-boot7 which + implement boot attempt counting with a counter embedded in the entry file name. @@ -165,6 +191,8 @@ machine-id5, os-release5, + depmod8, + systemd-boot7, Boot Loader Specification From f876f53789380cb2c096e3c09401f7273387d1d2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 16:07:33 +0200 Subject: [PATCH 08/15] units: add simple boot check unit This is might be useful in some cases, but it's primarily an example for a boot check service that can be plugged before boot-complete.target. It's disabled by default. All it does is check whether the failed unit count is zero --- meson.build | 9 ++ src/boot/boot-check-no-failures.c | 106 ++++++++++++++++++ units/meson.build | 1 + .../systemd-boot-check-no-failures.service.in | 24 ++++ 4 files changed, 140 insertions(+) create mode 100644 src/boot/boot-check-no-failures.c create mode 100644 units/systemd-boot-check-no-failures.service.in diff --git a/meson.build b/meson.build index 059a261813..7960e97893 100644 --- a/meson.build +++ b/meson.build @@ -1815,6 +1815,15 @@ if conf.get('ENABLE_EFI') == 1 and conf.get('HAVE_BLKID') == 1 install_dir : systemgeneratordir) endif +executable('systemd-boot-check-no-failures', + 'src/boot/boot-check-no-failures.c', + include_directories : includes, + link_with : [libshared], + dependencies : [libblkid], + install_rpath : rootlibexecdir, + install : true, + install_dir : rootlibexecdir) + exe = executable('systemd-socket-activate', 'src/activate/activate.c', include_directories : includes, link_with : [libshared], diff --git a/src/boot/boot-check-no-failures.c b/src/boot/boot-check-no-failures.c new file mode 100644 index 0000000000..e7884461c6 --- /dev/null +++ b/src/boot/boot-check-no-failures.c @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include +#include + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "bus-error.h" +#include "log.h" +#include "util.h" + +static int help(void) { + + printf("%s [COMMAND] [OPTIONS...]\n" + "\n" + "Verify system operational state.\n\n" + " -h --help Show this help\n" + " --version Print version\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_PATH = 0x100, + ARG_VERSION, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + uint32_t n; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to system bus: %m"); + goto finish; + } + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "NFailedUnits", + &error, + 'u', + &n); + if (r < 0) { + log_error_errno(r, "Failed to get failed units counter: %s", bus_error_message(&error, r)); + goto finish; + } + + if (n > 0) + log_notice("Health check: %" PRIu32 " units have failed.", n); + else + log_info("Health check: no failed units."); + + r = n > 0 ? EXIT_FAILURE : EXIT_SUCCESS; + +finish: + return r < 0 ? EXIT_FAILURE : r; +} diff --git a/units/meson.build b/units/meson.build index 26a725b575..d69508467f 100644 --- a/units/meson.build +++ b/units/meson.build @@ -137,6 +137,7 @@ in_units = [ ['systemd-binfmt.service', 'ENABLE_BINFMT', 'sysinit.target.wants/'], ['systemd-bless-boot.service', 'ENABLE_EFI HAVE_BLKID'], + ['systemd-boot-check-no-failures.service', ''], ['systemd-coredump@.service', 'ENABLE_COREDUMP'], ['systemd-firstboot.service', 'ENABLE_FIRSTBOOT', 'sysinit.target.wants/'], diff --git a/units/systemd-boot-check-no-failures.service.in b/units/systemd-boot-check-no-failures.service.in new file mode 100644 index 0000000000..27e898b85b --- /dev/null +++ b/units/systemd-boot-check-no-failures.service.in @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# 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=Check if Any System Units Failed +Documentation=man:systemd-boot-check-no-failures.service(8) +After=default.target graphical.target multi-user.target +Before=boot-complete.target +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-boot-check-no-failures + +[Install] +RequiredBy=boot-complete.target From 8eebff9e102e8f38fc260692c5dbb521817ea776 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 21:48:33 +0200 Subject: [PATCH 09/15] man: document the various EFI vars sd-boot sets --- man/systemd-boot.xml | 100 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index c3b34e54c9..9f2d410221 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -237,6 +237,106 @@ Loader Specification are read from /EFI/Linux/ on the ESP. + + EFI Variables + + The following EFI variables are defined, set and read by systemd-boot, under the vendor + UUID 4a67b082-0a4c-41cf-b6c7-440b29bb8c4, for communication between the OS and the boot + loader: + + + + LoaderBootCountPath + If boot counting is enabled, contains the path to the file in whose name the boot counters are + encoded. Set by the boot + loader. systemd-bless-boot.service8 + uses this information to mark a boot as successful as determined by the successful activation of the + boot-complete.target target unit. + + + + LoaderConfigTimeout + The menu time-out. Read by the boot loader. (Also, modified by it when the + t/T keys are used, see above.) + + + + LoaderDevicePartUUID + + Contains the partition UUID of the EFI System Partition the boot loader was run from. Set by + the boot + loader. systemd-gpt-auto-generator8 + uses this information to automatically find the disk booted from, in order to discover various other partitions + on the same disk automatically. + + + + LoaderEntries + + A list of the identifiers of all discovered boot loader entries. Set by the boot + loader. + + + + LoaderEntryDefault + LoaderEntryOneShot + + The identifier of the default boot loader entry. Set primarily by the OS and read by the boot + loader. LoaderEntryOneShot sets the default entry for the next boot only, while + LoaderEntryDefault sets it persistently for all future + boots. bootctl1's + and commands make use of these variables. The boot + loader modifies LoaderEntryDefault on request, when the d key is used, see + above.) + + + + LoaderEntrySelected + + The identifier of the boot loader entry currently being booted. Set by the boot + loader. + + + + LoaderFirmwareInfo + LoaderFirmwareType + + Brief firmware information. Set by the boot loader. Use + bootctl1 to view this + data. + + + + LoaderImageIdentifier + + The path of executable of the boot loader used for the current boot, relative to the EFI System + Partition's root directory. Set by the boot loader. Use + bootctl1 to view this + data. + + + + LoaderInfo + + Brief information about the boot loader. Set by the boot loader. Use + bootctl1 to view this + data. + + + + LoaderTimeExecUSec + LoaderTimeInitUSec + LoaderTimeMenuUsec + + Information about the time spent in various parts of the boot loader. Set by the boot + loader. Use systemd-analyze1 + to view this data. These variables are defined by the Boot Loader + Interface. + + + + See Also From ab3fc7b1935093fb63f476a06cbebc3f12cd9257 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jun 2018 15:03:27 +0200 Subject: [PATCH 10/15] man: document systemd-bless-boot --- man/rules/meson.build | 1 + man/systemd-bless-boot.service.xml | 115 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 man/systemd-bless-boot.service.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index 3602bbaa1a..93e410a5cb 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -617,6 +617,7 @@ manpages = [ ['systemd-ask-password', '1', [], ''], ['systemd-backlight@.service', '8', ['systemd-backlight'], 'ENABLE_BACKLIGHT'], ['systemd-binfmt.service', '8', ['systemd-binfmt'], 'ENABLE_BINFMT'], + ['systemd-bless-boot.service', '8', [], 'ENABLE_EFI'], ['systemd-boot', '7', ['sd-boot'], 'ENABLE_EFI'], ['systemd-cat', '1', [], ''], ['systemd-cgls', '1', [], ''], diff --git a/man/systemd-bless-boot.service.xml b/man/systemd-bless-boot.service.xml new file mode 100644 index 0000000000..fb362cef2d --- /dev/null +++ b/man/systemd-bless-boot.service.xml @@ -0,0 +1,115 @@ + + + + + + + + + systemd-bless-boot.service + systemd + + + + systemd-bless-boot.service + 8 + + + + systemd-bless-boot.service + Mark current boot process as successful + + + + systemd-bless-boot.service + /usr/lib/systemd/system-bless-boot + + + + Description + + systemd-bless-boot.service is a system service that marks the current boot process as + successful. It's automatically pulled into the initial transaction when + systemd-bless-boot-generator8 + detects that systemd-boot7 style + boot counting is used. + + Internally, the service operates based on the LoaderBootCountPath EFI variable (of the + vendor UUID 4a67b082-0a4c-41cf-b6c7-440b29bb8c4), which is passed from the boot loader to the + OS. It contains a file system path (relative to the EFI system partition) of the Boot Loader Specification compliant boot loader entry + file or unified kernel image file that was used to boot up the + system. systemd-bless-boot.service removes the two 'tries done' and 'tries left' numeric boot + counters from the filename, which indicates to future invocations of the boot loader that the entry has completed + booting successfully at least once. (This service will hence rename the boot loader entry file or unified kernel + image file on the first successful boot.) + + + + Options + + The /usr/lib/systemd/system-bless-boot executable may also be invoked from the + command line, taking one of the following command arguments: + + + + + + The current status of the boot loader entry file or unified kernel image file is shown. This + outputs one of good, bad, indeterminate, + clean, depending on the state and previous invocations of the command. The string + indeterminate is shown initially after boot, before it has been marked as "good" or + "bad". The string good is shown after the boot was marked as "good" with the + command below, and "bad" conversely after the command was + invoked. The string clean is returned when boot counting is currently not in effect. + + This command is implied if no command argument is specified. + + + + + + When invoked, the current boot loader entry file or unified kernel image file will be marked as + "good", executing the file rename operation described above. This command is intended to be invoked at the end + of a successful boot. The systemd-bless-boot.service unit invokes this + command. + + + + + + When called the 'tries left' counter in the boot loader entry file name or unified kernel image + file name is set to zero, marking the boot loader entry or kernel image as "bad", so that the boot loader won't + consider it anymore on future boots (at least as long as there are other entries available that are not marked + "bad" yet). This command is normally not executed, but can be used to instantly put an end to the boot counting + logic if a problem is detected and persistently mark the boot entry as bad. + + + + + + This command undoes any marking of the current boot loader entry file or unified kernel image + file as good or bad. This is implemented by renaming the boot loader entry file or unified kernel image file + back to the path encoded in the LoaderBootCountPath EFI variable. + + + + + + + + + + See Also + + systemd1, + systemd-boot7, + systemd.special1 + + + + From 04431cd1f8e1544ac6515ced821a6a7ff98f6909 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jun 2018 15:14:57 +0200 Subject: [PATCH 11/15] man: document systemd-boot-check-no-failures.service --- man/rules/meson.build | 1 + ...systemd-boot-check-no-failures.service.xml | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 man/systemd-boot-check-no-failures.service.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index 93e410a5cb..4258bd0633 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -618,6 +618,7 @@ manpages = [ ['systemd-backlight@.service', '8', ['systemd-backlight'], 'ENABLE_BACKLIGHT'], ['systemd-binfmt.service', '8', ['systemd-binfmt'], 'ENABLE_BINFMT'], ['systemd-bless-boot.service', '8', [], 'ENABLE_EFI'], + ['systemd-boot-check-no-failures.service', '8', [], ''], ['systemd-boot', '7', ['sd-boot'], 'ENABLE_EFI'], ['systemd-cat', '1', [], ''], ['systemd-cgls', '1', [], ''], diff --git a/man/systemd-boot-check-no-failures.service.xml b/man/systemd-boot-check-no-failures.service.xml new file mode 100644 index 0000000000..55c2adffdb --- /dev/null +++ b/man/systemd-boot-check-no-failures.service.xml @@ -0,0 +1,54 @@ + + + + + + + + + systemd-boot-check-no-failures.service + systemd + + + + systemd-boot-check-no-failures.service + 8 + + + + systemd-boot-check-no-failures.service + verify that the system booted up cleanly + + + + systemd-boot-check-no-failures.service + /usr/lib/systemd/system-boot-check-no-failures + + + + Description + + systemd-boot-check-no-failures.service is a system service that checks whether the + system booted up successfully. This service implements a very minimal test only: whether there are any failed units on + the system. This service is disabled by default. When enabled, it is ordered before + boot-complete.target, thus ensuring the target cannot be reached when the system booted up + with failed services. + + Note that due the simple nature of this check this service is probably not suitable for deployment in most + scenarios. It is primarily useful only as example for developing more fine-grained checks to order before + boot-complete.target. + + + + See Also + + systemd1, + systemd.special1 + + + + From 223ce56fa189bfacb7f21f364797acf910e09306 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jun 2018 15:46:28 +0200 Subject: [PATCH 12/15] man: document systemd-bless-boot-generator --- man/rules/meson.build | 1 + man/systemd-bless-boot-generator.xml | 49 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 man/systemd-bless-boot-generator.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index 4258bd0633..b3011c5f04 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -617,6 +617,7 @@ manpages = [ ['systemd-ask-password', '1', [], ''], ['systemd-backlight@.service', '8', ['systemd-backlight'], 'ENABLE_BACKLIGHT'], ['systemd-binfmt.service', '8', ['systemd-binfmt'], 'ENABLE_BINFMT'], + ['systemd-bless-boot-generator', '8', [], 'ENABLE_EFI'], ['systemd-bless-boot.service', '8', [], 'ENABLE_EFI'], ['systemd-boot-check-no-failures.service', '8', [], ''], ['systemd-boot', '7', ['sd-boot'], 'ENABLE_EFI'], diff --git a/man/systemd-bless-boot-generator.xml b/man/systemd-bless-boot-generator.xml new file mode 100644 index 0000000000..980941469e --- /dev/null +++ b/man/systemd-bless-boot-generator.xml @@ -0,0 +1,49 @@ + + + + + + + + systemd-bless-boot-generator + systemd + + + + systemd-bless-boot-generator + 8 + + + + systemd-bless-boot-generator + Pull systemd-bless-boot.service into the initial boot transaction when boot counting is in effect. + + + + /usr/lib/systemd/system-generators/systemd-bless-boot-generator + + + + Description + + systemd-bless-boot-generator is a generator that pulls + systemd-bless-boot.service into the initial boot transaction when boot counting, as + implemented by systemd-boot7, is + enabled. + + systemd-bless-boot-generator implements + systemd.generator7. + + + + See Also + + systemd1, + systemd-bless-boot.service8, + systemd-boot7 + + + + From 2b6cc3cab9b5f846929b4d00bec6027ace4136ee Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jun 2018 22:11:35 +0200 Subject: [PATCH 13/15] man: document boot counting logic in systemd-boot --- man/systemd-boot.xml | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 9f2d410221..1b911f9aaf 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -337,11 +337,68 @@ + + Boot Counting + + systemd-boot implements a simple boot counting mechanism on top of the Boot Loader Specification, for automatic and unattended + fallback to older kernel versions/boot loader entries when a specific entry continously fails. Any boot loader + entry file and unified kernel image file that contains a + followed by one or two numbers (if + two they need to be separated by a -), before the .conf or + .efi suffix is subject to boot counting: the first of the two numbers ('tries left') is + decreased by one on every boot attempt, the second of the two numbers ('tries done') is increased by one (if 'tries + done' is absent it is considered equivalent to 0). Depending on the current value of these two counters the boot + entry is considered to be in one of three states: + + + If the 'tries left' counter of an entry is greater than zero the entry is considered to be in + 'indeterminate' state. This means the entry has not completed booting successfully yet, but also hasn't been + determined not to work. + + If the 'tries left' counter of an entry is zero it is considered to be in 'bad' state. This means + no further attempts to boot this item will be made (that is, unless all other boot entries are also in 'bad' + state), as all attempts to boot this entry have not completed successfully. + + If the 'tries left' and 'tries done' counters of an entry are absent it is considered to be in + 'good' state. This means further boot counting for the entry is turned off, as it successfully booted at least + once. The + systemd-bless-boot.service8 + service moves the currently booted entry from 'indeterminate' into 'good' state when a boot attempt completed + successfully. + + + Generally, when new entries are added to the boot loader, they first start out in 'indeterminate' state, + i.e. with a 'tries left' counter greater than zero. The boot entry remains in this state until either it managed to + complete a full boot successfully at least once (in which case it will be in 'good' state) — or the 'tries left' + counter reaches zero (in which case it will be in 'bad' state). + + Example: let's say a boot loader entry file foo.conf is set up for 3 boot tries. The + installer will hence create it under the name foo+3.conf. On first boot, the boot loader will + rename it to foo+2-1.conf. If that boot does not complete successfully, the boot loader will + rename it to foo+1-2.conf on the following boot. If that fails too, it will finally be renamed + foo+0-3.conf by the boot loader on next boot, after which it will be considered 'bad'. If the + boot succeeds however the entry file will be renamed to foo.conf by the OS, so that it is + considered 'good' from then on. + + The boot menu takes the 'tries left' counter into account when sorting the menu entries: entries in 'bad' + state are ordered at the end of the list, and entries in 'good' or 'indeterminate' at the beginning. The user can + freely choose to boot any entry of the menu, including those already marked 'bad'. If the menu entry to boot is + automatically determined, this means that 'good' or 'indeterminate' entries are generally preferred (as the top item of + the menu is the one booted by default), and 'bad' entries will only be considered if there are no 'good' or + 'indeterminate' entries left. + + The kernel-install8 kernel + install framework optionally sets the initial 'tries left' counter to the value specified in + /etc/kernel/tries when a boot loader entry is first created. + + See Also bootctl1, loader.conf5, + systemd-bless-boot.service8, + kernel-install8, Boot Loader Specification, Boot Loader Interface From 0c74648b881fc47e3151c88610eaeaa56175714b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jun 2018 23:52:11 +0200 Subject: [PATCH 14/15] doc: add a markdown document introducing the boot assessment logic --- docs/AUTOMATIC_BOOT_ASSESSMENT.md | 203 ++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docs/AUTOMATIC_BOOT_ASSESSMENT.md diff --git a/docs/AUTOMATIC_BOOT_ASSESSMENT.md b/docs/AUTOMATIC_BOOT_ASSESSMENT.md new file mode 100644 index 0000000000..0909d88b5c --- /dev/null +++ b/docs/AUTOMATIC_BOOT_ASSESSMENT.md @@ -0,0 +1,203 @@ +# Automatic Boot Assessment + +systemd provides support for automatically reverting back to the previous +version of the OS or kernel in case the system consistently fails to boot. This +support is built into various of its components. When used together these +components provide a complete solution on UEFI systems, built as add-on to the +[Boot Loader +Specification](https://systemd.io/BOOT_LOADER_SPECIFICATION). However, the +different components may also be used independently, and in combination with +other software, to implement similar schemes, for example with other boot +loaders or for non-UEFI systems. Here's a brief overview of the complete set of +components: + +* The + [`systemd-boot(7)`](https://www.freedesktop.org/software/systemd/man/systemd-boot.html) + boot loader optionally maintains a per-boot-loader-entry counter that is + decreased by one on each attempt to boot the entry, prioritizing entries that + have non-zero counters over those which already reached a counter of zero + when choosing the entry to boot. + +* The + [`systemd-bless-boot.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-bless-boot.service.html) + service automatically marks a boot loader entry, for which boot counting as + mentioned above is enabled, as "good" when a boot has been determined to be + successful, thus turning off boot counting for it. + +* The + [`systemd-bless-boot-generator(8)`](https://www.freedesktop.org/software/systemd/man/systemd-bless-boot-generator.html) + generator automatically pulls in `systemd-bless-boot.service` when use of + `systemd-boot` with boot counting enabled is detected. + +* The + [`systemd-boot-check-no-failures.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-boot-check-no-failures.service.html) + service is a simple health check tool that determines whether the boot + completed successfuly. When enabled it becomes an indirect dependency of + `systemd-bless-boot.service` (by means of `boot-complete.target`, see + below), ensuring that the boot will not be considered successful if there are + any failed services. + +* The `boot-complete.target` target unit (see + [`systemd.special(7)`](https://www.freedesktop.org/software/systemd/man/systemd.special.html)) + serves as a generic extension point both for units that shall be considered + necessary to consider a boot successful on one side (example: + `systemd-boot-check-no-failures.service` as described above), and units that + want to act only if the boot is successful on the other (example: + `systemd-bless-boot.service` as described above). + +* The + [`kernel-install(8)`](https://www.freedesktop.org/software/systemd/man/kernel-install.html) + script can optionally create boot loader entries that carry an initial boot + counter (the initial counter is configurable in `/etc/kernel/tries`). + +# Details + +The boot counting data `systemd-boot` and `systemd-bless-boot.service` +manage is stored in the name of the boot loader entries. If a boot loader entry +file name contains `+` followed by one or two numbers (if two numbers, then +those need to be separated by `-`) right before the `.conf` suffix, then boot +counting is enabled for it. The first number is the "tries left" counter +encoding how many attempts to boot this entry shall still be made. The second +number is the "tries done" counter, encoding how many failed attempts to boot +it have already been made. Each time a boot loader entry marked this way is +booted the first counter is decreased by one, and the second one increased by +one. (If the second counter is missing, then it is assumed to be equivalent to +zero.) If the "tries left" counter is above zero the entry is still considered +for booting (the entry's state is considered to be "indeterminate"), as soon as +it reached zero the entry is not tried anymore (entry state "bad"). If the boot +attempt completed successfully the entry's counters are removed from the name +(entry state "good"), thus turning off boot counting for the future. + +## Walkthrough + +Here's an example walkthrough of how this all fits together. + +1. The user runs `echo 3 > /etc/kernel/tries` to enable boot counting. + +2. A new kernel is installed. `kernel-install` is used to generate a new boot + loader entry file for it. Let's say the version string for the new kernel is + `4.14.11-300.fc27.x86_64`, a new boot loader entry + `/boot/loader/entries/4.14.11-300.fc27.x86_64+3.conf` is hence created. + +3. The system is booted for the first time after the new kernel is + installed. The boot loader now sees the `+3` counter in the entry file + name. It hence renames the file to `4.14.11-300.fc27.x86_64+2-1.conf` + indicating that at this point one attempt has started and thus only one less + is left. After the rename completed the entry is booted as usual. + +4. Let's say this attempt to boot fails. On the following boot the boot loader + will hence see the `+2-1` tag in the name, and hence rename the entry file to + `4.14.11-300.fc27.x86_64+1-2.conf`, and boot it. + +5. Let's say the boot fails again. On the subsequent boot the loader hence will + see the `+1-2` tag, and rename the file to + `4.14.11-300.fc27.x86_64+0-3.conf` and boot it. + +6. If this boot also fails, on the next boot the boot loader will see the the + tag `+0-3`, i.e. the counter reached zero. At this point the entry will be + considered "bad", and ordered to the end of the list of entries. The next + newest boot entry is now tried, i.e. the system automatically reverted back + to an earlier version. + +The above describes the walkthrough when the selected boot entry continously +fails. Let's have a look at an alternative ending to this walkthrough. In this +scenario the first 4 steps are the same as above: + +1. *as above* + +2. *as above* + +3. *as above* + +4. *as above* + +5. Let's say the second boot succeeds. The kernel initializes properly, systemd + is started and invokes all generators. + +6. One of the generators started is `systemd-bless-boot-generator` which + detects that boot counting is used. It hence pulls + `systemd-bless-boot.service` into the initial transaction. + +7. `systemd-bless-boot.service` is ordered after and `Requires=` the generic + `boot-complete.target` unit. This unit is hence also pulled into the initial + transaction. + +8. The `boot-complete.target` unit is ordered after and pulls in various units + that are required to succeed for the boot process to be considered + successful. One such unit is `systemd-boot-check-no-failures.service`. + +9. `systemd-boot-check-no-failures.service` is run after all its own + dependencies completed, and assesses that the boot completed + successfully. It hence exits cleanly. + +10. This allows `boot-complete.target` to be reached. This signifies to the + system that this boot attempt shall be considered successful. + +11. Which in turn permits `systemd-bless-boot.service` to run. It now + determines which boot loader entry file was used to boot the system, and + renames it dropping the counter tag. Thus + `4.14.11-300.fc27.x86_64+1-2.conf` is renamed to + `4.14.11-300.fc27.x86_64.conf`. From this moment boot counting is turned + off. + +12. On the following boot (and all subsequent boots after that) the entry is + now seen with boot counting turned off, no further renaming takes place. + +# How to adapt this scheme to other setups + +Of the stack described above many components may be replaced or augmented. Here +are a couple of recommendations. + +1. To support alternative boot loaders in place of `systemd-boot` two scenarios + are recommended: + + a. Boot loaders already implementing the Boot Loader Specification can simply + implement an equivalent file rename based logic, and thus integrate fully + with the rest of the stack. + + b. Boot loaders that want to implement boot counting and store the counters + elsewhere can provide their own replacements for + `systemd-bless-boot.service` and `systemd-bless-boot-generator`, but should + continue to use `boot-complete.target` and thus support any services + ordered before that. + +2. To support additional components that shall succeed before the boot is + considered successful, simply place them in units (if they aren't already) + and order them before the generic `boot-complete.target` target unit, + combined with `Requires=` dependencies from the target, so that the target + cannot be reached when any of the units fail. You may add any number of + units like this, and only if they all succeed the boot entry is marked as + good. Note that the target unit shall pull in these boot checking units, not + the other way around. + +3. To support additional components that shall only run on boot success, simply + wrap them in a unit and order them after `boot-complete.target`, pulling it + in. + +# FAQ + +1. *Why do you use file renames to store the counter? Why not a regular file?* + — Mainly two reasons: it's relatively likely that renames can be implemented + atomically even in simpler file systems, while writing to file contents has + a much bigger chance to be result in incomplete or corrupt data, as renaming + generally avoids allocating or releasing data blocks. Moreover it has the + benefit that the boot count metadata is directly attached to the boot loader + entry file, and thus the lifecycle of the metadata and the entry itself are + bound together. This means no additional clean-up needs to take place to + drop the boot loader counting information for an entry when it is removed. + +2. *Why not use EFI variables for storing the boot counter?* — The memory chips + used to back the persistent EFI variables are generally not of the highest + quality, hence shouldn't be written to more than necessary. This means we + can't really use it for changes made regularly during boot, but can use it + only for seldom made configuration changes. + +3. *I have a service which — when it fails — should immediately cause a + reboot. How does that fit in with the above?* — Well, that's orthogonal to + the above, please use `FailureAction=` in the unit file for this. + +4. *Under some condition I want to mark the current boot loader entry as bad + right-away, so that it never is tried again, how do I do that?* — You may + invoke `/usr/lib/systemd/systemd-bless-boot bad` at any time to mark the + current boot loader entry as "bad" right-away so that it isn't tried again + on later boots. From 8c073ddeec7592ee7d5d9656f4abbefd4ed5e651 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jun 2018 20:31:34 +0200 Subject: [PATCH 15/15] man: use proper and docbook tags for key bindings --- man/systemd-boot.xml | 88 ++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 1b911f9aaf..3b726e63a4 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -75,67 +75,67 @@ - ↑ (Up) - ↓ (Down) - j - k - PageUp - PageDown - Home - End + (Up) + (Down) + j + k + PageUp + PageDown + Home + End Navigate up/down in the entry list - ↵ (Enter) + (Enter) Boot selected entry - d + d Make selected entry the default - e + e Edit the kernel command line for selected entry - + - t + + + t Increase the timeout before default entry is booted - - - T + - + T Decrease the timeout - v + v Show systemd-boot, UEFI, and firmware versions - P + P Print status - Q + Q Quit - h - ? + h + ? Show a help screen - Ctrl + l + Ctrll Reprint the screen @@ -145,35 +145,35 @@ - l + l Linux - w + w Windows - a + a OS X - s + s EFI shell - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 Boot entry number 1 … 9 @@ -183,36 +183,36 @@ - ← (Left) - → (Right) - Home - End + (Left) + (Right) + Home + End Navigate left/right - Esc + Esc Abort the edit and quit the editor - Ctrl + k + Ctrlk Clear the command line - Ctrl + w - Alt + Backspace + Ctrlw + AltBackspace Delete word backwards - Alt + d + Altd Delete word forwards - ↵ (Enter) + (Enter) Boot entry with the edited command line