Merge pull request #9437 from poettering/sd-boot-count

many sd-boot/bootctl fixes, and a new "boot counting" concept, for automatic fallback to older kernels on persistent failures
This commit is contained in:
Lennart Poettering 2018-10-19 23:04:22 +02:00 committed by GitHub
commit a2689fa5fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1513 additions and 81 deletions

20
TODO
View File

@ -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

View File

@ -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.

View File

@ -63,49 +63,61 @@
<varlistentry>
<term><command>add <replaceable>KERNEL-VERSION</replaceable> <replaceable>KERNEL-IMAGE</replaceable></command></term>
<listitem>
<para><command>kernel-install</command> creates the directory
<para>This command expects a kernel version string and a path to a kernel image file as
arguments. <command>kernel-install</command> creates the directory
<filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename>
and calls executables from
<filename>/usr/lib/kernel/install.d/*.install</filename> and
<filename>/etc/kernel/install.d/*.install</filename> with
the arguments
<programlisting>add <replaceable>KERNEL-VERSION</replaceable> \
<filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename> <replaceable>KERNEL-IMAGE</replaceable></programlisting>
and calls the executables from <filename>/usr/lib/kernel/install.d/*.install</filename> and
<filename>/etc/kernel/install.d/*.install</filename> with the following arguments:
<programlisting>add <replaceable>KERNEL-VERSION</replaceable> <filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename> <replaceable>KERNEL-IMAGE</replaceable></programlisting>
</para>
<para>The kernel-install plugin <filename>50-depmod.install</filename> runs depmod for the <replaceable>KERNEL-VERSION</replaceable>.</para>
<para>Two default plugins execute the following operations in this case:</para>
<para>The kernel-install plugin
<filename>90-loaderentry.install</filename> copies
<replaceable>KERNEL-IMAGE</replaceable> to
<filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/linux</filename>.
It also creates a boot loader entry according to the boot
loader specification in
<filename>/boot/loader/entries/<replaceable>MACHINE-ID</replaceable>-<replaceable>KERNEL-VERSION</replaceable>.conf</filename>.
The title of the entry is the
<replaceable>PRETTY_NAME</replaceable> parameter specified
in <filename>/etc/os-release</filename> or
<filename>/usr/lib/os-release</filename> (if the former is
missing), or "Linux
<replaceable>KERNEL-VERSION</replaceable>", if unset. If
the file <filename>initrd</filename> is found next to the
<filename>linux</filename> file, the initrd will be added to
the configuration.</para>
<itemizedlist>
<listitem><para><filename>50-depmod.install</filename> runs
<citerefentry><refentrytitle>depmod</refentrytitle><manvolnum>8</manvolnum></citerefentry> for the
<replaceable>KERNEL-VERSION</replaceable>.</para></listitem>
<listitem><para><filename>90-loaderentry.install</filename> copies <replaceable>KERNEL-IMAGE</replaceable>
to
<filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/linux</filename>.
It also creates a boot loader entry according to the <ulink
url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink> in
<filename>/boot/loader/entries/<replaceable>MACHINE-ID</replaceable>-<replaceable>KERNEL-VERSION</replaceable>.conf</filename>.
The title of the entry is the <replaceable>PRETTY_NAME</replaceable> parameter specified in
<filename>/etc/os-release</filename> or <filename>/usr/lib/os-release</filename> (if the former is
missing), or "Linux <replaceable>KERNEL-VERSION</replaceable>", if unset. If the file
<filename>initrd</filename> is found next to the kernel image file, the initrd will be added to the
configuration.</para></listitem>
</itemizedlist>
</listitem>
</varlistentry>
<varlistentry>
<term><command>remove <replaceable>KERNEL-VERSION</replaceable></command></term>
<listitem>
<para>Calls executables from <filename>/usr/lib/kernel/install.d/*.install</filename>
and <filename>/etc/kernel/install.d/*.install</filename> with the arguments
<para>This command expects a kernel version string as single argument. This calls executables from
<filename>/usr/lib/kernel/install.d/*.install</filename> and
<filename>/etc/kernel/install.d/*.install</filename> with the following arguments:
<programlisting>remove <replaceable>KERNEL-VERSION</replaceable> <filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename></programlisting>
</para>
<para><command>kernel-install</command> removes the entire directory
<filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename> afterwards.</para>
<para>Afterwards, <command>kernel-install</command> removes the directory
<filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename>
and its contents.</para>
<para>Two default plugins execute the following operations in this case:</para>
<itemizedlist>
<listitem><para><filename>50-depmod.install</filename> removes the files generated by <command>depmod</command> for this kernel again.</para></listitem>
<listitem><para><filename>90-loaderentry.install</filename> removes the file
<filename>/boot/loader/entries/<replaceable>MACHINE-ID</replaceable>-<replaceable>KERNEL-VERSION</replaceable>.conf</filename>.</para></listitem>
</itemizedlist>
<para>The kernel-install plugin <filename>90-loaderentry.install</filename> removes the file
<filename>/boot/loader/entries/<replaceable>MACHINE-ID</replaceable>-<replaceable>KERNEL-VERSION</replaceable>.conf</filename>.</para>
</listitem>
</varlistentry>
@ -136,8 +148,22 @@
<filename>/proc/cmdline</filename>
</term>
<listitem>
<para>The content of the file <filename>/etc/kernel/cmdline</filename> specifies the kernel command line to use.
If that file does not exist, <filename>/proc/cmdline</filename> is used.</para>
<para>Read by <filename>90-loaderentry.install</filename>. The content of the file
<filename>/etc/kernel/cmdline</filename> specifies the kernel command line to use. If that file does not
exist, <filename>/proc/cmdline</filename> is used.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<filename>/etc/kernel/tries</filename>
</term>
<listitem>
<para>Read by <filename>90-loaderentry.install</filename>. 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
<filename>/boot/loader/entries/<replaceable>MACHINE-ID</replaceable>-<replaceable>KERNEL-VERSION</replaceable>+<replaceable>TRIES</replaceable>.conf</filename>. This
is useful for boot loaders such as
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> which
implement boot attempt counting with a counter embedded in the entry file name.</para>
</listitem>
</varlistentry>
<varlistentry>
@ -165,6 +191,8 @@
<para>
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>depmod</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>
</para>
</refsect1>

View File

@ -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', [], ''],

View File

@ -0,0 +1,49 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1+
-->
<refentry id="systemd-bless-boot-generator" conditional='ENABLE_EFI'>
<refentryinfo>
<title>systemd-bless-boot-generator</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-bless-boot-generator</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-bless-boot-generator</refname>
<refpurpose>Pull <filename>systemd-bless-boot.service</filename> into the initial boot transaction when boot counting is in effect.</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>/usr/lib/systemd/system-generators/systemd-bless-boot-generator</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-bless-boot-generator</filename> is a generator that pulls
<filename>systemd-bless-boot.service</filename> into the initial boot transaction when boot counting, as
implemented by <citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>, is
enabled.</para>
<para><filename>systemd-bless-boot-generator</filename> implements
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -0,0 +1,115 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1+
-->
<refentry id="systemd-bless-boot.service" conditional='ENABLE_EFI'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-bless-boot.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-bless-boot.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-bless-boot.service</refname>
<refpurpose>Mark current boot process as successful</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-bless-boot.service</filename></para>
<para><filename>/usr/lib/systemd/system-bless-boot</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-bless-boot.service</filename> is a system service that marks the current boot process as
successful. It's automatically pulled into the initial transaction when
<citerefentry><refentrytitle>systemd-bless-boot-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
detects that <citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> style
boot counting is used.</para>
<para>Internally, the service operates based on the <varname>LoaderBootCountPath</varname> EFI variable (of the
vendor UUID <constant>4a67b082-0a4c-41cf-b6c7-440b29bb8c4</constant>), which is passed from the boot loader to the
OS. It contains a file system path (relative to the EFI system partition) of the <ulink
url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink> compliant boot loader entry
file or unified kernel image file that was used to boot up the
system. <command>systemd-bless-boot.service</command> 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.)</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The <filename>/usr/lib/systemd/system-bless-boot</filename> executable may also be invoked from the
command line, taking one of the following command arguments:</para>
<variablelist>
<varlistentry>
<term><option>status</option></term>
<listitem><para>The current status of the boot loader entry file or unified kernel image file is shown. This
outputs one of <literal>good</literal>, <literal>bad</literal>, <literal>indeterminate</literal>,
<literal>clean</literal>, depending on the state and previous invocations of the command. The string
<literal>indeterminate</literal> is shown initially after boot, before it has been marked as "good" or
"bad". The string <literal>good</literal> is shown after the boot was marked as "good" with the
<option>good</option> command below, and "bad" conversely after the <option>bad</option> command was
invoked. The string <literal>clean</literal> is returned when boot counting is currently not in effect.</para>
<para>This command is implied if no command argument is specified.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>good</option></term>
<listitem><para>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 <filename>systemd-bless-boot.service</filename> unit invokes this
command.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>bad</option></term>
<listitem><para>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.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>indeterminate</option></term>
<listitem><para>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 <varname>LoaderBootCountPath</varname> EFI variable.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -0,0 +1,54 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1+
-->
<refentry id="systemd-boot-check-no-failures.service"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-boot-check-no-failures.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-boot-check-no-failures.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-boot-check-no-failures.service</refname>
<refpurpose>verify that the system booted up cleanly</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-boot-check-no-failures.service</filename></para>
<para><filename>/usr/lib/systemd/system-boot-check-no-failures</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-boot-check-no-failures.service</filename> 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
<filename>boot-complete.target</filename>, thus ensuring the target cannot be reached when the system booted up
with failed services.</para>
<para>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
<filename>boot-complete.target</filename>.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -75,67 +75,67 @@
<variablelist>
<varlistentry>
<term>↑ (Up)</term>
<term>↓ (Down)</term>
<term>j</term>
<term>k</term>
<term>PageUp</term>
<term>PageDown</term>
<term>Home</term>
<term>End</term>
<term><keycap></keycap> (Up)</term>
<term><keycap></keycap> (Down)</term>
<term><keycap>j</keycap></term>
<term><keycap>k</keycap></term>
<term><keycap>PageUp</keycap></term>
<term><keycap>PageDown</keycap></term>
<term><keycap>Home</keycap></term>
<term><keycap>End</keycap></term>
<listitem><para>Navigate up/down in the entry list</para></listitem>
</varlistentry>
<varlistentry>
<term>↵ (Enter)</term>
<term><keycap></keycap> (Enter)</term>
<listitem><para>Boot selected entry</para></listitem>
</varlistentry>
<varlistentry>
<term>d</term>
<term><keycap>d</keycap></term>
<listitem><para>Make selected entry the default</para></listitem>
</varlistentry>
<varlistentry>
<term>e</term>
<term><keycap>e</keycap></term>
<listitem><para>Edit the kernel command line for selected entry</para></listitem>
</varlistentry>
<varlistentry>
<term>+</term>
<term>t</term>
<term><keycap>+</keycap></term>
<term><keycap>t</keycap></term>
<listitem><para>Increase the timeout before default entry is booted</para></listitem>
</varlistentry>
<varlistentry>
<term>-</term>
<term>T</term>
<term><keycap>-</keycap></term>
<term><keycap>T</keycap></term>
<listitem><para>Decrease the timeout</para></listitem>
</varlistentry>
<varlistentry>
<term>v</term>
<term><keycap>v</keycap></term>
<listitem><para>Show systemd-boot, UEFI, and firmware versions</para></listitem>
</varlistentry>
<varlistentry>
<term>P</term>
<term><keycap>P</keycap></term>
<listitem><para>Print status</para></listitem>
</varlistentry>
<varlistentry>
<term>Q</term>
<term><keycap>Q</keycap></term>
<listitem><para>Quit</para></listitem>
</varlistentry>
<varlistentry>
<term>h</term>
<term>?</term>
<term><keycap>h</keycap></term>
<term><keycap>?</keycap></term>
<listitem><para>Show a help screen</para></listitem>
</varlistentry>
<varlistentry>
<term>Ctrl + l</term>
<term><keycombo><keycap>Ctrl</keycap><keycap>l</keycap></keycombo></term>
<listitem><para>Reprint the screen</para></listitem>
</varlistentry>
</variablelist>
@ -145,35 +145,35 @@
<variablelist>
<varlistentry>
<term>l</term>
<term><keycap>l</keycap></term>
<listitem><para>Linux</para></listitem>
</varlistentry>
<varlistentry>
<term>w</term>
<term><keycap>w</keycap></term>
<listitem><para>Windows</para></listitem>
</varlistentry>
<varlistentry>
<term>a</term>
<term><keycap>a</keycap></term>
<listitem><para>OS X</para></listitem>
</varlistentry>
<varlistentry>
<term>s</term>
<term><keycap>s</keycap></term>
<listitem><para>EFI shell</para></listitem>
</varlistentry>
<varlistentry>
<term>1</term>
<term>2</term>
<term>3</term>
<term>4</term>
<term>5</term>
<term>6</term>
<term>7</term>
<term>8</term>
<term>9</term>
<term><keycap>1</keycap></term>
<term><keycap>2</keycap></term>
<term><keycap>3</keycap></term>
<term><keycap>4</keycap></term>
<term><keycap>5</keycap></term>
<term><keycap>6</keycap></term>
<term><keycap>7</keycap></term>
<term><keycap>8</keycap></term>
<term><keycap>9</keycap></term>
<listitem><para>Boot entry number 1 … 9</para></listitem>
</varlistentry>
</variablelist>
@ -183,36 +183,36 @@
<variablelist>
<varlistentry>
<term>← (Left)</term>
<term>→ (Right)</term>
<term>Home</term>
<term>End</term>
<term><keycap></keycap> (Left)</term>
<term><keycap></keycap> (Right)</term>
<term><keycap>Home</keycap></term>
<term><keycap>End</keycap></term>
<listitem><para>Navigate left/right</para></listitem>
</varlistentry>
<varlistentry>
<term>Esc</term>
<term><keycap>Esc</keycap></term>
<listitem><para>Abort the edit and quit the editor</para></listitem>
</varlistentry>
<varlistentry>
<term>Ctrl + k</term>
<term><keycombo><keycap>Ctrl</keycap><keycap>k</keycap></keycombo></term>
<listitem><para>Clear the command line</para></listitem>
</varlistentry>
<varlistentry>
<term>Ctrl + w</term>
<term>Alt + Backspace</term>
<term><keycombo><keycap>Ctrl</keycap><keycap>w</keycap></keycombo></term>
<term><keycombo><keycap>Alt</keycap><keycap>Backspace</keycap></keycombo></term>
<listitem><para>Delete word backwards</para></listitem>
</varlistentry>
<varlistentry>
<term>Alt + d </term>
<term><keycombo><keycap>Alt</keycap><keycap>d</keycap></keycombo></term>
<listitem><para>Delete word forwards</para></listitem>
</varlistentry>
<varlistentry>
<term>↵ (Enter)</term>
<term><keycap></keycap> (Enter)</term>
<listitem><para>Boot entry with the edited command line</para></listitem>
</varlistentry>
</variablelist>
@ -237,11 +237,168 @@
Loader Specification</ulink> are read from <filename>/EFI/Linux/</filename> on the ESP.</para>
</refsect1>
<refsect1>
<title>EFI Variables</title>
<para>The following EFI variables are defined, set and read by <command>systemd-boot</command>, under the vendor
UUID <literal>4a67b082-0a4c-41cf-b6c7-440b29bb8c4</literal>, for communication between the OS and the boot
loader:</para>
<variablelist>
<varlistentry>
<term><varname>LoaderBootCountPath</varname></term>
<listitem><para>If boot counting is enabled, contains the path to the file in whose name the boot counters are
encoded. Set by the boot
loader. <citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
uses this information to mark a boot as successful as determined by the successful activation of the
<filename>boot-complete.target</filename> target unit.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderConfigTimeout</varname></term>
<listitem><para>The menu time-out. Read by the boot loader. (Also, modified by it when the
<keycap>t</keycap>/<keycap>T</keycap> keys are used, see above.)</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderDevicePartUUID</varname></term>
<listitem><para>Contains the partition UUID of the EFI System Partition the boot loader was run from. Set by
the boot
loader. <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
uses this information to automatically find the disk booted from, in order to discover various other partitions
on the same disk automatically.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderEntries</varname></term>
<listitem><para>A list of the identifiers of all discovered boot loader entries. Set by the boot
loader.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderEntryDefault</varname></term>
<term><varname>LoaderEntryOneShot</varname></term>
<listitem><para>The identifier of the default boot loader entry. Set primarily by the OS and read by the boot
loader. <varname>LoaderEntryOneShot</varname> sets the default entry for the next boot only, while
<varname>LoaderEntryDefault</varname> sets it persistently for all future
boots. <citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
<option>set-default</option> and <option>set-oneshot</option> commands make use of these variables. The boot
loader modifies <varname>LoaderEntryDefault</varname> on request, when the <keycap>d</keycap> key is used, see
above.)</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderEntrySelected</varname></term>
<listitem><para>The identifier of the boot loader entry currently being booted. Set by the boot
loader.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderFirmwareInfo</varname></term>
<term><varname>LoaderFirmwareType</varname></term>
<listitem><para>Brief firmware information. Set by the boot loader. Use
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> to view this
data.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderImageIdentifier</varname></term>
<listitem><para>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
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> to view this
data.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderInfo</varname></term>
<listitem><para>Brief information about the boot loader. Set by the boot loader. Use
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> to view this
data.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderTimeExecUSec</varname></term>
<term><varname>LoaderTimeInitUSec</varname></term>
<term><varname>LoaderTimeMenuUsec</varname></term>
<listitem><para>Information about the time spent in various parts of the boot loader. Set by the boot
loader. Use <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
to view this data. These variables are defined by the <ulink
url="https://www.freedesktop.org/wiki/Software/systemd/BootLoaderInterface">Boot Loader
Interface</ulink>.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Boot Counting</title>
<para><command>systemd-boot</command> implements a simple boot counting mechanism on top of the <ulink
url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>, 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 <literal>+</literal> followed by one or two numbers (if
two they need to be separated by a <literal>-</literal>), before the <filename>.conf</filename> or
<filename>.efi</filename> 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:</para>
<orderedlist>
<listitem><para>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.</para></listitem>
<listitem><para>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.</para></listitem>
<listitem><para>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
<citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
service moves the currently booted entry from 'indeterminate' into 'good' state when a boot attempt completed
successfully.</para></listitem>
</orderedlist>
<para>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).</para>
<para>Example: let's say a boot loader entry file <filename>foo.conf</filename> is set up for 3 boot tries. The
installer will hence create it under the name <filename>foo+3.conf</filename>. On first boot, the boot loader will
rename it to <filename>foo+2-1.conf</filename>. If that boot does not complete successfully, the boot loader will
rename it to <filename>foo+1-2.conf</filename> on the following boot. If that fails too, it will finally be renamed
<filename>foo+0-3.conf</filename> 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 <filename>foo.conf</filename> by the OS, so that it is
considered 'good' from then on.</para>
<para>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.</para>
<para>The <citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry> kernel
install framework optionally sets the initial 'tries left' counter to the value specified in
<filename>/etc/kernel/tries</filename> when a boot loader entry is first created.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>loader.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>,
<ulink url="https://www.freedesktop.org/wiki/Software/systemd/BootLoaderInterface">Boot Loader Interface</ulink>
</para>

View File

@ -29,6 +29,7 @@
<filename>cryptsetup-pre.target</filename>,
<filename>cryptsetup.target</filename>,
<filename>ctrl-alt-del.target</filename>,
<filename>boot-complete.target</filename>,
<filename>default.target</filename>,
<filename>emergency.target</filename>,
<filename>exit.target</filename>,
@ -144,7 +145,28 @@
<citerefentry><refentrytitle>bootup</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for details on the targets involved.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>boot-complete.target</filename></term>
<listitem>
<para>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 <varname>Requires=</varname> 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 <varname>Requires=</varname>. 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.</para>
<para>See
<citerefentry><refentrytitle>systemd-boot-check-no-failures.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for a service that implements a generic system health check and orders itself before
<filename>boot-complete.target</filename>.</para>
<para>See
<citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for a service that propagates boot success information to the boot loader, and orders itself after
<filename>boot-complete.target</filename>.</para>
</listitem>
</varlistentry>
<varlistentry>

View File

@ -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],

View File

@ -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;

View File

@ -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);

View File

@ -0,0 +1,75 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#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;
}

476
src/boot/bless-boot.c Normal file
View File

@ -0,0 +1,476 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <getopt.h>
#include <stdlib.h>
#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;
}

View File

@ -0,0 +1,106 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#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;
}

View File

@ -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 </etc/kernel/tries
if ! [[ "$TRIES" =~ ^[0-9]+$ ]] ; then
echo "/etc/kernel/tries does not contain an integer." >&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" || {

View File

@ -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

View File

@ -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/'],

View File

@ -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

View File

@ -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