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 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. 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 diff --git a/man/rules/meson.build b/man/rules/meson.build index 3602bbaa1a..b3011c5f04 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -617,6 +617,9 @@ 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'], ['systemd-cat', '1', [], ''], ['systemd-cgls', '1', [], ''], 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 + + + + 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 + + + + 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 + + + + diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index c3b34e54c9..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 @@ -237,11 +237,168 @@ 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. + + + + + + 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 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. diff --git a/meson.build b/meson.build index a47d7f9370..7960e97893 100644 --- a/meson.build +++ b/meson.build @@ -1796,8 +1796,34 @@ 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) + + 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 +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/basic/fs-util.c b/src/basic/fs-util.c index 91e83962b8..fd19518271 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-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; +} 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/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/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" || { 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..d69508467f 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/'], @@ -135,6 +136,8 @@ in_units = [ ['systemd-backlight@.service', 'ENABLE_BACKLIGHT'], ['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-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 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