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-VERSIONKERNEL-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
+ EndNavigate up/down in the entry list
- ↵ (Enter)
+ ↵ (Enter)Boot selected entry
- d
+ dMake selected entry the default
- e
+ eEdit the kernel command line for selected entry
- +
- t
+ +
+ tIncrease the timeout before default entry is booted
- -
- T
+ -
+ TDecrease the timeout
- v
+ vShow systemd-boot, UEFI, and firmware versions
- P
+ PPrint status
- Q
+ QQuit
- h
- ?
+ h
+ ?Show a help screen
- Ctrl + l
+ CtrllReprint the screen
@@ -145,35 +145,35 @@
- l
+ lLinux
- w
+ wWindows
- a
+ aOS X
- s
+ sEFI shell
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9Boot entry number 1 … 9
@@ -183,36 +183,36 @@
- ← (Left)
- → (Right)
- Home
- End
+ ← (Left)
+ → (Right)
+ Home
+ EndNavigate left/right
- Esc
+ EscAbort the edit and quit the editor
- Ctrl + k
+ CtrlkClear the command line
- Ctrl + w
- Alt + Backspace
+ Ctrlw
+ AltBackspaceDelete word backwards
- Alt + d
+ AltdDelete 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 Alsobootctl1,
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