]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #11770 from yuwata/fix-9955
authorLennart Poettering <lennart@poettering.net>
Mon, 4 Mar 2019 11:11:07 +0000 (12:11 +0100)
committerGitHub <noreply@github.com>
Mon, 4 Mar 2019 11:11:07 +0000 (12:11 +0100)
network: rework address pool

62 files changed:
TODO
docs/BOOT_LOADER_SPECIFICATION.md
man/bootctl.xml
man/kernel-command-line.xml
man/systemd-boot.xml
man/systemd-fstab-generator.xml
man/systemd-gpt-auto-generator.xml
man/systemd-nspawn.xml
man/systemd-resolved.service.xml
mkosi.build
semaphoreci/gcc-compilation.sh
semaphoreci/setup.sh
src/basic/btrfs-util.c
src/basic/copy.c
src/basic/copy.h
src/basic/env-file.c
src/basic/fs-util.c
src/basic/fs-util.h
src/basic/log.c
src/basic/log.h
src/boot/bless-boot.c
src/boot/bootctl.c
src/boot/efi/boot.c
src/boot/efi/crc32.c [new file with mode: 0644]
src/boot/efi/crc32.h [new file with mode: 0644]
src/boot/efi/meson.build
src/boot/efi/stub.c
src/boot/efi/util.c
src/boot/efi/util.h
src/fstab-generator/fstab-generator.c
src/fuzz/fuzz-dhcp6-client.c
src/fuzz/fuzz-dhcp6-client.options [new file with mode: 0644]
src/gpt-auto-generator/gpt-auto-generator.c
src/import/export-raw.c
src/import/import-raw.c
src/import/pull-raw.c
src/libsystemd/sd-bus/bus-internal.h
src/libsystemd/sd-bus/bus-message.c
src/libsystemd/sd-bus/bus-message.h
src/libsystemd/sd-bus/bus-socket.c
src/libsystemd/sd-bus/sd-bus.c
src/libsystemd/sd-bus/test-bus-address.c
src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c [new file with mode: 0644]
src/login/71-seat.rules.in
src/nspawn/nspawn-mount.c
src/nspawn/nspawn-mount.h
src/nspawn/nspawn.c
src/shared/bootspec.c
src/shared/bootspec.h
src/shared/dissect-image.c
src/shared/dissect-image.h
src/shared/gpt.h
src/shared/json.c
src/shared/machine-image.c
src/shared/meson.build
src/shared/pe-header.h [new file with mode: 0644]
src/shared/volatile-util.c
src/shared/volatile-util.h
src/systemctl/systemctl.c
src/test/meson.build
src/test/test-log.c
src/volatile-root/volatile-root.c

diff --git a/TODO b/TODO
index b4e0cbb88a4518ec130cc573ac01630e538f90bf..0178d431d5b16ae31e99243384b538fb4fdb7782 100644 (file)
--- a/TODO
+++ b/TODO
@@ -51,8 +51,6 @@ Features:
 * When logind.conf contains HandleLidSwitch=suspend-then-hibernate and we can't
   hibernate because the swap partition isn't large enough, still suspend
 
-* bootctl: implement Type #2 boot loader entry discovery
-
 * bootctl,sd-boot: actually honour the "architecture" key
 
 * when a socket unit is spawned with an AF_UNIX path in /var/run, complain and
@@ -90,23 +88,17 @@ 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 set a special xattr on cgroups that have delegate=yes set, to make it
   easy to mark cut points
 
index 3612ff1d331519906a7f9d88cfd6697966d96a78..f4d7f211b6a08161a2c129ea000a35c678442fed 100644 (file)
@@ -42,9 +42,9 @@ Everything described below is located on a placeholder file system `$BOOT`. The
   * If the OS is installed on a disk with MBR disk label, and a partition with the MBR type id of 0xEA already exists it should be used as `$BOOT`.
   * Otherwise, if the OS is installed on a disk with MBR disk label, a new partition with MBR type id of 0xEA shall be created, of a suitable size (let's say 500MB), and it should be used as `$BOOT`.
 * On disks with GPT disk labels
-  * If the OS is installed on a disk with GPT disk label, and a partition with the GPT type GUID of bc13c2ff-59e6-4262-a352-b275fd6f7172 already exists, it should be used as `$BOOT`.
-  * Otherwise, if the OS is installed on a disk with GPT disk label, and an ESP partition (i.e. with the GPT type UID of c12a7328-f81f-11d2-ba4b-00a0c93ec93b) already exists and is large enough (let's say 250MB) and otherwise qualifies, it should be used as `$BOOT`.
-  * Otherwise, if the OS is installed on a disk with GPT disk label, and if the ESP partition already exists but is too small, a new suitably sized (let's say 500MB) partition with GPT type GUID of bc13c2ff-59e6-4262-a352-b275fd6f7172 shall be created and it should be used as `$BOOT`.
+  * If the OS is installed on a disk with GPT disk label, and a partition with the GPT type GUID of `bc13c2ff-59e6-4262-a352-b275fd6f7172` already exists, it should be used as `$BOOT`.
+  * Otherwise, if the OS is installed on a disk with GPT disk label, and an ESP partition (i.e. with the GPT type UID of `c12a7328-f81f-11d2-ba4b-00a0c93ec93b`) already exists and is large enough (let's say 250MB`) and otherwise qualifies, it should be used as `$BOOT`.
+  * Otherwise, if the OS is installed on a disk with GPT disk label, and if the ESP partition already exists but is too small, a new suitably sized (let's say 500MB) partition with GPT type GUID of `bc13c2ff-59e6-4262-a352-b275fd6f7172` shall be created and it should be used as `$BOOT`.
   * Otherwise, if the OS is installed on a disk with GPT disk label, and no ESP partition exists yet, a new suitably sized (let's say 500MB) ESP should be created and should be used as `$BOOT`.
 
 This placeholder file system shall be determined during _installation time_, and an fstab entry may be created. It should be mounted to either `/boot/` or `/efi/`. Additional locations like `/boot/efi/`, with `/boot/` being a separate file system, might be supported by implementations. This is not recommended because the mounting of `$BOOT` is then dependent on and requires the mounting of the intermediate file system.
@@ -80,7 +80,7 @@ We define two directories below `$BOOT`:
 
 Inside the `$BOOT/loader/entries/` directory each OS vendor may drop one or more configuration snippets with the suffix ".conf", one for each boot menu item. The file name of the file is used for identification of the boot item but shall never be presented to the user in the UI. The file name may be chosen freely but should be unique enough to avoid clashes between OS installations. More specifically it is suggested to include the machine ID (`/etc/machine-id` or the D-Bus machine ID for OSes that lack `/etc/machine-id`), the kernel version (as returned by `uname -r`) and an OS identifier (The ID field of `/etc/os-release`). Example: `$BOOT/loader/entries/6a9857a393724b7a981ebb5b8495b9ea-3.8.0-2.fc19.x86_64.conf`.
 
-These configuration snippets shall be Unix-style text files (i.e. line separation with a single newline character), in the UTF-8 encoding. The configuration snippets are loosely inspired on Grub1's configuration syntax. Lines beginning with '#' shall be ignored and used for commenting. The first word of a line is used as key and shall be separated by a space from its value. The following keys are known:
+These configuration snippets shall be Unix-style text files (i.e. line separation with a single newline character), in the UTF-8 encoding. The configuration snippets are loosely inspired on Grub1's configuration syntax. Lines beginning with '#' shall be ignored and used for commenting. The first word of a line is used as key and shall be separated by one or more spaces from its value. The following keys are known:
 
 * `title` shall contain a human readable title string for this menu item. This will be displayed in the boot menu for the item. It is a good idea to initialize this from the `PRETTY_NAME` of `/etc/os-release`. This name should be descriptive and does not have to be unique. If a boot loader discovers two entries with the same title it is a good idea to show more than just the raw title in the UI, for example by appending the `version` field. This field is optional. Example: "Fedora 18 (Spherical Cow)".
 * `version` shall contain a human readable version string for this menu item. This is usually the kernel version and is intended for use by OSes to install multiple kernel versions at the same time with the same `title` field. This field shall be in a syntax that is useful for Debian-style version sorts, so that the boot loader UI can determine the newest version easily and show it first or preselect it automatically. This field is optional. Example: `3.7.2-201.fc18.x86_64`.
index 9cfa9cccdf4bb7fd34b5d220d18983b8253dc6eb..41165cd09a5e9881ce24aba3ac87ea8652884be2 100644 (file)
 
     <variablelist>
       <varlistentry>
-        <term><option>--path=</option></term>
-        <listitem><para>Path to the EFI System Partition (ESP). If not specified, <filename>/efi</filename>,
-        <filename>/boot</filename>, and <filename>/boot/efi</filename> are checked in turn.  It is recommended to mount
-        the ESP to <filename>/boot</filename>, if possible.</para></listitem>
+        <term><option>--esp-path=</option></term>
+        <listitem><para>Path to the EFI System Partition (ESP). If not specified, <filename>/efi/</filename>,
+        <filename>/boot/</filename>, and <filename>/boot/efi</filename> are checked in turn.  It is recommended to mount
+        the ESP to <filename>/efi/</filename>, if possible.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--boot-path=</option></term>
+        <listitem><para>Path to the Extended Boot Loader partition, as defined in the <ulink
+        url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>. If not
+        specified, <filename>/boot/</filename> are checked.  It is recommended to mount the Extended Boot
+        Loader partition to <filename>/boot/</filename>, if possible.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><option>-p</option></term>
-        <term><option>--print-path</option></term>
-        <listitem><para>This option modifies the behaviour of <command>status</command>.
-        Just print the path to the EFI System Partition (ESP) to standard output and
-        exit.</para></listitem>
+        <term><option>--print-esp-path</option></term>
+        <listitem><para>This option modifies the behaviour of <command>status</command>.  Prints only the
+        path to the EFI System Partition (ESP) to standard output and exits.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--print-boot-path</option></term>
+        <listitem><para>This option modifies the behaviour of <command>status</command>.  Prints only the
+        path to the Extended Boot Loader partition if it exists, and the path to the ESP otherwise to
+        standard output and exit. This command is useful to determine where to place boot loader entries, as
+        they are preferably placed in the Extended Boot Loader partition if it exists and in the ESP
+        otherwise.</para></listitem>
       </varlistentry>
 
       <varlistentry>
       <varlistentry>
         <term><option>install</option></term>
 
-        <listitem><para>Installs systemd-boot into the EFI system partition. A copy of <command>systemd-boot</command>
-        will be stored as the EFI default/fallback loader at
-        <filename><replaceable>ESP</replaceable>/EFI/BOOT/BOOT*.EFI</filename>. The boot loader is then added to the
-        top of the firmware's boot loader list.</para></listitem>
+        <listitem><para>Installs <command>systemd-boot</command> into the EFI system partition. A copy of
+        <command>systemd-boot</command> will be stored as the EFI default/fallback loader at
+        <filename><replaceable>ESP</replaceable>/EFI/BOOT/BOOT*.EFI</filename>. The boot loader is then added
+        to the top of the firmware's boot loader list.</para></listitem>
       </varlistentry>
 
       <varlistentry>
 
   <refsect1>
     <title>Environment</title>
-    <para>If <varname>$SYSTEMD_RELAX_ESP_CHECKS=1</varname> is set the validation checks for the ESP are relaxed, and
-    the path specified with <option>--path=</option> may refer to any kind of file system on any kind of
-    partition.</para>
+    <para>If <varname>$SYSTEMD_RELAX_ESP_CHECKS=1</varname> is set the validation checks for the ESP are
+    relaxed, and the path specified with <option>--esp-path=</option> may refer to any kind of file system on
+    any kind of partition.</para>
+
+    <para>Similarly, <varname>$SYSTEMD_RELAX_XBOOTLDR_CHECKS=1</varname> turns off some validation checks for
+    the Extended Boot Loader partition.</para>
   </refsect1>
 
   <refsect1>
index e39f108d8f4975ae1c5db90cb0b3b441c8613745..6e791192b2bbe7d9d02766f8cf665d6b7f9a1bf0 100644 (file)
           enables fully state-less boots were the vendor-supplied OS is used as shipped, with only default
           configuration and no stored state in effect, as <filename>/etc</filename> and <filename>/var</filename> (as
           well as all other resources shipped in the root file system) are reset at boot and lost on shutdown. If this
-          setting is set to <literal>state</literal> the root file system is mounted as usual, however
+          setting is set to <literal>state</literal> the root file system is mounted read-only, however
           <filename>/var</filename> is mounted as a volatile memory file system (<literal>tmpfs</literal>), so that the
-          system boots up with the normal configuration applied, but all state reset at boot and lost at shutdown. For details,
-          see
+          system boots up with the normal configuration applied, but all state reset at boot and lost at shutdown. If
+          this setting is set to <literal>overlay</literal> the root file system is set up as
+          <literal>overlayfs</literal> mount combining the read-only root directory with a writable
+          <literal>tmpfs</literal>, so that no modifications are made to disk, but the file system may be modified
+          nonetheless with all changes being lost at reboot. For details, see
           <citerefentry><refentrytitle>systemd-volatile-root.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
           and
           <citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
index 4c914e615664dc5b99e18e5c3af7c633d3dc72ec..0ec7a47b51ecd6922b2b4a9326378c3794b243ba 100644 (file)
   <refsect1>
     <title>Description</title>
 
-    <para><command>systemd-boot</command> (short: <command>sd-boot</command>) is a simple UEFI boot manager. It
-    provides a graphical menu to select the entry to boot and an editor for the kernel command line. systemd-boot
-    supports systems with UEFI firmware only.</para>
+    <para><command>systemd-boot</command> (short: <command>sd-boot</command>) is a simple UEFI boot
+    manager. It provides a graphical menu to select the entry to boot and an editor for the kernel command
+    line. <command>systemd-boot</command> supports systems with UEFI firmware only.</para>
 
     <para>systemd-boot loads boot entry information from the EFI system partition (ESP), usually mounted at
-    <filename>/boot</filename>, <filename>/efi</filename>, or <filename>/boot/efi</filename> during OS
-    runtime. Configuration file fragments, kernels, initrds and other EFI images to boot generally need to reside on
-    the ESP. Linux kernels must be built with <option>CONFIG_EFI_STUB</option> to be able to be directly executed as an
-    EFI image. During boot systemd-boot automatically assembles a list of boot entries from the following
-    sources:</para>
+    <filename>/efi/</filename>, <filename>/boot/</filename>, or <filename>/boot/efi/</filename> during OS
+    runtime, as well as from the Extended Boot Loader partition if it exists (usually mounted to
+    <filename>/boot/</filename>). Configuration file fragments, kernels, initrds and other EFI images to boot
+    generally need to reside on the ESP or the Extended Boot Loader partition. Linux kernels must be built
+    with <option>CONFIG_EFI_STUB</option> to be able to be directly executed as an EFI image. During boot
+    systemd-boot automatically assembles a list of boot entries from the following sources:</para>
 
     <itemizedlist>
       <listitem><para>Boot entries defined with <ulink
-      url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader
-      Specification</ulink> description files located in <filename>/loader/entries/</filename> on the ESP. These
-      usually describe Linux kernel images with associated initrd images, but alternatively may also describe
-      arbitrary other EFI executables.</para></listitem>
+      url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink> description files
+      located in <filename>/loader/entries/</filename> on the ESP and the Extended Boot Loader
+      Partition. These usually describe Linux kernel images with associated initrd images, but alternatively
+      may also describe arbitrary other EFI executables.</para></listitem>
 
       <listitem><para>Unified kernel images following the <ulink
-      url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader
-      Specification</ulink>, as executable EFI binaries in <filename>/EFI/Linux/</filename> on the ESP.
+      url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>, as executable EFI
+      binaries in <filename>/EFI/Linux/</filename> on the ESP and the Extended Boot Loader Partition.
       </para></listitem>
 
       <listitem><para>The Microsoft Windows EFI boot manager, if installed</para></listitem>
       <listitem><para>A reboot into the UEFI firmware setup option, if supported by the firmware</para></listitem>
     </itemizedlist>
 
-    <para><citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry> may be
-    used to copy kernel images onto the ESP and to generate description files compliant with the Boot Loader
-    Specification. <citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> may be
-    used from a running system to locate the ESP, list available entries, and install systemd-boot itself.</para>
+    <para><citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    may be used to copy kernel images onto the ESP or the Extended Boot Loader Partition and to generate
+    description files compliant with the Boot Loader
+    Specification. <citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    may be used from a running system to locate the ESP and the Extended Boot Loader Partition, list
+    available entries, and install <command>systemd-boot</command> itself.</para>
 
     <para>systemd-boot will provide information about the time spent in UEFI firmware using the <ulink
     url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>. This information can be displayed
   <refsect1>
     <title>Files</title>
 
-    <para>The files systemd-boot reads generally reside on the UEFI ESP which is usually mounted to
-    <filename>/boot/</filename>, <filename>/efi/</filename> or <filename>/boot/efi</filename> during OS
-    runtime. systemd-boot reads runtime configuration such as the boot timeout and default entry from
-    <filename>/loader/loader.conf</filename> on the ESP (in combination with data read from EFI variables). See
-    <citerefentry><refentrytitle>loader.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Boot entry
-    description files following the <ulink
-    url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader
-    Specification</ulink> are read from <filename>/loader/entries/</filename> on the ESP. Unified kernel boot entries
-    following the <ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot
-    Loader Specification</ulink> are read from <filename>/EFI/Linux/</filename> on the ESP.</para>
+    <para>The files <command>systemd-boot</command> processes generally reside on the UEFI ESP which is
+    usually mounted to <filename>/efi/</filename>, <filename>/boot/</filename> or
+    <filename>/boot/efi/</filename> during OS runtime. It also processes files on the Extended Boot Loader
+    partition which is typically mounted to <filename>/boot/</filename>, if it
+    exists. <command>systemd-boot</command> reads runtime configuration such as the boot timeout and default
+    entry from <filename>/loader/loader.conf</filename> on the ESP (in combination with data read from EFI
+    variables). See
+    <citerefentry><refentrytitle>loader.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Boot
+    entry description files following the <ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot
+    Loader Specification</ulink> are read from <filename>/loader/entries/</filename> on the ESP and the
+    Extended Boot Loader partition. Unified kernel boot entries following the <ulink
+    url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink> are read from
+    <filename>/EFI/Linux/</filename> on the ESP and the Extended Boot Loader partition.</para>
   </refsect1>
 
   <refsect1>
index ab706420c5defd6c13531a6b30e51a6753aa919e..7897ce50e942103d58b74dc9ac0e4eeb8df6a89e 100644 (file)
         lost at shutdown, as <filename>/etc</filename> and <filename>/var</filename> will be served from the (initially
         unpopulated) volatile memory file system.</para>
 
-        <para>If set to <option>state</option> the generator will leave the root
-        directory mount point unaltered, however will mount a <literal>tmpfs</literal> file system to
-        <filename>/var</filename>. In this mode the normal system configuration (i.e. the contents of
-        <literal>/etc</literal>) is in effect (and may be modified during system runtime), however the system state
-        (i.e. the contents of <literal>/var</literal>) is reset at boot and lost at shutdown.</para>
+        <para>If set to <option>state</option> the generator will leave the root directory mount point unaltered,
+        however will mount a <literal>tmpfs</literal> file system to <filename>/var</filename>. In this mode the normal
+        system configuration (i.e. the contents of <literal>/etc</literal>) is in effect (and may be modified during
+        system runtime), however the system state (i.e. the contents of <literal>/var</literal>) is reset at boot and
+        lost at shutdown.</para>
+
+        <para>If this setting is set to <literal>overlay</literal> the root file system is set up as
+        <literal>overlayfs</literal> mount combining the read-only root directory with a writable
+        <literal>tmpfs</literal>, so that no modifications are made to disk, but the file system may be modified
+        nonetheless with all changes being lost at reboot.</para>
 
         <para>Note that in none of these modes the root directory, <filename>/etc</filename>, <filename>/var</filename>
         or any other resources stored in the root file system are physically removed. It's thus safe to boot a system
         that is normally operated in non-volatile mode temporarily into volatile mode, without losing data.</para>
 
-        <para>Note that enabling this setting will only work correctly on operating systems that can boot up with only
-        <filename>/usr</filename> mounted, and are able to automatically populate <filename>/etc</filename>, and also
-        <filename>/var</filename> in case of <literal>systemd.volatile=yes</literal>.</para></listitem>
+        <para>Note that with the exception of <literal>overlay</literal> mode, enabling this setting will only work
+        correctly on operating systems that can boot up with only <filename>/usr</filename> mounted, and are able to
+        automatically populate <filename>/etc</filename>, and also <filename>/var</filename> in case of
+        <literal>systemd.volatile=yes</literal>.</para></listitem>
       </varlistentry>
     </variablelist>
   </refsect1>
index 5ae434370564b5d9b2971d94251c7cf814752f90..c932ce5ce17fb6f18a6bcd0fc01beb53db37234f 100644 (file)
   <refsect1>
     <title>Description</title>
 
-    <para><filename>systemd-gpt-auto-generator</filename> is a unit
-    generator that automatically discovers root,
-    <filename>/home</filename>, <filename>/srv</filename> and swap
-    partitions and creates mount and swap units for them, based on the
-    partition type GUIDs of GUID partition tables (GPT),
-    see <ulink url="http://www.uefi.org/specifications">UEFI Specification</ulink>, chapter 5.
-    It implements the <ulink
-    url="https://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/">Discoverable
-    Partitions Specification</ulink>. Note that this generator has no
-    effect on non-GPT systems, or where the directories under the
-    mount points are already non-empty. Also, on systems where the
-    units are explicitly configured (for example, listed in
-    <citerefentry
-    project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>),
-    the units this generator creates are overridden, but additional
-    implicit dependencies might be created.</para>
+    <para><filename>systemd-gpt-auto-generator</filename> is a unit generator that automatically discovers
+    root, <filename>/home/</filename>, <filename>/srv/</filename>, the EFI System Partition, the Extended
+    Boot Loader Partition and swap partitions and creates mount and swap units for them, based on the
+    partition type GUIDs of GUID partition tables (GPT), see <ulink
+    url="http://www.uefi.org/specifications">UEFI Specification</ulink>, chapter 5.  It implements the <ulink
+    url="https://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/">Discoverable Partitions
+    Specification</ulink>. Note that this generator has no effect on non-GPT systems, and on specific mount
+    points that are directories already containing files. Also, on systems where the units are explicitly
+    configured (for example, listed in <citerefentry
+    project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>), the
+    units this generator creates are overridden, but additional implicit dependencies might be
+    created.</para>
 
     <para>This generator will only look for root partitions on the
     same physical disk the EFI System Partition (ESP) is located on.
       <tgroup cols='3' align='left' colsep='1' rowsep='1'>
         <colspec colname="guid" />
         <colspec colname="name" />
+        <colspec colname="where" />
         <colspec colname="explanation" />
         <thead>
           <row>
             <entry>Partition Type GUID</entry>
             <entry>Name</entry>
+            <entry>Mount Point</entry>
             <entry>Explanation</entry>
           </row>
         </thead>
           <row>
             <entry>44479540-f297-41b2-9af7-d131d5f0458a</entry>
             <entry><filename>Root Partition (x86)</filename></entry>
+            <entry><filename>/</filename></entry>
             <entry>On 32-bit x86 systems, the first x86 root partition on the disk the EFI ESP is located on is mounted to the root directory <filename>/</filename>.</entry>
           </row>
           <row>
             <entry>4f68bce3-e8cd-4db1-96e7-fbcaf984b709</entry>
             <entry><filename>Root Partition (x86-64)</filename></entry>
+            <entry><filename>/</filename></entry>
             <entry>On 64-bit x86 systems, the first x86-64 root partition on the disk the EFI ESP is located on is mounted to the root directory <filename>/</filename>.</entry>
           </row>
           <row>
             <entry>69dad710-2ce4-4e3c-b16c-21a1d49abed3</entry>
             <entry><filename>Root Partition (32-bit ARM)</filename></entry>
+            <entry><filename>/</filename></entry>
             <entry>On 32-bit ARM systems, the first ARM root partition on the disk the EFI ESP is located on is mounted to the root directory <filename>/</filename>.</entry>
           </row>
           <row>
             <entry>b921b045-1df0-41c3-af44-4c6f280d3fae</entry>
             <entry><filename>Root Partition (64-bit ARM)</filename></entry>
+            <entry><filename>/</filename></entry>
             <entry>On 64-bit ARM systems, the first ARM root partition on the disk the EFI ESP is located on is mounted to the root directory <filename>/</filename>.</entry>
           </row>
           <row>
             <entry>993d8d3d-f80e-4225-855a-9daf8ed7ea97</entry>
             <entry><filename>Root Partition (Itanium/IA-64)</filename></entry>
+            <entry><filename>/</filename></entry>
             <entry>On Itanium systems, the first Itanium root partition on the disk the EFI ESP is located on is mounted to the root directory <filename>/</filename>.</entry>
           </row>
           <row>
             <entry>933ac7e1-2eb4-4f13-b844-0e14e2aef915</entry>
             <entry>Home Partition</entry>
+            <entry><filename>/home/</filename></entry>
             <entry>The first home partition on the disk the root partition is located on is mounted to <filename>/home</filename>.</entry>
           </row>
           <row>
             <entry>3b8f8425-20e0-4f3b-907f-1a25a76f98e8</entry>
             <entry>Server Data Partition</entry>
+            <entry><filename>/srv/</filename></entry>
             <entry>The first server data partition on the disk the root partition is located on is mounted to <filename>/srv</filename>.</entry>
           </row>
           <row>
             <entry>0657fd6d-a4ab-43c4-84e5-0933c84b4f4f</entry>
             <entry>Swap</entry>
+            <entry>n/a</entry>
             <entry>All swap partitions located on the disk the root partition is located on are enabled.</entry>
           </row>
           <row>
             <entry>c12a7328-f81f-11d2-ba4b-00a0c93ec93b</entry>
             <entry>EFI System Partition (ESP)</entry>
+            <entry><filename>/efi/</filename> or <filename>/boot/</filename></entry>
             <entry>The first ESP located on the disk the root partition is located on is mounted to <filename>/boot</filename> or <filename>/efi</filename>, see below.</entry>
           </row>
+          <row>
+            <entry>bc13c2ff-59e6-4262-a352-b275fd6f7172</entry>
+            <entry>Extended Boot Loader Partition</entry>
+            <entry><filename>/boot/</filename></entry>
+            <entry>The first Extended Boot Loader Partition is mounted to <filename>/boot</filename>, see below.</entry>
+          </row>
         </tbody>
       </tgroup>
     </table>
           <row>
             <entry><constant>GPT_FLAG_READ_ONLY</constant></entry>
             <entry>0x1000000000000000</entry>
-            <entry><filename>/</filename>, <filename>/srv</filename>, <filename>/home</filename></entry>
+            <entry><filename>/</filename>, <filename>/home/</filename>, <filename>/srv/</filename>, Extended Boot Loader Partition</entry>
             <entry>Partition is mounted read-only</entry>
           </row>
 
           <row>
             <entry><constant>GPT_FLAG_NO_AUTO</constant></entry>
             <entry>0x8000000000000000</entry>
-            <entry><filename>/</filename>, <filename>/srv</filename>, <filename>/home</filename></entry>
+            <entry><filename>/</filename>, <filename>/home/</filename>, <filename>/srv/</filename>, Extended Boot Loader Partition</entry>
             <entry>Partition is not mounted automatically</entry>
           </row>
 
           <row>
             <entry><constant>GPT_FLAG_NO_BLOCK_IO_PROTOCOL</constant></entry>
             <entry>0x0000000000000002</entry>
-            <entry>ESP</entry>
+            <entry>EFI System Partition (ESP)</entry>
             <entry>Partition is not mounted automatically</entry>
           </row>
         </tbody>
       </tgroup>
     </table>
 
-    <para>The <filename>/home</filename> and <filename>/srv</filename>
-    partitions may be encrypted in LUKS format. In this case, a device
-    mapper device is set up under the names
-    <filename>/dev/mapper/home</filename> and
-    <filename>/dev/mapper/srv</filename>. Note that this might create
-    conflicts if the same partition is listed in
-    <filename>/etc/crypttab</filename> with a different device mapper
-    device name.</para>
-
-    <para>Mount and automount units for the EFI System Partition (ESP) are generated on EFI systems. The ESP is mounted
-    to <filename>/boot</filename>, unless a mount point directory <filename>/efi</filename> exists, in which case it is
-    mounted there. Since this generator creates an automount unit, the mount will only be activated on-demand, when
-    accessed. On systems where <filename>/boot</filename> (or <filename>/efi</filename> if it exists) is an explicitly
-    configured mount (for example, listed in <citerefentry
-    project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>) or where the
-    <filename>/boot</filename> (or <filename>/efi</filename>) mount point is non-empty, no mount units are
-    generated.</para>
+    <para>The <filename>/home/</filename> and <filename>/srv/</filename> partitions may be encrypted in LUKS
+    format. In this case, a device mapper device is set up under the names
+    <filename>/dev/mapper/home</filename> and <filename>/dev/mapper/srv</filename>. Note that this might
+    create conflicts if the same partition is listed in <filename>/etc/crypttab</filename> with a different
+    device mapper device name.</para>
+
+    <para>Mount and automount units for the EFI System Partition (ESP) are generated on EFI systems. The ESP
+    is mounted to <filename>/boot/</filename> (except if an Extended Boot Loader partition exists, see
+    below), unless a mount point directory <filename>/efi/</filename> exists, in which case it is mounted
+    there. Since this generator creates an automount unit, the mount will only be activated on-demand, when
+    accessed. On systems where <filename>/boot/</filename> (or <filename>/efi/</filename> if it exists) is an
+    explicitly configured mount (for example, listed in <citerefentry
+    project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>) or where
+    the <filename>/boot/</filename> (or <filename>/efi/</filename>) mount point is non-empty, no mount units
+    are generated.</para>
+
+    <para>If the disk contains an Extended Boot Loader partition, as defined in the <ulink
+    url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>, it is made
+    available at <filename>/boot</filename> (by means of an automount point, similar to the ESP, see
+    above). If both an EFI System Partition and an Extended Boot Loader partition exist the latter is
+    preferably mounted to <filename>/boot/</filename>. Make sure to create both <filename>/efi/</filename>
+    and <filename>/boot/</filename> to ensure both partitions are mounted.</para>
 
     <para>When using this generator in conjunction with btrfs file
     systems, make sure to set the correct default subvolumes on them,
index f0a5231acf43718d2f3257b64d0baf6863399a2d..00c14eafebbcc2a8e45e175efee0034f7b70296c 100644 (file)
       <varlistentry>
         <term><option>--template=</option></term>
 
-        <listitem><para>Directory or <literal>btrfs</literal> subvolume to use as template for the container's root
-        directory. If this is specified and the container's root directory (as configured by
-        <option>--directory=</option>) does not yet exist it is created as <literal>btrfs</literal> snapshot (if
-        supported) or plain directory (otherwise) and populated from this template tree. Ideally, the specified
-        template path refers to the root of a <literal>btrfs</literal> subvolume, in which case a simple copy-on-write
-        snapshot is taken, and populating the root directory is instant. If the specified template path does not refer
-        to the root of a <literal>btrfs</literal> subvolume (or not even to a <literal>btrfs</literal> file system at
-        all), the tree is copied (though possibly in a copy-on-write scheme â€” if the file system supports that), which
-        can be substantially more time-consuming. May not be specified together with <option>--image=</option> or
-        <option>--ephemeral</option>.</para>
+        <listitem><para>Directory or <literal>btrfs</literal> subvolume to use as template for the
+        container's root directory. If this is specified and the container's root directory (as configured by
+        <option>--directory=</option>) does not yet exist it is created as <literal>btrfs</literal> snapshot
+        (if supported) or plain directory (otherwise) and populated from this template tree. Ideally, the
+        specified template path refers to the root of a <literal>btrfs</literal> subvolume, in which case a
+        simple copy-on-write snapshot is taken, and populating the root directory is instant. If the
+        specified template path does not refer to the root of a <literal>btrfs</literal> subvolume (or not
+        even to a <literal>btrfs</literal> file system at all), the tree is copied (though possibly in a
+        'reflink' copy-on-write scheme â€” if the file system supports that), which can be substantially more
+        time-consuming. Note that the snapshot taken is of the specified directory or subvolume, including
+        all subdirectories and subvolumes below it, but excluding any sub-mounts. May not be specified
+        together with <option>--image=</option> or <option>--ephemeral</option>.</para>
 
         <para>Note that this switch leaves host name, machine ID and
         all other settings that could identify the instance
         <listitem><para>If specified, the container is run with a temporary snapshot of its file system that is removed
         immediately when the container terminates. May not be specified together with
         <option>--template=</option>.</para>
-        <para>Note that this switch leaves host name, machine ID and
-        all other settings that could identify the instance
-        unmodified.</para></listitem>
+        <para>Note that this switch leaves host name, machine ID and all other settings that could identify
+        the instance unmodified. Please note that â€” as with <option>--template=</option> â€” taking the
+        temporary snapshot is more efficient on file systems that support subvolume snapshots or 'reflinks'
+        natively (<literal>btrfs</literal> or new <literal>xfs</literal>) than on more traditional file
+        systems that do not (<literal>ext4</literal>). Note that the snapshot taken is of the specified
+        directory or subvolume, including all subdirectories and subvolumes below it, but excluding any
+        sub-mounts.</para>
+
+        <para>With this option no modifications of the container image are retained. Use
+        <option>--volatile=</option> (described below) for other mechanisms to restrict persistency of
+        container images during runtime.</para>
+        </listitem>
       </varlistentry>
 
       <varlistentry>
       <varlistentry>
         <term><option>--read-only</option></term>
 
-        <listitem><para>Mount the root file system read-only for the
-        container.</para></listitem>
+        <listitem><para>Mount the container's root file system (and any other file systems container in the container
+        image) read-only. This has no effect on additional mounts made with <option>--bind=</option>,
+        <option>--tmpfs=</option> and similar options. This mode is implied if the container image file or directory is
+        marked read-only itself. It is also implied if <option>--volatile=</option> is used. In this case the container
+        image on disk is strictly read-only, while changes are permitted but kept non-persistently in memory only. For
+        further details, see below.</para></listitem>
       </varlistentry>
 
       <varlistentry>
       <varlistentry>
         <term><option>--tmpfs=</option></term>
 
-        <listitem><para>Mount a tmpfs file system into the container.
-        Takes a single absolute path argument that specifies where to
-        mount the tmpfs instance to (in which case the directory
-        access mode will be chosen as 0755, owned by root/root), or
-        optionally a colon-separated pair of path and mount option
-        string that is used for mounting (in which case the kernel
-        default for access mode and owner will be chosen, unless
-        otherwise specified). This option is particularly useful for
-        mounting directories such as <filename>/var</filename> as
-        tmpfs, to allow state-less systems, in particular when
-        combined with <option>--read-only</option>.
-        Backslash escapes are interpreted in the path, so
-        <literal>\:</literal> may be used to embed colons in the path.
-        </para></listitem>
+        <listitem><para>Mount a tmpfs file system into the container.  Takes a single absolute path argument that
+        specifies where to mount the tmpfs instance to (in which case the directory access mode will be chosen as 0755,
+        owned by root/root), or optionally a colon-separated pair of path and mount option string that is used for
+        mounting (in which case the kernel default for access mode and owner will be chosen, unless otherwise
+        specified). Backslash escapes are interpreted in the path, so <literal>\:</literal> may be used to embed colons
+        in the path.</para>
+
+        <para>Note that this option cannot be used to replace the root file system of the container with a temporary
+        file system. However, the <option>--volatile=</option> option described below provides similar
+        functionality, with a focus on implementing stateless operating system images.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         be on the same file system as the top-most directory
         tree). Also note that the <literal>lowerdir=</literal> mount
         option receives the paths to stack in the opposite order of
-        this switch.</para></listitem>
+        this switch.</para>
+
+        <para>Note that this option cannot be used to replace the root file system of the container with an overlay
+        file system. However, the <option>--volatile=</option> option described below provides similar functionality,
+        with a focus on implementing stateless operating system images.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><option>--volatile</option></term>
         <term><option>--volatile=</option><replaceable>MODE</replaceable></term>
 
-        <listitem><para>Boots the container in volatile mode. When no
-        mode parameter is passed or when mode is specified as
-        <option>yes</option>, full volatile mode is enabled. This
-        means the root directory is mounted as a mostly unpopulated
-        <literal>tmpfs</literal> instance, and
-        <filename>/usr</filename> from the OS tree is mounted into it
-        in read-only mode (the system thus starts up with read-only OS
-        image, but pristine state and configuration, any changes
-        are lost on shutdown). When the mode parameter
-        is specified as <option>state</option>, the OS tree is
-        mounted read-only, but <filename>/var</filename> is mounted as
-        a <literal>tmpfs</literal> instance into it (the system thus
-        starts up with read-only OS resources and configuration, but
-        pristine state, and any changes to the latter are lost on
-        shutdown). When the mode parameter is specified as
-        <option>no</option> (the default), the whole OS tree is made
-        available writable.</para>
+        <listitem><para>Boots the container in volatile mode. When no mode parameter is passed or when mode is
+        specified as <option>yes</option>, full volatile mode is enabled. This means the root directory is mounted as a
+        mostly unpopulated <literal>tmpfs</literal> instance, and <filename>/usr/</filename> from the OS tree is
+        mounted into it in read-only mode (the system thus starts up with read-only OS image, but pristine state and
+        configuration, any changes are lost on shutdown). When the mode parameter is specified as
+        <option>state</option>, the OS tree is mounted read-only, but <filename>/var/</filename> is mounted as a
+        writable <literal>tmpfs</literal> instance into it (the system thus starts up with read-only OS resources and
+        configuration, but pristine state, and any changes to the latter are lost on shutdown). When the mode parameter
+        is specified as <option>overlay</option> the read-only root file system is combined with a writable
+        <filename>tmpfs</filename> instance through <literal>overlayfs</literal>, so that it appears at it normally
+        would, but any changes are applied to the temporary file system only and lost when the container is
+        terminated. When the mode parameter is specified as <option>no</option> (the default), the whole OS tree is
+        made available writable (unless <option>--read-only</option> is specified, see above).</para>
+
+        <para>Note that if one of the volatile modes is chosen, its effect is limited to the root file system (or
+        <filename>/var/</filename> in case of <option>state</option>), and any other mounts placed in the hierarchy are
+        unaffected â€” regardless if they are established automatically (e.g. the EFI system partition that might be
+        mounted to <filename>/efi/</filename> or <filename>/boot/</filename>) or explicitly (e.g. through an additional
+        command line option such as <option>--bind=</option>, see above). This means, even if
+        <option>--volatile=overlay</option> is used changes to <filename>/efi/</filename> or
+        <filename>/boot/</filename> are prohibited in case such a partition exists in the container image operated on,
+        and even if <option>--volatile=state</option> is used the hypothetical file <filename>/etc/foobar</filename> is
+        potentially writable if <option>--bind=/etc/foobar</option> if used to mount it from outside the read-only
+        container <filename>/etc</filename> directory.</para>
+
+        <para>The <option>--ephemeral</option> option is closely related to this setting, and provides similar
+        behaviour by making a temporary, ephemeral copy of the whole OS image and executing that. For further details,
+        see above.</para>
+
+        <para>The <option>--tmpfs=</option> and <option>--overlay=</option> options provide similar functionality, but
+        for specific sub-directories of the OS image only. For details, see above.</para>
 
         <para>This option provides similar functionality for containers as the <literal>systemd.volatile=</literal>
         kernel command line switch provides for host systems. See
         <citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
         details.</para>
 
-        <para>Note that enabling this setting will only work correctly with operating systems in the container that can
-        boot up with only <filename>/usr</filename> mounted, and are able to automatically populate
-        <filename>/var</filename>, and also <filename>/etc</filename> in case of
-        <literal>--volatile=yes</literal>.</para></listitem>
+        <para>Note that setting this option to <option>yes</option> or <option>state</option> will only work correctly
+        with operating systems in the container that can boot up with only <filename>/usr</filename> mounted, and are
+        able to automatically populate <filename>/var</filename>, and also <filename>/etc</filename> in case of
+        <literal>--volatile=yes</literal>. The <option>overlay</option> option does not require any particular
+        preparations in the OS, but do note that <literal>overlayfs</literal> behaviour differs from regular file
+        systems in a number of ways, and hence compatibility is limited.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index d7334e01ecf1a0b7d81129f20917cfbc9b713152..190bd2e451388a9efa5eed53f884f7044f796468 100644 (file)
         <command>systemd-resolved</command> will flush all caches it maintains. Note that it should normally not be
         necessary to request this explicitly â€“ except for debugging purposes â€“ as <command>systemd-resolved</command>
         flushes the caches automatically anyway any time the host's network configuration changes. Sending this signal
-        to <command>systemd-resolved</command> is equivalent to the <command>resolvectl --flush-caches</command>
+        to <command>systemd-resolved</command> is equivalent to the <command>resolvectl flush-caches</command>
         command, however the latter is recommended since it operates in a synchronous way.</para></listitem>
       </varlistentry>
 
         should normally not be necessary to request this explicitly â€“ except for debugging purposes â€“ as
         <command>systemd-resolved</command> automatically forgets learnt information any time the DNS server
         configuration changes. Sending this signal to <command>systemd-resolved</command> is equivalent to the
-        <command>resolvectl --reset-server-features</command> command, however the latter is recommended since it
+        <command>resolvectl reset-server-features</command> command, however the latter is recommended since it
         operates in a synchronous way.</para></listitem>
       </varlistentry>
     </variablelist>
index 114e617c8586e57d8a200d7a0f7ab63584af8c71..16ac2e9d0d6952949b1be240962d94167666df77 100755 (executable)
@@ -95,3 +95,7 @@ EOF
 mkdir -p "$DESTDIR"/boot/efi/EFI/systemd "$DESTDIR"/boot/efi/EFI/BOOT
 cp "$DESTDIR"/usr/lib/systemd/boot/efi/systemd-bootx64.efi "$DESTDIR"/boot/efi/EFI/systemd/systemd-bootx64.efi
 cp "$DESTDIR"/usr/lib/systemd/boot/efi/systemd-bootx64.efi "$DESTDIR"/boot/efi/EFI/BOOT/bootx64.efi
+
+mkdir -p "$DESTDIR"/efi/EFI/systemd "$DESTDIR"/efi/EFI/BOOT
+cp "$DESTDIR"/usr/lib/systemd/boot/efi/systemd-bootx64.efi "$DESTDIR"/efi/EFI/systemd/systemd-bootx64.efi
+cp "$DESTDIR"/usr/lib/systemd/boot/efi/systemd-bootx64.efi "$DESTDIR"/efi/EFI/BOOT/bootx64.efi
index 0e0c83788a6a21d85443f556907f85a03c350e22..8755b850b60b0a25350d492e67ffbaf7ad892724 100755 (executable)
@@ -2,7 +2,42 @@
 
 set -ex
 
-meson build -Dtests=unsafe -Dsplit-usr=true -Dslow-tests=true
-ninja -C build
-ninja -C build test
-DESTDIR=/var/tmp/inst1 ninja -C build install
+# keep this in sync with setup.sh
+CONTAINER=${RELEASE:-buster}-${ARCH:-amd64}
+AUTOPKGTESTDIR=${SEMAPHORE_CACHE_DIR:-/tmp}/autopkgtest
+# semaphore cannot expose these, but useful for interactive/local runs
+ARTIFACTS_DIR=/tmp/artifacts
+
+# add current debian/ packaging
+git fetch --depth=1 https://salsa.debian.org/systemd-team/systemd.git master
+git checkout FETCH_HEAD debian
+
+# craft changelog
+UPSTREAM_VER=$(git describe | sed 's/^v//')
+cat << EOF > debian/changelog.new
+systemd (${UPSTREAM_VER}-0) UNRELEASED; urgency=low
+
+  * Automatic build for upstream test
+
+ -- systemd test <pkg-systemd-maintainers@lists.alioth.debian.org>  $(date -R)
+
+EOF
+cat debian/changelog >> debian/changelog.new
+mv debian/changelog.new debian/changelog
+
+# clean out patches
+rm -rf debian/patches
+# disable autopkgtests which are not for upstream
+sed -i '/# NOUPSTREAM/ q' debian/tests/control
+# enable more unit tests
+sed -i '/^CONFFLAGS =/ s/=/= -Dtests=unsafe -Dsplit-usr=true -Dslow-tests=true /' debian/rules
+# no orig tarball
+echo '1.0' > debian/source/format
+
+# build source package
+dpkg-buildpackage -S -I -I$(basename "$SEMAPHORE_CACHE_DIR") -d -us -uc -nc
+
+# now build the package and run the tests
+rm -rf "$ARTIFACTS_DIR"
+# autopkgtest exits with 2 for "some tests skipped", accept that
+$AUTOPKGTESTDIR/runner/autopkgtest --apt-upgrade --env DEB_BUILD_OPTIONS=noudeb --env TEST_UPSTREAM=1 ../systemd_*.dsc -o "$ARTIFACTS_DIR" -- lxc -s $CONTAINER || [ $? -eq 2 ]
index d9a7b50685f77b65c8904e1c257ca04c8f84a672..b8cdba8033b8c3db2989c1467f848efc5c455127 100755 (executable)
@@ -2,26 +2,40 @@
 
 set -ex
 
-sudo add-apt-repository ppa:upstream-systemd-ci/systemd-ci -y
-sudo rm -rf /etc/apt/sources.list.d/beineri* /etc/apt/sources.list.d/google-chrome* /etc/apt/sources.list.d/heroku* /etc/apt/sources.list.d/mongodb* /etc/apt/sources.list.d/webupd8team* /etc/apt/sources.list.d/rwky* /etc/apt/sources.list.d/rethinkdb* /etc/apt/sources.list.d/cassandra* /etc/apt/sources.list.d/cwchien* /etc/apt/sources.list.d/rabbitmq* /etc/apt/sources.list.d/docker* /home/runner/{.npm,.phpbrew,.phpunit,.kerl,.kiex,.lein,.nvm,.npm,.phpbrew,.rbenv}
-sudo bash -c "echo 'deb-src http://de.archive.ubuntu.com/ubuntu/ xenial main restricted universe multiverse' >>/etc/apt/sources.list"
-sudo apt-get update -qq
-sudo apt-get build-dep systemd -y
-sudo apt-get install --force-yes -y util-linux libmount-dev libblkid-dev liblzma-dev libqrencode-dev libmicrohttpd-dev iptables-dev liblz4-dev libcurl4-gnutls-dev unifont clang-3.6 libasan0 itstool kbd cryptsetup-bin net-tools isc-dhcp-client iputils-ping strace qemu-system-x86 linux-image-virtual mount libgpg-error-dev libxkbcommon-dev python-lxml python3-lxml python3-pip libcap-dev
-# curl -s https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
-# sudo add-apt-repository -y 'deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty main'
-# sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
-sudo apt-get update
-sudo apt-get install --force-yes -y gettext python3-evdev python3-pyparsing libmount-dev
-# sudo apt-get install -y clang-6.0
-sudo sh -c 'echo 01010101010101010101010101010101 >/etc/machine-id'
-sudo mount -t tmpfs none /tmp
-test -d /run/mount || sudo mkdir /run/mount
-sudo adduser --system --no-create-home nfsnobody
-sudo rm -f /etc/mtab
-git clone https://github.com/ninja-build/ninja
-cd ninja
-./configure.py --bootstrap
-sudo cp ninja /usr/bin/
-cd ..
-pip3 install --user 'meson == 0.46.1'
+# default to Debian testing
+DISTRO=${DISTRO:-debian}
+RELEASE=${RELEASE:-buster}
+ARCH=${ARCH:-amd64}
+CONTAINER=${RELEASE}-${ARCH}
+
+# remove semaphore repos, some of them don't work and cause error messages
+sudo rm -f /etc/apt/sources.list.d/*
+
+# enable backports for latest LXC
+echo 'deb http://archive.ubuntu.com/ubuntu xenial-backports main restricted universe multiverse' | sudo tee -a /etc/apt/sources.list.d/backports.list
+sudo apt-get -q update
+sudo apt-get install -y -t xenial-backports lxc
+sudo apt-get install -y python3-debian git dpkg-dev fakeroot
+
+AUTOPKGTESTDIR=${SEMAPHORE_CACHE_DIR:-/tmp}/autopkgtest
+[ -d $AUTOPKGTESTDIR ] || git clone --quiet --depth=1 https://salsa.debian.org/ci-team/autopkgtest.git "$AUTOPKGTESTDIR"
+
+# TODO: cache container image (though downloading/building it takes < 1 min)
+# create autopkgtest LXC image
+sudo lxc-create -n $CONTAINER -t download -- -d $DISTRO -r $RELEASE -a $ARCH
+
+# unconfine the container, otherwise some tests fail
+echo 'lxc.apparmor.profile = unconfined' | sudo tee -a /var/lib/lxc/$CONTAINER/config
+
+sudo lxc-start -n $CONTAINER
+
+# enable source repositories so that apt-get build-dep works
+sudo lxc-attach -n $CONTAINER -- sh -ex <<EOF
+sed 's/^deb/deb-src/' /etc/apt/sources.list >> /etc/apt/sources.list.d/sources.list
+# wait until online
+while [ -z "\$(ip route list 0/0)" ]; do sleep 1; done
+apt-get -q update
+apt-get -y dist-upgrade
+apt-get install -y eatmydata
+EOF
+sudo lxc-stop -n $CONTAINER
index da4dd2a82707e439db4cf7f3515a8642d347ea92..b1519cc4eb49c3de300f1d2fc743839deb4b990e 100644 (file)
@@ -1628,7 +1628,7 @@ int btrfs_subvol_snapshot_fd_full(
                 } else if (r < 0)
                         return r;
 
-                r = copy_directory_fd_full(old_fd, new_path, COPY_MERGE|COPY_REFLINK, progress_path, progress_bytes, userdata);
+                r = copy_directory_fd_full(old_fd, new_path, COPY_MERGE|COPY_REFLINK|COPY_SAME_MOUNT, progress_path, progress_bytes, userdata);
                 if (r < 0)
                         goto fallback_fail;
 
index 46e02a37593ccbd53fc4c9576b1ef9ce61231285..2f36c8eb878d58d652433d83efa3fc1756531e7d 100644 (file)
@@ -743,7 +743,7 @@ int copy_file_fd_full(
 
         r = copy_bytes_full(fdf, fdt, (uint64_t) -1, copy_flags, NULL, NULL, progress_bytes, userdata);
 
-        (void) copy_times(fdf, fdt);
+        (void) copy_times(fdf, fdt, copy_flags);
         (void) copy_xattr(fdf, fdt);
 
         return r;
@@ -849,10 +849,9 @@ int copy_file_atomic_full(
         return 0;
 }
 
-int copy_times(int fdf, int fdt) {
+int copy_times(int fdf, int fdt, CopyFlags flags) {
         struct timespec ut[2];
         struct stat st;
-        usec_t crtime = 0;
 
         assert(fdf >= 0);
         assert(fdt >= 0);
@@ -866,8 +865,12 @@ int copy_times(int fdf, int fdt) {
         if (futimens(fdt, ut) < 0)
                 return -errno;
 
-        if (fd_getcrtime(fdf, &crtime) >= 0)
-                (void) fd_setcrtime(fdt, crtime);
+        if (FLAGS_SET(flags, COPY_CRTIME)) {
+                usec_t crtime;
+
+                if (fd_getcrtime(fdf, &crtime) >= 0)
+                        (void) fd_setcrtime(fdt, crtime);
+        }
 
         return 0;
 }
index f677021881969ed2d283c51991311fc732da2438..a33546d3ab5033c0ccf1ae423ce199ec9218f8f1 100644 (file)
@@ -14,6 +14,7 @@ typedef enum CopyFlags {
         COPY_REPLACE     = 1 << 2, /* Replace an existing file if there's one */
         COPY_SAME_MOUNT  = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
         COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */
+        COPY_CRTIME      = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */
 } CopyFlags;
 
 typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);
@@ -57,5 +58,5 @@ static inline int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags cop
         return copy_bytes_full(fdf, fdt, max_bytes, copy_flags, NULL, NULL, NULL, NULL);
 }
 
-int copy_times(int fdf, int fdt);
+int copy_times(int fdf, int fdt, CopyFlags flags);
 int copy_xattr(int fdf, int fdt);
index 7f10f9ad39e4c4e39cc3418a118750e013e9b2cb..a1f1308a54afe519a7352ed6be17ef034e0c0bcb 100644 (file)
@@ -559,6 +559,6 @@ int write_env_file(const char *fname, char **l) {
                 r = -errno;
         }
 
-        unlink(p);
+        (void) unlink(p);
         return r;
 }
index 3ff861579735c3bf573d8207d4a1360c9e3945f9..0d631093b29fabac8ef20ed74b1a57b8d1825300 100644 (file)
@@ -1330,6 +1330,21 @@ int fsync_path_at(int at_fd, const char *path) {
         return 0;
 }
 
+int syncfs_path(int atfd, const char *path) {
+        _cleanup_close_ int fd = -1;
+
+        assert(path);
+
+        fd = openat(atfd, path, O_CLOEXEC|O_RDONLY|O_NONBLOCK);
+        if (fd < 0)
+                return -errno;
+
+        if (syncfs(fd) < 0)
+                return -errno;
+
+        return 0;
+}
+
 int open_parent(const char *path, int flags, mode_t mode) {
         _cleanup_free_ char *parent = NULL;
         int fd;
index 7ad030be5d0e4a7c71a136e371f953892b4b81dd..9c9044669d9c436672870c91b297105703100fc8 100644 (file)
@@ -108,4 +108,6 @@ 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 syncfs_path(int atfd, const char *path);
+
 int open_parent(const char *path, int flags, mode_t mode);
index 0486027296d1cf777e2f0409add83ec8acb0c08f..84621932db4946fe6282407bd3d026a400bf8480 100644 (file)
@@ -1223,7 +1223,7 @@ int log_syntax_internal(
             log_target == LOG_TARGET_NULL)
                 return -ERRNO_VALUE(error);
 
-        errno = error;
+        errno = ERRNO_VALUE(error);
 
         va_start(ap, format);
         (void) vsnprintf(buffer, sizeof buffer, format, ap);
index 17438d7ff7e495d30b4d1e3cea47aa8ac0d5f8ea..113e0dedee7a5b096d49b05420c08e82a6c5fbf6 100644 (file)
@@ -308,7 +308,7 @@ int log_syntax_invalid_utf8_internal(
                 int _level = (level), _e = (error);                     \
                 (log_get_max_level() >= LOG_PRI(_level))                \
                         ? log_syntax_internal(unit, _level, config_file, config_line, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \
-                        : -abs(_e);                                     \
+                        : -ERRNO_VALUE(_e);                             \
         })
 
 #define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \
index 42b9618aa93bb9237cd17380012318edfb035213..b5d110f4224aa111e8b5419fa9bb16b18e85df5e 100644 (file)
@@ -16,9 +16,9 @@
 #include "verbs.h"
 #include "virt.h"
 
-static char *arg_path = NULL;
+static char **arg_path = NULL;
 
-STATIC_DESTRUCTOR_REGISTER(arg_path, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep);
 
 static int help(int argc, char *argv[], void *userdata) {
 
@@ -27,7 +27,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "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"
+               "     --path=PATH     Path to the $BOOT partition (may be used multiple times)\n"
                "\n"
                "Commands:\n"
                "     good            Mark this boot as good\n"
@@ -67,7 +67,7 @@ static int parse_argv(int argc, char *argv[]) {
                         return version();
 
                 case ARG_PATH:
-                        r = free_and_strdup(&arg_path, optarg);
+                        r = strv_extend(&arg_path, optarg);
                         if (r < 0)
                                 return log_oom();
                         break;
@@ -82,20 +82,42 @@ static int parse_argv(int argc, char *argv[]) {
         return 1;
 }
 
-static int acquire_esp(void) {
-        _cleanup_free_ char *np = NULL;
+static int acquire_path(void) {
+        _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL;
+        char **a;
         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)
+        if (!strv_isempty(arg_path))
+                return 0;
+
+        r = find_esp_and_warn(NULL, false, &esp_path, NULL, NULL, NULL, NULL);
+        if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */
+                return r;
+
+        r = find_xbootldr_and_warn(NULL, false, &xbootldr_path, NULL);
+        if (r < 0 && r != -ENOKEY)
                 return r;
 
-        free_and_replace(arg_path, np);
-        log_debug("Using EFI System Partition at %s.", arg_path);
+        if (!esp_path && !xbootldr_path)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+                                       "Couldn't find $BOOT partition. It is recommended to mount it to /boot.\n"
+                                       "Alternatively, use --path= to specify path to mount point.");
+
+        if (esp_path)
+                a = strv_new(esp_path, xbootldr_path);
+        else
+                a = strv_new(xbootldr_path);
+        if (!a)
+                return log_oom();
+
+        strv_free_and_replace(arg_path, a);
+
+        if (DEBUG_LOGGING) {
+                _cleanup_free_ char *j;
+
+                j = strv_join(arg_path, ":");
+                log_debug("Using %s as boot loader drop-in search path.", j);
+        }
 
         return 0;
 }
@@ -282,10 +304,9 @@ static const char *skip_slash(const char *path) {
 }
 
 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;
+        char **p;
         int r;
 
         r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
@@ -296,7 +317,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return r;
 
-        r = acquire_esp();
+        r = acquire_path();
         if (r < 0)
                 return r;
 
@@ -308,50 +329,61 @@ static int verb_status(int argc, char *argv[], void *userdata) {
         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("Booted file: %s\n"
+                  "The same modified for 'good': %s\n"
+                  "The same modified for 'bad':  %s\n",
+                  path,
+                  good,
+                  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);
+        STRV_FOREACH(p, arg_path) {
+                _cleanup_close_ int fd = -1;
 
-        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);
+                fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
+                if (fd < 0) {
+                        if (errno == ENOENT)
+                                continue;
 
-        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);
+                        return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
+                }
 
-        if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) {
-                puts("bad");
-                return 0;
+                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);
+
+                /* We didn't find any of the three? If so, let's try the next directory, before we give up. */
         }
-        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");
+        return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "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;
+        char **p;
         int r;
 
         r = acquire_boot_count_path(&path, &prefix, NULL, &done, &suffix);
@@ -360,7 +392,7 @@ static int verb_set(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return r;
 
-        r = acquire_esp();
+        r = acquire_path();
         if (r < 0)
                 return r;
 
@@ -372,10 +404,6 @@ static int verb_set(int argc, char *argv[], void *userdata) {
         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;
@@ -392,45 +420,58 @@ static int verb_set(int argc, char *argv[], void *userdata) {
                 source2 = bad;
         }
 
-        r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target));
-        if (r == -EEXIST)
-                goto exists;
-        else if (r == -ENOENT) {
+        STRV_FOREACH(p, arg_path) {
+                _cleanup_close_ int fd = -1;
+
+                fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
+                if (fd < 0)
+                        return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
 
-                r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target));
+                r = rename_noreplace(fd, skip_slash(source1), 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? */
+                        r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target));
+                        if (r == -EEXIST)
                                 goto exists;
+                        else if (r == -ENOENT) {
+
+                                if (faccessat(fd, skip_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
+                                        goto exists;
+
+                                if (errno != ENOENT)
+                                        return log_error_errno(errno, "Failed to determine if %s already exists: %m", target);
+
+                                /* We found none of the snippets here, try the next directory */
+                                continue;
+                        } 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);
 
-                        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);
+                        return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, 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);
+                        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();
+                /* First, fsync() the directory these files are located in */
+                parent = dirname_malloc(target);
+                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");
+                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");
+                /* Secondly, syncfs() the whole file system these files are located in */
+                if (syncfs(fd) < 0)
+                        log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m");
 
-        log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
+                log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
+        }
 
+        log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target);
         return 1;
 
 exists:
index dc2fd96628bce00d46ca9f190929606587fadcc7..1e0d115fe3ebbf6fee2e3555b3cbcb18d7530f2a 100644 (file)
 #include "verbs.h"
 #include "virt.h"
 
-static char *arg_path = NULL;
-static bool arg_print_path = false;
+static char *arg_esp_path = NULL;
+static char *arg_xbootldr_path = NULL;
+static bool arg_print_esp_path = false;
+static bool arg_print_dollar_boot_path = false;
 static bool arg_touch_variables = true;
 static PagerFlags arg_pager_flags = 0;
 
-STATIC_DESTRUCTOR_REGISTER(arg_path, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
+
+static const char *arg_dollar_boot_path(void) {
+        /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
+        return arg_xbootldr_path ?: arg_esp_path;
+}
 
 static int acquire_esp(
                 bool unprivileged_mode,
@@ -62,24 +70,44 @@ static int acquire_esp(
         char *np;
         int r;
 
-        /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on its own,
-         * except for ENOKEY (which is good, we want to show our own message in that case, suggesting use of --path=)
-         * and EACCESS (only when we request unprivileged mode; in this case we simply eat up the error here, so that
-         * --list and --status work too, without noise about this). */
+        /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on
+         * its own, except for ENOKEY (which is good, we want to show our own message in that case,
+         * suggesting use of --esp-path=) and EACCESS (only when we request unprivileged mode; in this case
+         * we simply eat up the error here, so that --list and --status work too, without noise about
+         * this). */
 
-        r = find_esp_and_warn(arg_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid);
+        r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid);
         if (r == -ENOKEY)
                 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.");
+                                       "Alternatively, use --esp-path= to specify path to mount point.");
         if (r < 0)
                 return r;
 
-        free_and_replace(arg_path, np);
+        free_and_replace(arg_esp_path, np);
+        log_debug("Using EFI System Partition at %s.", arg_esp_path);
 
-        log_debug("Using EFI System Partition at %s.", arg_path);
+        return 1;
+}
 
-        return 0;
+static int acquire_xbootldr(bool unprivileged_mode, sd_id128_t *ret_uuid) {
+        char *np;
+        int r;
+
+        r = find_xbootldr_and_warn(arg_xbootldr_path, unprivileged_mode, &np, ret_uuid);
+        if (r == -ENOKEY) {
+                log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT.");
+                if (ret_uuid)
+                        *ret_uuid = SD_ID128_NULL;
+                return 0;
+        }
+        if (r < 0)
+                return r;
+
+        free_and_replace(arg_xbootldr_path, np);
+        log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path);
+
+        return 1;
 }
 
 /* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */
@@ -96,6 +124,10 @@ static int get_file_version(int fd, char **v) {
         if (fstat(fd, &st) < 0)
                 return log_error_errno(errno, "Failed to stat EFI binary: %m");
 
+        r = stat_verify_regular(&st);
+        if (r < 0)
+                return log_error_errno(errno, "EFI binary is not a regular file: %m");
+
         if (st.st_size < 27) {
                 *v = NULL;
                 return 0;
@@ -112,8 +144,7 @@ static int get_file_version(int fd, char **v) {
 
         e = memmem(s, st.st_size - (s - buf), " ####", 5);
         if (!e || e - s < 3) {
-                log_error("Malformed version string.");
-                r = -EINVAL;
+                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed version string.");
                 goto finish;
         }
 
@@ -131,10 +162,13 @@ finish:
 }
 
 static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) {
-        char *p;
         _cleanup_closedir_ DIR *d = NULL;
         struct dirent *de;
-        int r = 0, c = 0;
+        int c = 0, r;
+        char *p;
+
+        assert(esp_path);
+        assert(path);
 
         p = strjoina(esp_path, "/", path);
         d = opendir(p);
@@ -146,8 +180,8 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char
         }
 
         FOREACH_DIRENT(de, d, break) {
-                _cleanup_close_ int fd = -1;
                 _cleanup_free_ char *v = NULL;
+                _cleanup_close_ int fd = -1;
 
                 if (!endswith_no_case(de->d_name, ".efi"))
                         continue;
@@ -166,6 +200,7 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char
                         printf("         File: %s/%s/%s (%s%s%s)\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name, ansi_highlight(), v, ansi_normal());
                 else
                         printf("         File: %s/%s/%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name);
+
                 c++;
         }
 
@@ -188,20 +223,22 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) {
         printf("\n");
 
         r = enumerate_binaries(esp_path, "EFI/systemd", NULL);
+        if (r < 0)
+                goto finish;
         if (r == 0)
                 log_info("systemd-boot not installed in ESP.");
-        else if (r < 0)
-                return r;
 
         r = enumerate_binaries(esp_path, "EFI/BOOT", "boot");
+        if (r < 0)
+                goto finish;
         if (r == 0)
                 log_info("No default/fallback boot loader installed in ESP.");
-        else if (r < 0)
-                return r;
 
-        printf("\n");
+        r = 0;
 
-        return 0;
+finish:
+        printf("\n");
+        return r;
 }
 
 static int print_efi_option(uint16_t id, bool in_order) {
@@ -316,11 +353,34 @@ static int boot_entry_show(const BootEntry *e, bool show_as_default) {
         return 0;
 }
 
-static int status_entries(const char *esp_path, sd_id128_t partition) {
+static int status_entries(
+                const char *esp_path,
+                sd_id128_t esp_partition_uuid,
+                const char *xbootldr_path,
+                sd_id128_t xbootldr_partition_uuid) {
+
         _cleanup_(boot_config_free) BootConfig config = {};
+        sd_id128_t dollar_boot_partition_uuid;
+        const char *dollar_boot_path;
         int r;
 
-        r = boot_entries_load_config(esp_path, &config);
+        assert(esp_path || xbootldr_path);
+
+        if (xbootldr_path) {
+                dollar_boot_path = xbootldr_path;
+                dollar_boot_partition_uuid = xbootldr_partition_uuid;
+        } else {
+                dollar_boot_path = esp_path;
+                dollar_boot_partition_uuid = esp_partition_uuid;
+        }
+
+        printf("Boot Loader Entries:\n"
+               "        $BOOT: %s", dollar_boot_path);
+        if (!sd_id128_is_null(dollar_boot_partition_uuid))
+                printf(" (/dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x)", SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid));
+        printf("\n\n");
+
+        r = boot_entries_load_config(esp_path, xbootldr_path, &config);
         if (r < 0)
                 return r;
 
@@ -386,10 +446,8 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t
                                         "Skipping \"%s\", since it's owned by another boot loader.",
                                         to);
 
-        if (compare_version(a, b) < 0) {
-                log_warning("Skipping \"%s\", since a newer boot loader version exists already.", to);
-                return -ESTALE;
-        }
+        if (compare_version(a, b) < 0)
+                return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since a newer boot loader version exists already.", to);
 
         return 0;
 }
@@ -436,7 +494,7 @@ static int copy_file_with_version_check(const char *from, const char *to, bool f
                 return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", from, t);
         }
 
-        (void) copy_times(fd_from, fd_to);
+        (void) copy_times(fd_from, fd_to, 0);
 
         if (fsync(fd_to) < 0) {
                 (void) unlink_noerrno(t);
@@ -468,20 +526,21 @@ static int mkdir_one(const char *prefix, const char *suffix) {
         return 0;
 }
 
-static const char *efi_subdirs[] = {
+static const char *const esp_subdirs[] = {
         "EFI",
         "EFI/systemd",
         "EFI/BOOT",
         "loader",
-        "loader/entries",
+        /* Note that "/loader/entries" is not listed here, since it should be placed in $BOOT, which might
+         * not necessarily be the ESP */
         NULL
 };
 
-static int create_dirs(const char *esp_path) {
-        const char **i;
+static int create_esp_subdirs(const char *esp_path) {
+        const char *const *i;
         int r;
 
-        STRV_FOREACH(i, efi_subdirs) {
+        STRV_FOREACH(i, esp_subdirs) {
                 r = mkdir_one(esp_path, *i);
                 if (r < 0)
                         return r;
@@ -520,22 +579,11 @@ static int install_binaries(const char *esp_path, bool force) {
         _cleanup_closedir_ DIR *d = NULL;
         int r = 0;
 
-        if (force) {
-                /* Don't create any of these directories when we are
-                 * just updating. When we update we'll drop-in our
-                 * files (unless there are newer ones already), but we
-                 * won't create the directories for them in the first
-                 * place. */
-                r = create_dirs(esp_path);
-                if (r < 0)
-                        return r;
-        }
-
         d = opendir(BOOTLIBDIR);
         if (!d)
                 return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m");
 
-        FOREACH_DIRENT(de, d, break) {
+        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \""BOOTLIBDIR"\": %m")) {
                 int k;
 
                 if (!endswith_no_case(de->d_name, ".efi"))
@@ -751,18 +799,36 @@ static int rmdir_one(const char *prefix, const char *suffix) {
 
         p = strjoina(prefix, "/", suffix);
         if (rmdir(p) < 0) {
-                if (!IN_SET(errno, ENOENT, ENOTEMPTY))
-                        return log_error_errno(errno, "Failed to remove \"%s\": %m", p);
+                bool ignore = IN_SET(errno, ENOENT, ENOTEMPTY);
+
+                log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, errno,
+                               "Failed to remove directory \"%s\": %m", p);
+                if (!ignore)
+                        return -errno;
         } else
                 log_info("Removed \"%s\".", p);
 
         return 0;
 }
 
+static int remove_esp_subdirs(const char *esp_path) {
+        size_t i;
+        int r = 0;
+
+        for (i = ELEMENTSOF(esp_subdirs)-1; i > 0; i--) {
+                int q;
+
+                q = rmdir_one(esp_path, esp_subdirs[i-1]);
+                if (q < 0 && r >= 0)
+                        r = q;
+        }
+
+        return r;
+}
+
 static int remove_binaries(const char *esp_path) {
         char *p;
         int r, q;
-        unsigned i;
 
         p = strjoina(esp_path, "/EFI/systemd");
         r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
@@ -771,15 +837,31 @@ static int remove_binaries(const char *esp_path) {
         if (q < 0 && r == 0)
                 r = q;
 
-        for (i = ELEMENTSOF(efi_subdirs)-1; i > 0; i--) {
-                q = rmdir_one(esp_path, efi_subdirs[i-1]);
-                if (q < 0 && r == 0)
-                        r = q;
-        }
-
         return r;
 }
 
+static int remove_loader_config(const char *esp_path) {
+        const char *p;
+
+        assert(esp_path);
+
+        p = strjoina(esp_path, "/loader/loader.conf");
+        if (unlink(p) < 0) {
+                log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to unlink file \"%s\": %m", p);
+                if (errno != ENOENT)
+                        return -errno;
+        } else
+                log_info("Removed \"%s\".", p);
+
+        return  0;
+}
+
+static int remove_entries_directory(const char *dollar_boot_path) {
+        assert(dollar_boot_path);
+
+        return rmdir_one(dollar_boot_path, "/loader/entries");
+}
+
 static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
         uint16_t slot;
         int r;
@@ -802,7 +884,6 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
 }
 
 static int install_loader_config(const char *esp_path) {
-
         char machine_string[SD_ID128_STRING_MAX];
         _cleanup_(unlink_and_freep) char *t = NULL;
         _cleanup_fclose_ FILE *f = NULL;
@@ -810,15 +891,14 @@ static int install_loader_config(const char *esp_path) {
         const char *p;
         int r, fd;
 
-        r = sd_id128_get_machine(&machine_id);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get machine id: %m");
-
         p = strjoina(esp_path, "/loader/loader.conf");
-
         if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */
                 return 0;
 
+        r = sd_id128_get_machine(&machine_id);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get machine id: %m");
+
         fd = open_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t);
         if (fd < 0)
                 return log_error_errno(fd, "Failed to open \"%s\" for writing: %m", p);
@@ -844,10 +924,15 @@ static int install_loader_config(const char *esp_path) {
                 return log_error_errno(r, "Failed to move \"%s\" into place: %m", p);
 
         t = mfree(t);
-
         return 1;
 }
 
+static int install_entries_directory(const char *dollar_boot_path) {
+        assert(dollar_boot_path);
+
+        return mkdir_one(dollar_boot_path, "/loader/entries");
+}
+
 static int help(int argc, char *argv[], void *userdata) {
         _cleanup_free_ char *link = NULL;
         int r;
@@ -858,21 +943,23 @@ static int help(int argc, char *argv[], void *userdata) {
 
         printf("%s [COMMAND] [OPTIONS...]\n\n"
                "Install, update or remove the systemd-boot EFI boot manager.\n\n"
-               "  -h --help          Show this help\n"
-               "     --version       Print version\n"
-               "     --path=PATH     Path to the EFI System Partition (ESP)\n"
-               "  -p --print-path    Print path to the EFI partition\n"
-               "     --no-variables  Don't touch EFI variables\n"
-               "     --no-pager      Do not pipe output into a pager\n"
+               "  -h --help            Show this help\n"
+               "     --version         Print version\n"
+               "     --esp-path=PATH   Path to the EFI System Partition (ESP)\n"
+               "     --boot-path=PATH  Path to the $BOOT partition\n"
+               "  -p --print-esp-path  Print path to the EFI System Partition\n"
+               "     --print-boot-path Print path to the $BOOT partition\n"
+               "     --no-variables    Don't touch EFI variables\n"
+               "     --no-pager        Do not pipe output into a pager\n"
                "\nBoot Loader Commands:\n"
-               "     status          Show status of installed systemd-boot and EFI variables\n"
-               "     install         Install systemd-boot to the ESP and EFI variables\n"
-               "     update          Update systemd-boot in the ESP and EFI variables\n"
-               "     remove          Remove systemd-boot from the ESP and EFI variables\n"
+               "     status            Show status of installed systemd-boot and EFI variables\n"
+               "     install           Install systemd-boot to the ESP and EFI variables\n"
+               "     update            Update systemd-boot in the ESP and EFI variables\n"
+               "     remove            Remove systemd-boot from the ESP and EFI variables\n"
                "\nBoot Loader Entries Commands:\n"
-               "     list            List boot loader entries\n"
-               "     set-default ID  Set default boot loader entry\n"
-               "     set-oneshot ID  Set default boot loader entry, for next boot only\n"
+               "     list              List boot loader entries\n"
+               "     set-default ID    Set default boot loader entry\n"
+               "     set-oneshot ID    Set default boot loader entry, for next boot only\n"
                "\nSee the %s for details.\n"
                , program_invocation_short_name
                , link);
@@ -882,19 +969,25 @@ static int help(int argc, char *argv[], void *userdata) {
 
 static int parse_argv(int argc, char *argv[]) {
         enum {
-                ARG_PATH = 0x100,
+                ARG_ESP_PATH = 0x100,
+                ARG_BOOT_PATH,
+                ARG_PRINT_BOOT_PATH,
                 ARG_VERSION,
                 ARG_NO_VARIABLES,
                 ARG_NO_PAGER,
         };
 
         static const struct option options[] = {
-                { "help",         no_argument,       NULL, 'h'              },
-                { "version",      no_argument,       NULL, ARG_VERSION      },
-                { "path",         required_argument, NULL, ARG_PATH         },
-                { "print-path",   no_argument,       NULL, 'p'              },
-                { "no-variables", no_argument,       NULL, ARG_NO_VARIABLES },
-                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
+                { "help",            no_argument,       NULL, 'h'                 },
+                { "version",         no_argument,       NULL, ARG_VERSION         },
+                { "esp-path",        required_argument, NULL, ARG_ESP_PATH        },
+                { "path",            required_argument, NULL, ARG_ESP_PATH        }, /* Compatibility alias */
+                { "boot-path",       required_argument, NULL, ARG_BOOT_PATH       },
+                { "print-esp-path",  no_argument,       NULL, 'p'                 },
+                { "print-path",      no_argument,       NULL, 'p'                 }, /* Compatibility alias */
+                { "print-boot-path", no_argument,       NULL, ARG_PRINT_BOOT_PATH },
+                { "no-variables",    no_argument,       NULL, ARG_NO_VARIABLES    },
+                { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
                 {}
         };
 
@@ -913,14 +1006,24 @@ static int parse_argv(int argc, char *argv[]) {
                 case ARG_VERSION:
                         return version();
 
-                case ARG_PATH:
-                        r = free_and_strdup(&arg_path, optarg);
+                case ARG_ESP_PATH:
+                        r = free_and_strdup(&arg_esp_path, optarg);
+                        if (r < 0)
+                                return log_oom();
+                        break;
+
+                case ARG_BOOT_PATH:
+                        r = free_and_strdup(&arg_xbootldr_path, optarg);
                         if (r < 0)
                                 return log_oom();
                         break;
 
                 case 'p':
-                        arg_print_path = true;
+                        arg_print_esp_path = true;
+                        break;
+
+                case ARG_PRINT_BOOT_PATH:
+                        arg_print_dollar_boot_path = true;
                         break;
 
                 case ARG_NO_VARIABLES:
@@ -950,23 +1053,33 @@ static void read_loader_efi_var(const char *name, char **var) {
 }
 
 static int verb_status(int argc, char *argv[], void *userdata) {
-
-        sd_id128_t uuid = SD_ID128_NULL;
+        sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL;
         int r, k;
 
-        r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &uuid);
-
-        if (arg_print_path) {
+        r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &esp_uuid);
+        if (arg_print_esp_path) {
                 if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only
                                    * error the find_esp_and_warn() won't log on its own) */
-                        return log_error_errno(r, "Failed to determine ESP: %m");
+                        return log_error_errno(r, "Failed to determine ESP location: %m");
                 if (r < 0)
                         return r;
 
-                puts(arg_path);
-                return 0;
+                puts(arg_esp_path);
         }
 
+        r = acquire_xbootldr(geteuid() != 0, &xbootldr_uuid);
+        if (arg_print_dollar_boot_path) {
+                if (r == -EACCES)
+                        return log_error_errno(r, "Failed to determine XBOOTLDR location: %m");
+                if (r < 0)
+                        return r;
+
+                puts(arg_dollar_boot_path());
+        }
+
+        if (arg_print_esp_path || arg_print_dollar_boot_path)
+                return 0;
+
         r = 0; /* If we couldn't determine the path, then don't consider that a problem from here on, just show what we
                 * can show */
 
@@ -1037,8 +1150,8 @@ static int verb_status(int argc, char *argv[], void *userdata) {
         } else
                 printf("System:\n    Not booted with EFI\n\n");
 
-        if (arg_path) {
-                k = status_binaries(arg_path, uuid);
+        if (arg_esp_path) {
+                k = status_binaries(arg_esp_path, esp_uuid);
                 if (k < 0)
                         r = k;
         }
@@ -1049,8 +1162,8 @@ static int verb_status(int argc, char *argv[], void *userdata) {
                         r = k;
         }
 
-        if (arg_path) {
-                k = status_entries(arg_path, uuid);
+        if (arg_esp_path || arg_xbootldr_path) {
+                k = status_entries(arg_esp_path, esp_uuid, arg_xbootldr_path, xbootldr_uuid);
                 if (k < 0)
                         r = k;
         }
@@ -1061,20 +1174,25 @@ static int verb_status(int argc, char *argv[], void *userdata) {
 static int verb_list(int argc, char *argv[], void *userdata) {
         _cleanup_(boot_config_free) BootConfig config = {};
         _cleanup_free_ char **found_by_loader = NULL;
-        sd_id128_t uuid = SD_ID128_NULL;
         int r;
 
         /* If we lack privileges we invoke find_esp_and_warn() in "unprivileged mode" here, which does two things: turn
          * off logging about access errors and turn off potentially privileged device probing. Here we're interested in
          * the latter but not the former, hence request the mode, and log about EACCES. */
 
-        r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &uuid);
+        r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, NULL);
         if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */
                 return log_error_errno(r, "Failed to determine ESP: %m");
         if (r < 0)
                 return r;
 
-        r = boot_entries_load_config(arg_path, &config);
+        r = acquire_xbootldr(geteuid() != 0, NULL);
+        if (r == -EACCES)
+                return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
+        if (r < 0)
+                return r;
+
+        r = boot_entries_load_config(arg_esp_path, arg_xbootldr_path, &config);
         if (r < 0)
                 return r;
 
@@ -1114,24 +1232,25 @@ static int verb_list(int argc, char *argv[], void *userdata) {
         return 0;
 }
 
-static int sync_esp(void) {
-        _cleanup_close_ int fd = -1;
+static int sync_everything(void) {
+        int ret = 0, k;
 
-        if (!arg_path)
-                return 0;
-
-        fd = open(arg_path, O_CLOEXEC|O_DIRECTORY|O_RDONLY);
-        if (fd < 0)
-                return log_error_errno(errno, "Couldn't open ESP '%s' for synchronization: %m", arg_path);
+        if (arg_esp_path) {
+                k = syncfs_path(AT_FDCWD, arg_esp_path);
+                if (k < 0)
+                        ret = log_error_errno(k, "Failed to synchronize the ESP '%s': %m", arg_esp_path);
+        }
 
-        if (syncfs(fd) < 0)
-                return log_error_errno(errno, "Failed to synchronize the ESP '%s': %m", arg_path);
+        if (arg_xbootldr_path) {
+                k = syncfs_path(AT_FDCWD, arg_xbootldr_path);
+                if (k < 0)
+                        ret = log_error_errno(k, "Failed to synchronize $BOOT '%s': %m", arg_xbootldr_path);
+        }
 
-        return 1;
+        return ret;
 }
 
 static int verb_install(int argc, char *argv[], void *userdata) {
-
         sd_id128_t uuid = SD_ID128_NULL;
         uint64_t pstart = 0, psize = 0;
         uint32_t part = 0;
@@ -1142,24 +1261,41 @@ static int verb_install(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return r;
 
+        r = acquire_xbootldr(false, NULL);
+        if (r < 0)
+                return r;
+
         install = streq(argv[0], "install");
 
         RUN_WITH_UMASK(0002) {
-                r = install_binaries(arg_path, install);
+                if (install) {
+                        /* Don't create any of these directories when we are just updating. When we update
+                         * we'll drop-in our files (unless there are newer ones already), but we won't create
+                         * the directories for them in the first place. */
+                        r = create_esp_subdirs(arg_esp_path);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = install_binaries(arg_esp_path, install);
                 if (r < 0)
                         return r;
 
                 if (install) {
-                        r = install_loader_config(arg_path);
+                        r = install_loader_config(arg_esp_path);
+                        if (r < 0)
+                                return r;
+
+                        r = install_entries_directory(arg_dollar_boot_path());
                         if (r < 0)
                                 return r;
                 }
         }
 
-        (void) sync_esp();
+        (void) sync_everything();
 
         if (arg_touch_variables)
-                r = install_variables(arg_path,
+                r = install_variables(arg_esp_path,
                                       part, pstart, psize, uuid,
                                       "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi",
                                       install);
@@ -1169,21 +1305,35 @@ static int verb_install(int argc, char *argv[], void *userdata) {
 
 static int verb_remove(int argc, char *argv[], void *userdata) {
         sd_id128_t uuid = SD_ID128_NULL;
-        int r;
+        int r, q;
 
         r = acquire_esp(false, NULL, NULL, NULL, &uuid);
         if (r < 0)
                 return r;
 
-        r = remove_binaries(arg_path);
+        r = acquire_xbootldr(false, NULL);
+        if (r < 0)
+                return r;
 
-        (void) sync_esp();
+        r = remove_binaries(arg_esp_path);
 
-        if (arg_touch_variables) {
-                int q;
+        q = remove_loader_config(arg_esp_path);
+        if (q < 0 && r >= 0)
+                r = q;
+
+        q = remove_entries_directory(arg_dollar_boot_path());
+        if (q < 0 && r >= 0)
+                r = q;
 
+        q = remove_esp_subdirs(arg_esp_path);
+        if (q < 0 && r >= 0)
+                r = q;
+
+        (void) sync_everything();
+
+        if (arg_touch_variables) {
                 q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true);
-                if (q < 0 && r == 0)
+                if (q < 0 && r >= 0)
                         r = q;
         }
 
index 9bf6895831baecbeab36881c599a3cddbd073bb7..c7ba088761007b9d6371578c3ac377753e64e055 100644 (file)
@@ -1,9 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include <efi.h>
+#include <efigpt.h>
 #include <efilib.h>
 
 #include "console.h"
+#include "crc32.h"
 #include "disk.h"
 #include "graphics.h"
 #include "linux.h"
@@ -1316,7 +1318,7 @@ static VOID config_entry_add_from_file(
                         entry->loader = stra_to_path(value);
 
                         /* do not add an entry for ourselves */
-                        if (StriCmp(entry->loader, loaded_image_path) == 0) {
+                        if (loaded_image_path && StriCmp(entry->loader, loaded_image_path) == 0) {
                                 entry->type = LOADER_UNDEFINED;
                                 break;
                         }
@@ -1836,7 +1838,7 @@ static VOID config_entry_add_osx(Config *config) {
 
 static VOID config_entry_add_linux(
                 Config *config,
-                EFI_LOADED_IMAGE *loaded_image,
+                EFI_HANDLE *device,
                 EFI_FILE *root_dir) {
 
         EFI_FILE_HANDLE linux_dir;
@@ -1926,7 +1928,7 @@ static VOID config_entry_add_linux(
                         conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build);
                         path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName);
 
-                        entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path);
+                        entry = config_entry_add_loader(config, device, LOADER_LINUX, conf, 'l', os_name, path);
 
                         FreePool(content);
                         content = NULL;
@@ -1955,6 +1957,193 @@ static VOID config_entry_add_linux(
         uefi_call_wrapper(linux_dir->Close, 1, linux_dir);
 }
 
+/* Note that this is in GUID format, i.e. the first 32bit, and the following pair of 16bit are byteswapped. */
+static const UINT8 xbootldr_guid[16] = {
+        0xff, 0xc2, 0x13, 0xbc, 0xe6, 0x59, 0x62, 0x42, 0xa3, 0x52, 0xb2, 0x75, 0xfd, 0x6f, 0x71, 0x72
+};
+
+EFI_DEVICE_PATH *path_parent(EFI_DEVICE_PATH *path, EFI_DEVICE_PATH *node) {
+        EFI_DEVICE_PATH *parent;
+        UINTN len;
+
+        len = (UINT8*) NextDevicePathNode(node) - (UINT8*) path;
+        parent = (EFI_DEVICE_PATH*) AllocatePool(len + sizeof(EFI_DEVICE_PATH));
+        CopyMem(parent, path, len);
+        CopyMem((UINT8*) parent + len, EndDevicePath, sizeof(EFI_DEVICE_PATH));
+
+        return parent;
+}
+
+static VOID config_load_xbootldr(
+                Config *config,
+                EFI_HANDLE *device) {
+
+        EFI_DEVICE_PATH *partition_path, *node, *disk_path, *copy;
+        UINT32 found_partition_number = (UINT32) -1;
+        UINT64 found_partition_start = (UINT64) -1;
+        UINT64 found_partition_size = (UINT64) -1;
+        UINT8 found_partition_signature[16] = {};
+        EFI_HANDLE new_device;
+        EFI_FILE *root_dir;
+        EFI_STATUS r;
+
+        partition_path = DevicePathFromHandle(device);
+        if (!partition_path)
+                return;
+
+        for (node = partition_path; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) {
+                EFI_HANDLE disk_handle;
+                EFI_BLOCK_IO *block_io;
+                EFI_DEVICE_PATH *p;
+                UINTN nr;
+
+                /* First, Let's look for the SCSI/SATA/USB/… device path node, i.e. one above the media
+                 * devices */
+                if (DevicePathType(node) != MESSAGING_DEVICE_PATH)
+                        continue;
+
+                /* Determine the device path one level up */
+                disk_path = path_parent(partition_path, node);
+                p = disk_path;
+                r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, &p, &disk_handle);
+                if (EFI_ERROR(r))
+                        continue;
+
+                r = uefi_call_wrapper(BS->HandleProtocol, 3, disk_handle, &BlockIoProtocol, (VOID **)&block_io);
+                if (EFI_ERROR(r))
+                        continue;
+
+                /* Filter out some block devices early. (We only care about block devices that aren't
+                 * partitions themselves â€” we look for GPT partition tables to parse after all â€”, and only
+                 * those which contain a medium and have at least 2 blocks.) */
+                if (block_io->Media->LogicalPartition ||
+                    !block_io->Media->MediaPresent ||
+                    block_io->Media->LastBlock <= 1)
+                        continue;
+
+                /* Try both copies of the GPT header, in case one is corrupted */
+                for (nr = 0; nr < 2; nr++) {
+                        _cleanup_freepool_ EFI_PARTITION_ENTRY* entries = NULL;
+                        union {
+                                EFI_PARTITION_TABLE_HEADER gpt_header;
+                                uint8_t space[((sizeof(EFI_PARTITION_TABLE_HEADER) + 511) / 512) * 512];
+                        } gpt_header_buffer;
+                        UINT64 where;
+                        UINTN i, sz;
+                        UINT32 c;
+
+                        if (nr == 0)
+                                where = 1; /* Read the first copy at LBA 1 */
+                        else
+                                where = block_io->Media->LastBlock; /* Read the second copy at the very last LBA of this block device */
+
+                        /* Read the GPT header */
+                        r = uefi_call_wrapper(block_io->ReadBlocks, 5, block_io, block_io->Media->MediaId, where, sizeof(gpt_header_buffer), &gpt_header_buffer);
+                        if (EFI_ERROR(r))
+                                continue;
+
+                        /* Some superficial validation of the GPT header */
+                        if (CompareMem(&gpt_header_buffer.gpt_header.Header.Signature, "EFI PART", sizeof(gpt_header_buffer.gpt_header.Header.Signature)) != 0)
+                                continue;
+
+                        if (gpt_header_buffer.gpt_header.Header.HeaderSize < 92 || gpt_header_buffer.gpt_header.Header.HeaderSize > 512)
+                                continue;
+
+                        if (gpt_header_buffer.gpt_header.Header.Revision != 0x00010000U)
+                                continue;
+
+                        /* Calculate CRC check */
+                        c = ~crc32_exclude_offset((UINT32) -1, (const UINT8*) &gpt_header_buffer, gpt_header_buffer.gpt_header.Header.HeaderSize,
+                                                  OFFSETOF(EFI_PARTITION_TABLE_HEADER, Header.CRC32), sizeof(gpt_header_buffer.gpt_header.Header.CRC32));
+                        if (c != gpt_header_buffer.gpt_header.Header.CRC32)
+                                continue;
+
+                        if (gpt_header_buffer.gpt_header.MyLBA != where)
+                                continue;
+
+                        if (gpt_header_buffer.gpt_header.SizeOfPartitionEntry < sizeof(EFI_PARTITION_ENTRY))
+                                continue;
+
+                        if (gpt_header_buffer.gpt_header.NumberOfPartitionEntries <= 0 || gpt_header_buffer.gpt_header.NumberOfPartitionEntries > 1024)
+                                continue;
+
+                        /* Now load the GPT entry table */
+                        sz = ((gpt_header_buffer.gpt_header.SizeOfPartitionEntry * gpt_header_buffer.gpt_header.NumberOfPartitionEntries + 511) / 512) * 512;
+                        entries = AllocatePool(sz);
+
+                        r = uefi_call_wrapper(block_io->ReadBlocks, 5, block_io, block_io->Media->MediaId, gpt_header_buffer.gpt_header.PartitionEntryLBA, sz, entries);
+                        if (EFI_ERROR(r))
+                                continue;
+
+                        /* Calculate CRC of entries array, too */
+                        c = ~crc32((UINT32) -1, entries, sz);
+                        if (c != gpt_header_buffer.gpt_header.PartitionEntryArrayCRC32)
+                                continue;
+
+                        for (i = 0; i < gpt_header_buffer.gpt_header.NumberOfPartitionEntries; i++) {
+                                EFI_PARTITION_ENTRY *entry;
+
+                                entry = (EFI_PARTITION_ENTRY*) ((UINT8*) entries + gpt_header_buffer.gpt_header.SizeOfPartitionEntry * i);
+
+                                if (CompareMem(&entry->PartitionTypeGUID, xbootldr_guid, 16) == 0) {
+                                        UINT64 end;
+
+                                        /* Let's use memcpy(), in case the structs are not aligned (they really should be though) */
+                                        CopyMem(&found_partition_start, &entry->StartingLBA, sizeof(found_partition_start));
+                                        CopyMem(&end, &entry->EndingLBA, sizeof(end));
+
+                                        if (end < found_partition_start) /* Bogus? */
+                                                continue;
+
+                                        found_partition_size = end - found_partition_start + 1;
+                                        CopyMem(found_partition_signature, &entry->UniquePartitionGUID, sizeof(found_partition_signature));
+
+                                        found_partition_number = i + 1;
+                                        goto found;
+                                }
+                        }
+
+                        break; /* This GPT was fully valid, but we didn't find what we are looking for. This
+                                * means there's no reason to check the second copy of the GPT header */
+                }
+        }
+
+        return; /* Not found */
+
+found:
+        copy = DuplicateDevicePath(partition_path);
+
+        /* Patch in the data we found */
+        for (node = copy; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) {
+                HARDDRIVE_DEVICE_PATH *hd;
+
+                if (DevicePathType(node) != MEDIA_DEVICE_PATH)
+                        continue;
+
+                if (DevicePathSubType(node) != MEDIA_HARDDRIVE_DP)
+                        continue;
+
+                hd = (HARDDRIVE_DEVICE_PATH*) node;
+                hd->PartitionNumber = found_partition_number;
+                hd->PartitionStart = found_partition_start;
+                hd->PartitionSize = found_partition_size;
+                CopyMem(hd->Signature, found_partition_signature, sizeof(hd->Signature));
+                hd->MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER;
+                hd->SignatureType = SIGNATURE_TYPE_GUID;
+        }
+
+        r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, &copy, &new_device);
+        if (EFI_ERROR(r))
+                return;
+
+        root_dir = LibOpenRoot(new_device);
+        if (!root_dir)
+                return;
+
+        config_entry_add_linux(config, new_device, root_dir);
+        config_load_entries(config, new_device, root_dir, NULL);
+}
+
 static EFI_STATUS image_start(
                 EFI_HANDLE parent_image,
                 const Config *config,
@@ -2121,7 +2310,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
 
         root_dir = LibOpenRoot(loaded_image->DeviceHandle);
         if (!root_dir) {
-                Print(L"Unable to open root directory: %r ", err);
+                Print(L"Unable to open root directory.");
                 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
                 return EFI_LOAD_ERROR;
         }
@@ -2142,11 +2331,14 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
         config_load_defaults(&config, root_dir);
 
         /* scan /EFI/Linux/ directory */
-        config_entry_add_linux(&config, loaded_image, root_dir);
+        config_entry_add_linux(&config, loaded_image->DeviceHandle, root_dir);
 
         /* scan /loader/entries/\*.conf files */
         config_load_entries(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path);
 
+        /* Similar, but on any XBOOTLDR partition */
+        config_load_xbootldr(&config, loaded_image->DeviceHandle);
+
         /* sort entries after version number */
         config_sort_entries(&config);
 
diff --git a/src/boot/efi/crc32.c b/src/boot/efi/crc32.c
new file mode 100644 (file)
index 0000000..46b9aee
--- /dev/null
@@ -0,0 +1,142 @@
+/* This is copied from util-linux, which in turn copied in the version from Gary S. Brown */
+
+/*
+ *  COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or
+ *  code or tables extracted from it, as desired without restriction.
+ *
+ *  First, the polynomial itself and its table of feedback terms.  The
+ *  polynomial is
+ *  X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ *  Note that we take it "backwards" and put the highest-order term in
+ *  the lowest-order bit.  The X^32 term is "implied"; the LSB is the
+ *  X^31 term, etc.  The X^0 term (usually shown as "+1") results in
+ *  the MSB being 1.
+ *
+ *  Note that the usual hardware shift register implementation, which
+ *  is what we're using (we're merely optimizing it by doing eight-bit
+ *  chunks at a time) shifts bits into the lowest-order term.  In our
+ *  implementation, that means shifting towards the right.  Why do we
+ *  do it this way?  Because the calculated CRC must be transmitted in
+ *  order from highest-order term to lowest-order term.  UARTs transmit
+ *  characters in order from LSB to MSB.  By storing the CRC this way,
+ *  we hand it to the UART in the order low-byte to high-byte; the UART
+ *  sends each low-bit to high-bit; and the result is transmission bit
+ *  by bit from highest- to lowest-order term without requiring any bit
+ *  shuffling on our part.  Reception works similarly.
+ *
+ *  The feedback terms table consists of 256, 32-bit entries.  Notes
+ *
+ *      The table can be generated at runtime if desired; code to do so
+ *      is shown later.  It might not be obvious, but the feedback
+ *      terms simply represent the results of eight shift/xor opera-
+ *      tions for all combinations of data and CRC register values.
+ *
+ *      The values must be right-shifted by eight bits by the "updcrc"
+ *      logic; the shift must be unsigned (bring in zeroes).  On some
+ *      hardware you could probably optimize the shift in assembler by
+ *      using byte-swap instructions.
+ *      polynomial $edb88320
+ *
+ */
+
+#include "crc32.h"
+
+static const UINT32 crc32_tab[] = {
+        0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+        0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+        0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+        0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+        0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+        0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+        0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+        0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+        0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+        0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+        0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+        0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+        0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+        0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+        0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+        0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+        0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+        0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+        0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+        0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+        0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+        0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+        0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+        0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+        0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+        0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+        0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+        0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+        0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+        0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+        0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+        0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+        0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+        0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+        0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+        0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+        0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+        0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+        0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+        0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+        0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+        0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+        0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+        0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+        0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+        0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+        0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+        0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+        0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+        0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+        0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+        0x2d02ef8dL
+};
+
+static inline UINT32 crc32_add_char(UINT32 crc, UINT8 c) {
+        return crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8);
+}
+
+/*
+ * This a generic crc32() function, it takes seed as an argument,
+ * and does __not__ xor at the end. Then individual users can do
+ * whatever they need.
+ */
+UINT32 crc32(UINT32 seed, const VOID *buf, UINTN len) {
+        const UINT8 *p = buf;
+        UINT32 crc = seed;
+
+        while (len > 0) {
+                crc = crc32_add_char(crc, *p++);
+                len--;
+        }
+
+        return crc;
+}
+
+UINT32 crc32_exclude_offset(
+                UINT32 seed,
+                const VOID *buf,
+                UINTN len,
+                UINTN exclude_off,
+                UINTN exclude_len) {
+
+        const UINT8 *p = buf;
+        UINT32 crc = seed;
+        UINTN i;
+
+        for (i = 0; i < len; i++) {
+                UINT8 x = *p++;
+
+                if (i >= exclude_off && i < exclude_off + exclude_len)
+                        x = 0;
+
+                crc = crc32_add_char(crc, x);
+        }
+
+        return crc;
+}
diff --git a/src/boot/efi/crc32.h b/src/boot/efi/crc32.h
new file mode 100644 (file)
index 0000000..64150ee
--- /dev/null
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <efi.h>
+#include <efilib.h>
+
+UINT32 crc32(UINT32 seed, const VOID *buf, UINTN len);
+UINT32 crc32_exclude_offset(UINT32 seed, const VOID *buf, UINTN len, UINTN exclude_off, UINTN exclude_len);
index 2140151844e7e2615c0ab56876cb4e4fe053f6a6..0ae319163581e3c20a5b96e20770778423f4b525 100644 (file)
@@ -2,14 +2,15 @@
 
 efi_headers = files('''
         console.h
+        crc32.h
         disk.h
         graphics.h
         linux.h
         measure.h
         pe.h
+        shim.h
         splash.h
         util.h
-        shim.h
 '''.split())
 
 common_sources = '''
@@ -24,6 +25,7 @@ systemd_boot_sources = '''
         boot.c
         console.c
         shim.c
+        crc32.c
 '''.split()
 
 stub_sources = '''
index 2a60f38bf77a2c0e807eaf37ed77ab42e2698202..26c204fb438a1757083699eced8373f066b53444 100644 (file)
@@ -87,12 +87,13 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
 #endif
         }
 
-        /* export the device path this image is started from */
-        if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS)
-                efivar_set(L"LoaderDevicePartUUID", uuid, FALSE);
+        /* Export the device path this image is started from, if it's not set yet */
+        if (efivar_get_raw(&loader_guid, L"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS)
+                if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS)
+                        efivar_set(L"LoaderDevicePartUUID", uuid, FALSE);
 
         /* if LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from UEFI */
-        if (efivar_get_raw(&global_guid, L"LoaderImageIdentifier", &b, &size) != EFI_SUCCESS) {
+        if (efivar_get_raw(&loader_guid, L"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS) {
                 _cleanup_freepool_ CHAR16 *s;
 
                 s = DevicePathToStr(loaded_image->FilePath);
@@ -100,7 +101,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
         }
 
         /* if LoaderFirmwareInfo is not set, let's set it */
-        if (efivar_get_raw(&global_guid, L"LoaderFirmwareInfo", &b, &size) != EFI_SUCCESS) {
+        if (efivar_get_raw(&loader_guid, L"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) {
                 _cleanup_freepool_ CHAR16 *s;
 
                 s = PoolPrint(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
@@ -108,7 +109,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
         }
 
         /* ditto for LoaderFirmwareType */
-        if (efivar_get_raw(&global_guid, L"LoaderFirmwareType", &b, &size) != EFI_SUCCESS) {
+        if (efivar_get_raw(&loader_guid, L"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) {
                 _cleanup_freepool_ CHAR16 *s;
 
                 s = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
@@ -116,7 +117,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
         }
 
         /* add StubInfo */
-        if (efivar_get_raw(&global_guid, L"StubInfo", &b, &size) != EFI_SUCCESS)
+        if (efivar_get_raw(&loader_guid, L"StubInfo", NULL, NULL) != EFI_SUCCESS)
                 efivar_set(L"StubInfo", L"systemd-stub " GIT_VERSION, FALSE);
 
         if (szs[3] > 0)
index f1f1674c59493f031db986044fbd5c699948a7a3..bc1d4ae5c46225af62ece7507fe8a5ac3ab63aaf 100644 (file)
@@ -117,6 +117,9 @@ EFI_STATUS efivar_get(const CHAR16 *name, CHAR16 **value) {
         if ((size % 2) != 0)
                 return EFI_INVALID_PARAMETER;
 
+        if (!value)
+                return EFI_SUCCESS;
+
         /* Return buffer directly if it happens to be NUL terminated already */
         if (size >= 2 && buf[size-2] == 0 && buf[size-1] == 0) {
                 *value = (CHAR16*) buf;
@@ -141,7 +144,7 @@ EFI_STATUS efivar_get_int(const CHAR16 *name, UINTN *i) {
         EFI_STATUS err;
 
         err = efivar_get(name, &val);
-        if (!EFI_ERROR(err))
+        if (!EFI_ERROR(err) && i)
                 *i = Atoi(val);
 
         return err;
@@ -159,8 +162,10 @@ EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const CHAR16 *name, CHAR8 **bu
 
         err = uefi_call_wrapper(RT->GetVariable, 5, (CHAR16*) name, (EFI_GUID *)vendor, NULL, &l, buf);
         if (!EFI_ERROR(err)) {
-                *buffer = buf;
-                buf = NULL;
+
+                if (buffer)
+                        *buffer = TAKE_PTR(buf);
+
                 if (size)
                         *size = l;
         }
index 4ba7050a9159aef202e12f847be5ff0d580eb492..8c5e35ad2513d28003a227b31f48701059c30d79 100644 (file)
@@ -55,3 +55,10 @@ const EFI_GUID loader_guid;
 
 #define UINTN_MAX (~(UINTN)0)
 #define INTN_MAX ((INTN)(UINTN_MAX>>1))
+
+#define TAKE_PTR(ptr)                           \
+        ({                                      \
+                typeof(ptr) _ptr_ = (ptr);      \
+                (ptr) = NULL;                   \
+                _ptr_;                          \
+        })
index 30a6d356d0e32d24c94c1b52123efc9094c318a0..d1bfa775e4375a5c39e242943253cf062c2295dd 100644 (file)
@@ -722,10 +722,11 @@ static int add_sysroot_usr_mount(void) {
 }
 
 static int add_volatile_root(void) {
+
         /* Let's add in systemd-remount-volatile.service which will remount the root device to tmpfs if this is
-         * requested, leaving only /usr from the root mount inside. */
+         * requested (or as an overlayfs), leaving only /usr from the root mount inside. */
 
-        if (arg_volatile_mode != VOLATILE_YES)
+        if (!IN_SET(arg_volatile_mode, VOLATILE_YES, VOLATILE_OVERLAY))
                 return 0;
 
         return generator_add_symlink(arg_dest, SPECIAL_INITRD_ROOT_FS_TARGET, "requires",
index c9bc2b381553d95431691659d0f431529e4fd2ae..6d887eaf1f8d1c07baa95b7312621053699af791 100644 (file)
@@ -49,6 +49,9 @@ static void fuzz_client(const uint8_t *data, size_t size, bool is_information_re
 }
 
 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+        if (size > 65536)
+                return 0;
+
         /* This triggers client_receive_advertise */
         fuzz_client(data, size, false);
 
diff --git a/src/fuzz/fuzz-dhcp6-client.options b/src/fuzz/fuzz-dhcp6-client.options
new file mode 100644 (file)
index 0000000..678d526
--- /dev/null
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
index 09c0bcba2da207313dd8ebfd028e00727724dfc8..0f1e184eea932cc149eb1c8a8fa42e4750b57aa2 100644 (file)
@@ -18,6 +18,7 @@
 #include "efivars.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "fs-util.h"
 #include "fstab-util.h"
 #include "generator.h"
 #include "gpt.h"
@@ -322,7 +323,6 @@ static int add_swap(const char *path) {
         return generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, "wants", name);
 }
 
-#if ENABLE_EFI
 static int add_automount(
                 const char *id,
                 const char *what,
@@ -384,8 +384,43 @@ static int add_automount(
         return generator_add_symlink(arg_dest, SPECIAL_LOCAL_FS_TARGET, "wants", unit);
 }
 
-static int add_esp(DissectedPartition *p) {
-        const char *esp;
+static int add_xbootldr(DissectedPartition *p) {
+        int r;
+
+        assert(p);
+
+        if (in_initrd()) {
+                log_debug("In initrd, ignoring the XBOOTLDR partition.");
+                return 0;
+        }
+
+        r = fstab_is_mount_point("/boot");
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse fstab: %m");
+        if (r > 0) {
+                log_debug("/boot specified in fstab, ignoring XBOOTLDR partition.");
+                return 0;
+        }
+
+        r = path_is_busy("/boot");
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 0;
+
+        return add_automount("boot",
+                             p->node,
+                             "/boot",
+                             p->fstype,
+                             true,
+                             "umask=0077",
+                             "Boot Loader Partition",
+                             120 * USEC_PER_SEC);
+}
+
+#if ENABLE_EFI
+static int add_esp(DissectedPartition *p, bool has_xbootldr) {
+        const char *esp_path = NULL, *id = NULL;
         int r;
 
         assert(p);
@@ -395,21 +430,37 @@ static int add_esp(DissectedPartition *p) {
                 return 0;
         }
 
-        /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice */
-        esp = access("/efi/", F_OK) >= 0 ? "/efi" : "/boot";
+        /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice, but
+         * only if there's no explicit XBOOTLDR partition around. */
+        if (access("/efi", F_OK) < 0) {
+                if (errno != ENOENT)
+                        return log_error_errno(errno, "Failed to determine whether /efi exists: %m");
+
+                /* Use /boot as fallback, but only if there's no XBOOTLDR partition */
+                if (!has_xbootldr) {
+                        esp_path = "/boot";
+                        id = "boot";
+                }
+        }
+        if (!esp_path)
+                esp_path = "/efi";
+        if (!id)
+                id = "efi";
 
         /* We create an .automount which is not overridden by the .mount from the fstab generator. */
-        r = fstab_is_mount_point(esp);
+        r = fstab_is_mount_point(esp_path);
         if (r < 0)
                 return log_error_errno(r, "Failed to parse fstab: %m");
         if (r > 0) {
-                log_debug("%s specified in fstab, ignoring.", esp);
+                log_debug("%s specified in fstab, ignoring.", esp_path);
                 return 0;
         }
 
-        r = path_is_busy(esp);
-        if (r != 0)
-                return r < 0 ? r : 0;
+        r = path_is_busy(esp_path);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 0;
 
         if (is_efi_boot()) {
                 sd_id128_t loader_uuid;
@@ -425,15 +476,15 @@ static int add_esp(DissectedPartition *p) {
                         return log_error_errno(r, "Failed to read ESP partition UUID: %m");
 
                 if (!sd_id128_equal(p->uuid, loader_uuid)) {
-                        log_debug("Partition for %s does not appear to be the partition we are booted from.", esp);
+                        log_debug("Partition for %s does not appear to be the partition we are booted from.", p->node);
                         return 0;
                 }
         } else
                 log_debug("Not an EFI boot, skipping ESP check.");
 
-        return add_automount("boot",
+        return add_automount(id,
                              p->node,
-                             esp,
+                             esp_path,
                              p->fstype,
                              true,
                              "umask=0077",
@@ -441,7 +492,7 @@ static int add_esp(DissectedPartition *p) {
                              120 * USEC_PER_SEC);
 }
 #else
-static int add_esp(DissectedPartition *p) {
+static int add_esp(DissectedPartition *p, bool has_xbootldr) {
         return 0;
 }
 #endif
@@ -483,7 +534,7 @@ static int add_root_rw(DissectedPartition *p) {
         return 0;
 }
 
-static int open_parent(dev_t devnum, int *ret) {
+static int open_parent_devno(dev_t devnum, int *ret) {
         _cleanup_(sd_device_unrefp) sd_device *d = NULL;
         const char *name, *devtype, *node;
         sd_device *parent;
@@ -551,7 +602,7 @@ static int enumerate_partitions(dev_t devnum) {
         _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
         int r, k;
 
-        r = open_parent(devnum, &fd);
+        r = open_parent_devno(devnum, &fd);
         if (r <= 0)
                 return r;
 
@@ -569,8 +620,14 @@ static int enumerate_partitions(dev_t devnum) {
                         r = k;
         }
 
+        if (m->partitions[PARTITION_XBOOTLDR].found) {
+                k = add_xbootldr(m->partitions + PARTITION_XBOOTLDR);
+                if (k < 0)
+                        r = k;
+        }
+
         if (m->partitions[PARTITION_ESP].found) {
-                k = add_esp(m->partitions + PARTITION_ESP);
+                k = add_esp(m->partitions + PARTITION_ESP, m->partitions[PARTITION_XBOOTLDR].found);
                 if (k < 0)
                         r = k;
         }
@@ -707,8 +764,25 @@ static int add_mounts(void) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to determine block device of /usr file system: %m");
                 if (r == 0) {
-                        log_debug("Neither root nor /usr file system are on a (single) block device.");
-                        return 0;
+                        _cleanup_free_ char *p = NULL;
+                        mode_t m;
+
+                        /* If the root mount has been replaced by some form of volatile file system (overlayfs), the
+                         * original root block device node is symlinked in /run/systemd/volatile-root. Let's read that
+                         * here. */
+                        r = readlink_malloc("/run/systemd/volatile-root", &p);
+                        if (r == -ENOENT) {
+                                log_debug("Neither root nor /usr file system are on a (single) block device.");
+                                return 0;
+                        }
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to read symlink /run/systemd/volatile-root: %m");
+
+                        r = device_path_parse_major_minor(p, &m, &devno);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse major/minor device node: %m");
+                        if (!S_ISBLK(m))
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Volatile root device is of wrong type.");
                 }
         }
 
index 6a02b47a1745577ebb3415b7bffede7bcc28b579..c1c946cd2be1800385516daec9066a188dcf4844 100644 (file)
@@ -223,7 +223,7 @@ static int raw_export_process(RawExport *e) {
 
 finish:
         if (r >= 0) {
-                (void) copy_times(e->input_fd, e->output_fd);
+                (void) copy_times(e->input_fd, e->output_fd, COPY_CRTIME);
                 (void) copy_xattr(e->input_fd, e->output_fd);
         }
 
index 4b1161557ddc28656428415b35bcb16d2581d9fe..56f3431a08474d2a4bb272b4d578c1a5d951e78f 100644 (file)
@@ -215,7 +215,7 @@ static int raw_import_finish(RawImport *i) {
                 return r;
 
         if (S_ISREG(i->st.st_mode)) {
-                (void) copy_times(i->input_fd, i->output_fd);
+                (void) copy_times(i->input_fd, i->output_fd, COPY_CRTIME);
                 (void) copy_xattr(i->input_fd, i->output_fd);
         }
 
index 3a3e015df8faad76c45c92e3740f0b7b2af464d5..72b9054e49c67ec51733cae58371195845a1ec65 100644 (file)
@@ -368,7 +368,7 @@ static int raw_pull_make_local_copy(RawPull *i) {
                 return log_error_errno(r, "Failed to make writable copy of image: %m");
         }
 
-        (void) copy_times(i->raw_job->disk_fd, dfd);
+        (void) copy_times(i->raw_job->disk_fd, dfd, COPY_CRTIME);
         (void) copy_xattr(i->raw_job->disk_fd, dfd);
 
         dfd = safe_close(dfd);
index a8d61bf72a4e5a489da2040d964101ab4111ef58..04864d4df01b8db6697749bfbda525f312270117 100644 (file)
@@ -219,11 +219,11 @@ struct sd_bus {
         size_t rbuffer_size;
 
         sd_bus_message **rqueue;
-        unsigned rqueue_size;
+        size_t rqueue_size;
         size_t rqueue_allocated;
 
         sd_bus_message **wqueue;
-        unsigned wqueue_size;
+        size_t wqueue_size;
         size_t windex;
         size_t wqueue_allocated;
 
index bb7e09c945ab7b7c18e578fdbc7e033691a2a025..700c57d3621d223957faac3f22e710e57af5a49c 100644 (file)
@@ -118,7 +118,8 @@ static sd_bus_message* message_free(sd_bus_message *m) {
 
         message_reset_parts(m);
 
-        sd_bus_unref(m->bus);
+        /* Note that we don't unref m->bus here. That's already done by sd_bus_message_unref() as each user
+         * reference to the bus message also is considered a reference to the bus connection itself. */
 
         if (m->free_fds) {
                 close_many(m->fds, m->n_fds);
@@ -136,8 +137,6 @@ static sd_bus_message* message_free(sd_bus_message *m) {
         return mfree(m);
 }
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(sd_bus_message*, message_free);
-
 static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz, bool add_offset) {
         void *op, *np;
         size_t old_size, new_size, start;
@@ -459,7 +458,6 @@ int bus_message_from_header(
         if (!m)
                 return -ENOMEM;
 
-        m->n_ref = 1;
         m->sealed = true;
         m->header = header;
         m->header_accessible = header_accessible;
@@ -513,7 +511,9 @@ int bus_message_from_header(
                 m->creds.mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
         }
 
+        m->n_ref = 1;
         m->bus = sd_bus_ref(bus);
+
         *ret = TAKE_PTR(m);
 
         return 0;
@@ -528,7 +528,7 @@ int bus_message_from_malloc(
                 const char *label,
                 sd_bus_message **ret) {
 
-        _cleanup_(message_freep) sd_bus_message *m = NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
         size_t sz;
         int r;
 
@@ -585,13 +585,13 @@ _public_ int sd_bus_message_new(
                 return -ENOMEM;
 
         t->n_ref = 1;
+        t->bus = sd_bus_ref(bus);
         t->header = (struct bus_header*) ((uint8_t*) t + ALIGN(sizeof(struct sd_bus_message)));
         t->header->endian = BUS_NATIVE_ENDIAN;
         t->header->type = type;
         t->header->version = bus->message_version;
         t->allow_fds = bus->can_fds || !IN_SET(bus->state, BUS_HELLO, BUS_RUNNING);
         t->root_container.need_offsets = BUS_MESSAGE_IS_GVARIANT(t);
-        t->bus = sd_bus_ref(bus);
 
         if (bus->allow_interactive_authorization)
                 t->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION;
@@ -647,7 +647,7 @@ _public_ int sd_bus_message_new_method_call(
                 const char *interface,
                 const char *member) {
 
-        _cleanup_(message_freep) sd_bus_message *t = NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
         int r;
 
         assert_return(bus, -ENOTCONN);
@@ -692,7 +692,7 @@ static int message_new_reply(
                 uint8_t type,
                 sd_bus_message **m) {
 
-        _cleanup_(message_freep) sd_bus_message *t = NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
         uint64_t cookie;
         int r;
 
@@ -743,7 +743,7 @@ _public_ int sd_bus_message_new_method_error(
                 sd_bus_message **m,
                 const sd_bus_error *e) {
 
-        _cleanup_(message_freep) sd_bus_message *t = NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
         int r;
 
         assert_return(sd_bus_error_is_set(e), -EINVAL);
@@ -846,7 +846,7 @@ int bus_message_new_synthetic_error(
                 const sd_bus_error *e,
                 sd_bus_message **m) {
 
-        _cleanup_(message_freep) sd_bus_message *t = NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
         int r;
 
         assert(bus);
@@ -890,7 +890,80 @@ int bus_message_new_synthetic_error(
         return 0;
 }
 
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus_message, sd_bus_message, message_free);
+
+_public_ sd_bus_message* sd_bus_message_ref(sd_bus_message *m) {
+        if (!m)
+                return NULL;
+
+        /* We are fine if this message so far was either explicitly reffed or not reffed but queued into at
+         * least one bus connection object. */
+        assert(m->n_ref > 0 || m->n_queued > 0);
+
+        m->n_ref++;
+
+        /* Each user reference to a bus message shall also be considered a ref on the bus */
+        sd_bus_ref(m->bus);
+        return m;
+}
+
+_public_ sd_bus_message* sd_bus_message_unref(sd_bus_message *m) {
+        if (!m)
+                return NULL;
+
+        assert(m->n_ref > 0);
+
+        sd_bus_unref(m->bus); /* Each regular ref is also a ref on the bus connection. Let's hence drop it
+                               * here. Note we have to do this before decrementing our own n_ref here, since
+                               * otherwise, if this message is currently queued sd_bus_unref() might call
+                               * bus_message_unref_queued() for this which might then destroy the message
+                               * while we are still processing it. */
+        m->n_ref--;
+
+        if (m->n_ref > 0 || m->n_queued > 0)
+                return NULL;
+
+        /* Unset the bus field if neither the user has a reference nor this message is queued. We are careful
+         * to reset the field only after the last reference to the bus is dropped, after all we might keep
+         * multiple references to the bus, once for each reference kept on outselves. */
+        m->bus = NULL;
+
+        return message_free(m);
+}
+
+sd_bus_message* bus_message_ref_queued(sd_bus_message *m, sd_bus *bus) {
+        if (!m)
+                return NULL;
+
+        /* If this is a different bus than the message is associated with, then implicitly turn this into a
+         * regular reference. This means that you can create a memory leak by enqueuing a message generated
+         * on one bus onto another at the same time as enqueueing a message from the second one on the first,
+         * as we'll not detect the cyclic references there. */
+        if (bus != m->bus)
+                return sd_bus_message_ref(m);
+
+        assert(m->n_ref > 0 || m->n_queued > 0);
+        m->n_queued++;
+
+        return m;
+}
+
+sd_bus_message* bus_message_unref_queued(sd_bus_message *m, sd_bus *bus) {
+        if (!m)
+                return NULL;
+
+        if (bus != m->bus)
+                return sd_bus_message_unref(m);
+
+        assert(m->n_queued > 0);
+        m->n_queued--;
+
+        if (m->n_ref > 0 || m->n_queued > 0)
+                return NULL;
+
+        m->bus = NULL;
+
+        return message_free(m);
+}
 
 _public_ int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type) {
         assert_return(m, -EINVAL);
index 0115437d26b3b8b109f1948199ccf6829bb95c82..a7c4f81c4bac4f0879aa894c13f299d8250e9637 100644 (file)
@@ -48,7 +48,16 @@ struct bus_body_part {
 };
 
 struct sd_bus_message {
-        unsigned n_ref;
+        /* Caveat: a message can be referenced in two different ways: the main (user-facing) way will also
+         * pin the bus connection object the message is associated with. The secondary way ("queued") is used
+         * when a message is in the read or write queues of the bus connection object, which will not pin the
+         * bus connection object. This is necessary so that we don't have to have a pair of cyclic references
+         * between a message that is queued and its connection: as soon as a message is only referenced by
+         * the connection (by means of being queued) and the connection itself has no other references it
+         * will be freed. */
+
+        unsigned n_ref;     /* Counter of references that pin the connection */
+        unsigned n_queued;  /* Counter of references that do not pin the connection */
 
         sd_bus *bus;
 
@@ -211,3 +220,6 @@ int bus_message_remarshal(sd_bus *bus, sd_bus_message **m);
 
 void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m);
 void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m);
+
+sd_bus_message* bus_message_ref_queued(sd_bus_message *m, sd_bus *bus);
+sd_bus_message* bus_message_unref_queued(sd_bus_message *m, sd_bus *bus);
index 441b4a816f3af989459fc4024378fd0b413c2736..df9b2631fd81fa06a3ae8d630a5f9b15a9d66936 100644 (file)
@@ -1110,8 +1110,10 @@ static int bus_socket_make_message(sd_bus *bus, size_t size) {
         bus->fds = NULL;
         bus->n_fds = 0;
 
-        if (t)
-                bus->rqueue[bus->rqueue_size++] = t;
+        if (t) {
+                bus->rqueue[bus->rqueue_size++] = bus_message_ref_queued(t, bus);
+                sd_bus_message_unref(t);
+        }
 
         return 1;
 }
index 9eeb3c448c2f22de69cdac3b040df2c184bbfcbe..69f15199763e8db965998ee8712981310c56746f 100644 (file)
@@ -146,13 +146,13 @@ static void bus_reset_queues(sd_bus *b) {
         assert(b);
 
         while (b->rqueue_size > 0)
-                sd_bus_message_unref(b->rqueue[--b->rqueue_size]);
+                bus_message_unref_queued(b->rqueue[--b->rqueue_size], b);
 
         b->rqueue = mfree(b->rqueue);
         b->rqueue_allocated = 0;
 
         while (b->wqueue_size > 0)
-                sd_bus_message_unref(b->wqueue[--b->wqueue_size]);
+                bus_message_unref_queued(b->wqueue[--b->wqueue_size], b);
 
         b->wqueue = mfree(b->wqueue);
         b->wqueue_allocated = 0;
@@ -248,12 +248,12 @@ _public_ int sd_bus_new(sd_bus **ret) {
                 .close_on_exit = true,
         };
 
-        assert_se(pthread_mutex_init(&b->memfd_cache_mutex, NULL) == 0);
-
         /* We guarantee that wqueue always has space for at least one entry */
         if (!GREEDY_REALLOC(b->wqueue, b->wqueue_allocated, 1))
                 return -ENOMEM;
 
+        assert_se(pthread_mutex_init(&b->memfd_cache_mutex, NULL) == 0);
+
         *ret = TAKE_PTR(b);
         return 0;
 }
@@ -493,7 +493,7 @@ static int synthesize_connected_signal(sd_bus *bus) {
 
         /* Insert at the very front */
         memmove(bus->rqueue + 1, bus->rqueue, sizeof(sd_bus_message*) * bus->rqueue_size);
-        bus->rqueue[0] = TAKE_PTR(m);
+        bus->rqueue[0] = bus_message_ref_queued(m, bus);
         bus->rqueue_size++;
 
         return 0;
@@ -1811,7 +1811,7 @@ static int dispatch_wqueue(sd_bus *bus) {
                          * anyway. */
 
                         bus->wqueue_size--;
-                        sd_bus_message_unref(bus->wqueue[0]);
+                        bus_message_unref_queued(bus->wqueue[0], bus);
                         memmove(bus->wqueue, bus->wqueue + 1, sizeof(sd_bus_message*) * bus->wqueue_size);
                         bus->windex = 0;
 
@@ -1840,6 +1840,15 @@ int bus_rqueue_make_room(sd_bus *bus) {
         return 0;
 }
 
+static void rqueue_drop_one(sd_bus *bus, size_t i) {
+        assert(bus);
+        assert(i < bus->rqueue_size);
+
+        bus_message_unref_queued(bus->rqueue[i], bus);
+        memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1));
+        bus->rqueue_size--;
+}
+
 static int dispatch_rqueue(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **m) {
         int r, ret = 0;
 
@@ -1854,10 +1863,8 @@ static int dispatch_rqueue(sd_bus *bus, bool hint_priority, int64_t priority, sd
         for (;;) {
                 if (bus->rqueue_size > 0) {
                         /* Dispatch a queued message */
-
-                        *m = bus->rqueue[0];
-                        bus->rqueue_size--;
-                        memmove(bus->rqueue, bus->rqueue + 1, sizeof(sd_bus_message*) * bus->rqueue_size);
+                        *m = sd_bus_message_ref(bus->rqueue[0]);
+                        rqueue_drop_one(bus, 0);
                         return 1;
                 }
 
@@ -1865,8 +1872,10 @@ static int dispatch_rqueue(sd_bus *bus, bool hint_priority, int64_t priority, sd
                 r = bus_read_message(bus, hint_priority, priority);
                 if (r < 0)
                         return r;
-                if (r == 0)
+                if (r == 0) {
+                        *m = NULL;
                         return ret;
+                }
 
                 ret = 1;
         }
@@ -1933,7 +1942,7 @@ _public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) {
                          * of the wqueue array is always allocated so
                          * that we always can remember how much was
                          * written. */
-                        bus->wqueue[0] = sd_bus_message_ref(m);
+                        bus->wqueue[0] = bus_message_ref_queued(m, bus);
                         bus->wqueue_size = 1;
                         bus->windex = idx;
                 }
@@ -1947,7 +1956,7 @@ _public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) {
                 if (!GREEDY_REALLOC(bus->wqueue, bus->wqueue_allocated, bus->wqueue_size + 1))
                         return -ENOMEM;
 
-                bus->wqueue[bus->wqueue_size++] = sd_bus_message_ref(m);
+                bus->wqueue[bus->wqueue_size++] = bus_message_ref_queued(m, bus);
         }
 
 finish:
@@ -2125,7 +2134,7 @@ _public_ int sd_bus_call(
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m);
         usec_t timeout;
         uint64_t cookie;
-        unsigned i;
+        size_t i;
         int r;
 
         bus_assert_return(m, -EINVAL, error);
@@ -2167,37 +2176,30 @@ _public_ int sd_bus_call(
                 usec_t left;
 
                 while (i < bus->rqueue_size) {
-                        sd_bus_message *incoming = NULL;
+                        _cleanup_(sd_bus_message_unrefp) sd_bus_message *incoming = NULL;
 
-                        incoming = bus->rqueue[i];
+                        incoming = sd_bus_message_ref(bus->rqueue[i]);
 
                         if (incoming->reply_cookie == cookie) {
                                 /* Found a match! */
 
-                                memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1));
-                                bus->rqueue_size--;
+                                rqueue_drop_one(bus, i);
                                 log_debug_bus_message(incoming);
 
                                 if (incoming->header->type == SD_BUS_MESSAGE_METHOD_RETURN) {
 
                                         if (incoming->n_fds <= 0 || bus->accept_fd) {
                                                 if (reply)
-                                                        *reply = incoming;
-                                                else
-                                                        sd_bus_message_unref(incoming);
+                                                        *reply = TAKE_PTR(incoming);
 
                                                 return 1;
                                         }
 
-                                        r = sd_bus_error_setf(error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptors which I couldn't accept. Sorry.");
-                                        sd_bus_message_unref(incoming);
-                                        return r;
+                                        return sd_bus_error_setf(error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptors which I couldn't accept. Sorry.");
 
-                                } else if (incoming->header->type == SD_BUS_MESSAGE_METHOD_ERROR) {
-                                        r = sd_bus_error_copy(error, &incoming->error);
-                                        sd_bus_message_unref(incoming);
-                                        return r;
-                                } else {
+                                } else if (incoming->header->type == SD_BUS_MESSAGE_METHOD_ERROR)
+                                        return sd_bus_error_copy(error, &incoming->error);
+                                else {
                                         r = -EIO;
                                         goto fail;
                                 }
@@ -2207,15 +2209,11 @@ _public_ int sd_bus_call(
                                    incoming->sender &&
                                    streq(bus->unique_name, incoming->sender)) {
 
-                                memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1));
-                                bus->rqueue_size--;
+                                rqueue_drop_one(bus, i);
 
-                                /* Our own message? Somebody is trying
-                                 * to send its own client a message,
-                                 * let's not dead-lock, let's fail
-                                 * immediately. */
+                                /* Our own message? Somebody is trying to send its own client a message,
+                                 * let's not dead-lock, let's fail immediately. */
 
-                                sd_bus_message_unref(incoming);
                                 r = -ELOOP;
                                 goto fail;
                         }
@@ -2673,7 +2671,6 @@ static int process_builtin(sd_bus *bus, sd_bus_message *m) {
                                 SD_BUS_ERROR_UNKNOWN_METHOD,
                                  "Unknown method '%s' on interface '%s'.", m->member, m->interface);
         }
-
         if (r < 0)
                 return r;
 
@@ -2797,7 +2794,6 @@ static int process_running(sd_bus *bus, bool hint_priority, int64_t priority, sd
                         return r;
 
                 *ret = TAKE_PTR(m);
-
                 return 1;
         }
 
index db5ff72ef4459c47ec575db8fa18a89c7125088e..c58c52a778bf6b6843aa87d82cfdfa6863f7e5f6 100644 (file)
@@ -40,8 +40,8 @@ static void test_bus_set_address_system_remote(char **args) {
                                  -EINVAL, NULL);
         test_one_address(b, "user@host",
                          0, "unixexec:path=ssh,argv1=-xT,argv2=--,argv3=user%40host,argv4=systemd-stdio-bridge");
-         test_one_address(b, "user@host@host",
-                                 -EINVAL, NULL);
+        test_one_address(b, "user@host@host",
+                         -EINVAL, NULL);
         test_one_address(b, "[::1]",
                          0, "unixexec:path=ssh,argv1=-xT,argv2=--,argv3=%3a%3a1,argv4=systemd-stdio-bridge");
         test_one_address(b, "user@[::1]",
diff --git a/src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c b/src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c
new file mode 100644 (file)
index 0000000..70901c3
--- /dev/null
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "main-func.h"
+#include "sd-bus.h"
+#include "tests.h"
+
+static int run(int argc, char *argv[]) {
+        sd_bus_message *m = NULL;
+        sd_bus *bus = NULL;
+        int r;
+
+        /* This test will result in a memory leak in <= v240, but not on v241. Hence to be really useful it
+         * should be run through a leak tracker such as valgrind. */
+
+        r = sd_bus_open_system(&bus);
+        if (r < 0)
+                return log_tests_skipped("Failed to connect to bus");
+
+        /* Create a message and enqueue it (this shouldn't send it though as the connection setup is not complete yet) */
+        assert_se(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/foo", "quux.quux", "waldo") >= 0);
+        assert_se(sd_bus_send(bus, m, NULL) >= 0);
+
+        /* Let's now unref the message first and the bus second. */
+        m = sd_bus_message_unref(m);
+        bus = sd_bus_unref(bus);
+
+        /* We should have a memory leak now on <= v240. Let's do this again, but destory in the opposite
+         * order. On v240 that too should be a leak. */
+
+        r = sd_bus_open_system(&bus);
+        if (r < 0)
+                return log_tests_skipped("Failed to connect to bus");
+
+        assert_se(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/foo", "quux.quux", "waldo") >= 0);
+        assert_se(sd_bus_send(bus, m, NULL) >= 0);
+
+        /* Let's now unref things in the opposite order */
+        bus = sd_bus_unref(bus);
+        m = sd_bus_message_unref(m);
+
+        return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
index 8049be48c71bf55254a181045b719f4410beb004..a5928b0a4f3915d023270cc502afbfb6924cf31f 100644 (file)
@@ -13,6 +13,10 @@ TAG=="uaccess", SUBSYSTEM!="sound", TAG+="seat"
 SUBSYSTEM=="sound", KERNEL=="card*", TAG+="seat"
 SUBSYSTEM=="input", KERNEL=="input*", TAG+="seat"
 SUBSYSTEM=="graphics", KERNEL=="fb[0-9]*", TAG+="seat"
+
+# HyperV currently doesn't do DRM, hence we need to synthesize for HyperV's fb device instead
+SUBSYSTEM=="graphics", KERNEL=="fb[0-9]", DRIVERS=="hyperv_fb", TAG+="master-of-seat"
+
 SUBSYSTEM=="drm", KERNEL=="card[0-9]*", TAG+="seat", TAG+="master-of-seat"
 SUBSYSTEM=="usb", ATTR{bDeviceClass}=="09", TAG+="seat"
 
index a9af889747b838e730c3b2c4f1526debf02d2c5c..eb0a26ef35bf491d935c77312fb142b37b46b67a 100644 (file)
@@ -212,6 +212,8 @@ int bind_mount_parse(CustomMount **l, size_t *n, const char *s, bool read_only)
 
         if (!path_is_absolute(destination))
                 return -EINVAL;
+        if (empty_or_root(destination))
+                return -EINVAL;
 
         m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND);
         if (!m)
@@ -251,6 +253,8 @@ int tmpfs_mount_parse(CustomMount **l, size_t *n, const char *s) {
 
         if (!path_is_absolute(path))
                 return -EINVAL;
+        if (empty_or_root(path))
+                return -EINVAL;
 
         m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS);
         if (!m)
@@ -310,6 +314,9 @@ int overlay_mount_parse(CustomMount **l, size_t *n, const char *s, bool read_onl
                         return -EINVAL;
         }
 
+        if (empty_or_root(destination))
+                return -EINVAL;
+
         m = custom_mount_add(l, n, CUSTOM_MOUNT_OVERLAY);
         if (!m)
                 return -ENOMEM;
@@ -849,9 +856,8 @@ int mount_custom(
         return 0;
 }
 
-int setup_volatile_state(
+static int setup_volatile_state(
                 const char *directory,
-                VolatileMode mode,
                 bool userns, uid_t uid_shift, uid_t uid_range,
                 const char *selinux_apifs_context) {
 
@@ -861,11 +867,7 @@ int setup_volatile_state(
 
         assert(directory);
 
-        if (mode != VOLATILE_STATE)
-                return 0;
-
-        /* --volatile=state means we simply overmount /var
-           with a tmpfs, and the rest read-only. */
+        /* --volatile=state means we simply overmount /var with a tmpfs, and the rest read-only. */
 
         r = bind_remount_recursive(directory, true, NULL);
         if (r < 0)
@@ -886,9 +888,8 @@ int setup_volatile_state(
         return mount_verbose(LOG_ERR, "tmpfs", p, "tmpfs", MS_STRICTATIME, options);
 }
 
-int setup_volatile(
+static int setup_volatile_yes(
                 const char *directory,
-                VolatileMode mode,
                 bool userns, uid_t uid_shift, uid_t uid_range,
                 const char *selinux_apifs_context) {
 
@@ -900,11 +901,8 @@ int setup_volatile(
 
         assert(directory);
 
-        if (mode != VOLATILE_YES)
-                return 0;
-
-        /* --volatile=yes means we mount a tmpfs to the root dir, and
-           the original /usr to use inside it, and that read-only. */
+        /* --volatile=yes means we mount a tmpfs to the root dir, and the original /usr to use inside it, and that
+           read-only. */
 
         if (!mkdtemp(template))
                 return log_error_errno(errno, "Failed to create temporary directory: %m");
@@ -912,7 +910,7 @@ int setup_volatile(
         options = "mode=755";
         r = tmpfs_patch_options(options, uid_shift == 0 ? UID_INVALID : uid_shift, selinux_apifs_context, &buf);
         if (r < 0)
-                return log_oom();
+                goto fail;
         if (r > 0)
                 options = buf;
 
@@ -961,6 +959,93 @@ fail:
         return r;
 }
 
+static int setup_volatile_overlay(
+                const char *directory,
+                bool userns, uid_t uid_shift, uid_t uid_range,
+                const char *selinux_apifs_context) {
+
+        _cleanup_free_ char *buf = NULL, *escaped_directory = NULL, *escaped_upper = NULL, *escaped_work = NULL;
+        char template[] = "/tmp/nspawn-volatile-XXXXXX";
+        const char *upper, *work, *options;
+        bool tmpfs_mounted = false;
+        int r;
+
+        assert(directory);
+
+        /* --volatile=overlay means we mount an overlayfs to the root dir. */
+
+        if (!mkdtemp(template))
+                return log_error_errno(errno, "Failed to create temporary directory: %m");
+
+        options = "mode=755";
+        r = tmpfs_patch_options(options, uid_shift == 0 ? UID_INVALID : uid_shift, selinux_apifs_context, &buf);
+        if (r < 0)
+                goto finish;
+        if (r > 0)
+                options = buf;
+
+        r = mount_verbose(LOG_ERR, "tmpfs", template, "tmpfs", MS_STRICTATIME, options);
+        if (r < 0)
+                goto finish;
+
+        tmpfs_mounted = true;
+
+        upper = strjoina(template, "/upper");
+        work = strjoina(template, "/work");
+
+        if (mkdir(upper, 0755) < 0) {
+                r = log_error_errno(errno, "Failed to create %s: %m", upper);
+                goto finish;
+        }
+        if (mkdir(work, 0755) < 0) {
+                r = log_error_errno(errno, "Failed to create %s: %m", work);
+                goto finish;
+        }
+
+        /* And now, let's overmount the root dir with an overlayfs that uses the root dir as lower dir. It's kinda nice
+         * that the kernel allows us to do that without going through some mount point rearrangements. */
+
+        escaped_directory = shell_escape(directory, ",:");
+        escaped_upper = shell_escape(upper, ",:");
+        escaped_work = shell_escape(work, ",:");
+        if (!escaped_directory || !escaped_upper || !escaped_work) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        options = strjoina("lowerdir=", escaped_directory, ",upperdir=", escaped_upper, ",workdir=", escaped_work);
+        r = mount_verbose(LOG_ERR, "overlay", directory, "overlay", 0, options);
+
+finish:
+        if (tmpfs_mounted)
+                (void) umount_verbose(template);
+
+        (void) rmdir(template);
+        return r;
+}
+
+int setup_volatile_mode(
+                const char *directory,
+                VolatileMode mode,
+                bool userns, uid_t uid_shift, uid_t uid_range,
+                const char *selinux_apifs_context) {
+
+        switch (mode) {
+
+        case VOLATILE_YES:
+                return setup_volatile_yes(directory, userns, uid_shift, uid_range, selinux_apifs_context);
+
+        case VOLATILE_STATE:
+                return setup_volatile_state(directory, userns, uid_shift, uid_range, selinux_apifs_context);
+
+        case VOLATILE_OVERLAY:
+                return setup_volatile_overlay(directory, userns, uid_shift, uid_range, selinux_apifs_context);
+
+        default:
+                return 0;
+        }
+}
+
 /* Expects *pivot_root_new and *pivot_root_old to be initialised to allocated memory or NULL. */
 int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s) {
         _cleanup_free_ char *root_new = NULL, *root_old = NULL;
index 8051a7d9d91ae4a263f159f42d35681bf18e7049..e060ca0e4de594b41e83f5ca950a645cc04c72de 100644 (file)
@@ -49,8 +49,7 @@ int mount_sysfs(const char *dest, MountSettingsMask mount_settings);
 
 int mount_custom(const char *dest, CustomMount *mounts, size_t n, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
 
-int setup_volatile(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
-int setup_volatile_state(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
+int setup_volatile_mode(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
 
 int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s);
 int setup_pivot_root(const char *directory, const char *pivot_root_new, const char *pivot_root_old);
index e0c2d711e60a9f3d17a202343ef01f55d5a7c1d1..5cb049e5f734fe4ac6dd2e32405388182976f93a 100644 (file)
@@ -1308,6 +1308,9 @@ static int verify_arguments(void) {
         if (arg_start_mode == START_BOOT && arg_kill_signal <= 0)
                 arg_kill_signal = SIGRTMIN+3;
 
+        if (arg_volatile_mode != VOLATILE_NO) /* Make sure all file systems contained in the image are mounted read-only if we are in volatile mode */
+                arg_read_only = true;
+
         if (arg_keep_unit && arg_register && cg_pid_get_owner_uid(0, NULL) >= 0)
                 /* Save the user from accidentally registering either user-$SESSION.scope or user@.service.
                  * The latter is not technically a user session, but we don't need to labour the point. */
@@ -1334,6 +1337,12 @@ static int verify_arguments(void) {
         if (arg_userns_chown && arg_read_only)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--read-only and --private-users-chown may not be combined.");
 
+        /* We don't support --private-users-chown together with any of the volatile modes since we couldn't
+         * change the read-only part of the tree (i.e. /usr) anyway, or because it would trigger a massive
+         * copy-up (in case of overlay) making the entire excercise pointless. */
+        if (arg_userns_chown && arg_volatile_mode != VOLATILE_NO)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--volatile= and --private-users-chown may not be combined.");
+
         /* If --network-namespace-path is given with any other network-related option,
          * we need to error out, to avoid conflicts between different network options. */
         if (arg_network_namespace_path &&
@@ -1352,9 +1361,6 @@ static int verify_arguments(void) {
         if (arg_userns_mode != USER_NAMESPACE_NO && !(arg_mount_settings & MOUNT_APPLY_APIVFS_RO))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --private-users with read-write mounts.");
 
-        if (arg_volatile_mode != VOLATILE_NO && arg_read_only)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy.");
-
         if (arg_expose_ports && !arg_private_network)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --port= without private networking.");
 
@@ -1420,6 +1426,10 @@ static const char *timezone_from_path(const char *path) {
                         "/usr/share/zoneinfo/");
 }
 
+static bool etc_writable(void) {
+        return !arg_read_only || IN_SET(arg_volatile_mode, VOLATILE_YES, VOLATILE_OVERLAY);
+}
+
 static int setup_timezone(const char *dest) {
         _cleanup_free_ char *p = NULL, *etc = NULL;
         const char *where, *check;
@@ -1431,9 +1441,9 @@ static int setup_timezone(const char *dest) {
         if (IN_SET(arg_timezone, TIMEZONE_AUTO, TIMEZONE_SYMLINK)) {
                 r = readlink_malloc("/etc/localtime", &p);
                 if (r == -ENOENT && arg_timezone == TIMEZONE_AUTO)
-                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_OFF : TIMEZONE_DELETE;
+                        m = etc_writable() ? TIMEZONE_DELETE : TIMEZONE_OFF;
                 else if (r == -EINVAL && arg_timezone == TIMEZONE_AUTO) /* regular file? */
-                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_BIND : TIMEZONE_COPY;
+                        m = etc_writable() ? TIMEZONE_COPY : TIMEZONE_BIND;
                 else if (r < 0) {
                         log_warning_errno(r, "Failed to read host's /etc/localtime symlink, not updating container timezone: %m");
                         /* To handle warning, delete /etc/localtime and replace it with a symbolic link to a time zone data
@@ -1444,7 +1454,7 @@ static int setup_timezone(const char *dest) {
                          */
                         return 0;
                 } else if (arg_timezone == TIMEZONE_AUTO)
-                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_BIND : TIMEZONE_SYMLINK;
+                        m = etc_writable() ? TIMEZONE_SYMLINK : TIMEZONE_BIND;
                 else
                         m = arg_timezone;
         } else
@@ -1606,11 +1616,11 @@ static int setup_resolv_conf(const char *dest) {
                 if (arg_private_network)
                         m = RESOLV_CONF_OFF;
                 else if (have_resolv_conf(STATIC_RESOLV_CONF) > 0 && resolved_listening() > 0)
-                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_BIND_STATIC : RESOLV_CONF_COPY_STATIC;
+                        m = etc_writable() ? RESOLV_CONF_COPY_STATIC : RESOLV_CONF_BIND_STATIC;
                 else if (have_resolv_conf("/etc/resolv.conf") > 0)
-                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_BIND_HOST : RESOLV_CONF_COPY_HOST;
+                        m = etc_writable() ? RESOLV_CONF_COPY_HOST : RESOLV_CONF_BIND_HOST;
                 else
-                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_OFF : RESOLV_CONF_DELETE;
+                        m = etc_writable() ? RESOLV_CONF_DELETE : RESOLV_CONF_OFF;
         } else
                 m = arg_resolv_conf;
 
@@ -2896,6 +2906,30 @@ static int outer_child(
                          "Selected user namespace base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range);
         }
 
+        if (!dissected_image) {
+                /* Turn directory into bind mount */
+                r = mount_verbose(LOG_ERR, directory, directory, NULL, MS_BIND|MS_REC, NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        r = setup_pivot_root(
+                        directory,
+                        arg_pivot_root_new,
+                        arg_pivot_root_old);
+        if (r < 0)
+                return r;
+
+        r = setup_volatile_mode(
+                        directory,
+                        arg_volatile_mode,
+                        arg_userns_mode != USER_NAMESPACE_NO,
+                        arg_uid_shift,
+                        arg_uid_range,
+                        arg_selinux_context);
+        if (r < 0)
+                return r;
+
         if (dissected_image) {
                 /* Now we know the uid shift, let's now mount everything else that might be in the image. */
                 r = dissected_image_mount(dissected_image, directory, arg_uid_shift,
@@ -2921,38 +2955,6 @@ static int outer_child(
                 unified_cgroup_hierarchy_socket = safe_close(unified_cgroup_hierarchy_socket);
         }
 
-        /* Turn directory into bind mount */
-        r = mount_verbose(LOG_ERR, directory, directory, NULL, MS_BIND|MS_REC, NULL);
-        if (r < 0)
-                return r;
-
-        r = setup_pivot_root(
-                        directory,
-                        arg_pivot_root_new,
-                        arg_pivot_root_old);
-        if (r < 0)
-                return r;
-
-        r = setup_volatile(
-                        directory,
-                        arg_volatile_mode,
-                        arg_userns_mode != USER_NAMESPACE_NO,
-                        arg_uid_shift,
-                        arg_uid_range,
-                        arg_selinux_context);
-        if (r < 0)
-                return r;
-
-        r = setup_volatile_state(
-                        directory,
-                        arg_volatile_mode,
-                        arg_userns_mode != USER_NAMESPACE_NO,
-                        arg_uid_shift,
-                        arg_uid_range,
-                        arg_selinux_context);
-        if (r < 0)
-                return r;
-
         /* Mark everything as shared so our mounts get propagated down. This is
          * required to make new bind mounts available in systemd services
          * inside the containter that create a new mount namespace.
@@ -2971,7 +2973,7 @@ static int outer_child(
         if (r < 0)
                 return r;
 
-        if (arg_read_only) {
+        if (arg_read_only && arg_volatile_mode == VOLATILE_NO) {
                 r = bind_remount_recursive(directory, true, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to make tree read-only: %m");
@@ -4398,7 +4400,7 @@ int main(int argc, char *argv[]) {
                                 goto finish;
                         }
 
-                        r = copy_file(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600, FS_NOCOW_FL, COPY_REFLINK);
+                        r = copy_file(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600, FS_NOCOW_FL, COPY_REFLINK|COPY_CRTIME);
                         if (r < 0) {
                                 r = log_error_errno(r, "Failed to copy image file: %m");
                                 goto finish;
index 7e276f1edf58001755cf93cb09d9c969e50b947a..53ab042404674a208dac87b21a134055cc751ff0 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdio.h>
 #include <linux/magic.h>
 
+#include "sd-device.h"
 #include "sd-id128.h"
 
 #include "alloc-util.h"
 #include "conf-files.h"
 #include "def.h"
 #include "device-nodes.h"
+#include "dirent-util.h"
 #include "efivars.h"
+#include "env-file.h"
 #include "env-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "pe-header.h"
 #include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "unaligned.h"
 #include "virt.h"
 
 static void boot_entry_free(BootEntry *entry) {
@@ -27,6 +32,7 @@ static void boot_entry_free(BootEntry *entry) {
 
         free(entry->id);
         free(entry->path);
+        free(entry->root);
         free(entry->title);
         free(entry->show_title);
         free(entry->version);
@@ -39,21 +45,24 @@ static void boot_entry_free(BootEntry *entry) {
         free(entry->device_tree);
 }
 
-static int boot_entry_load(const char *path, BootEntry *entry) {
+static int boot_entry_load(
+                const char *root,
+                const char *path,
+                BootEntry *entry) {
+
         _cleanup_(boot_entry_free) BootEntry tmp = {};
         _cleanup_fclose_ FILE *f = NULL;
         unsigned line = 1;
         char *b, *c;
         int r;
 
+        assert(root);
         assert(path);
         assert(entry);
 
         c = endswith_no_case(path, ".conf");
-        if (!c) {
-                log_error("Invalid loader entry filename: %s", path);
-                return -EINVAL;
-        }
+        if (!c)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
 
         b = basename(path);
         tmp.id = strndup(b, c - b);
@@ -64,6 +73,10 @@ static int boot_entry_load(const char *path, BootEntry *entry) {
         if (!tmp.path)
                 return log_oom();
 
+        tmp.root = strdup(root);
+        if (!tmp.root)
+                return log_oom();
+
         f = fopen(path, "re");
         if (!f)
                 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
@@ -115,7 +128,7 @@ static int boot_entry_load(const char *path, BootEntry *entry) {
                 else if (streq(field, "devicetree"))
                         r = free_and_strdup(&tmp.device_tree, p);
                 else {
-                        log_notice("%s:%u: Unknown line \"%s\"", path, line, field);
+                        log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
                         continue;
                 }
                 if (r < 0)
@@ -203,7 +216,7 @@ static int boot_loader_read_conf(const char *path, BootConfig *config) {
                 else if (streq(field, "console-mode"))
                         r = free_and_strdup(&config->console_mode, p);
                 else {
-                        log_notice("%s:%u: Unknown line \"%s\"", path, line, field);
+                        log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
                         continue;
                 }
                 if (r < 0)
@@ -217,36 +230,270 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
         return str_verscmp(a->id, b->id);
 }
 
-static int boot_entries_find(const char *dir, BootEntry **ret_entries, size_t *ret_n_entries) {
+static int boot_entries_find(
+                const char *root,
+                const char *dir,
+                BootEntry **entries,
+                size_t *n_entries) {
+
         _cleanup_strv_free_ char **files = NULL;
+        size_t n_allocated = *n_entries;
         char **f;
         int r;
-        BootEntry *array = NULL;
-        size_t n_allocated = 0, n = 0;
 
+        assert(root);
         assert(dir);
-        assert(ret_entries);
-        assert(ret_n_entries);
+        assert(entries);
+        assert(n_entries);
 
         r = conf_files_list(&files, ".conf", NULL, 0, dir, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
 
         STRV_FOREACH(f, files) {
-                if (!GREEDY_REALLOC0(array, n_allocated, n + 1))
+                if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1))
                         return log_oom();
 
-                r = boot_entry_load(*f, array + n);
+                r = boot_entry_load(root, *f, *entries + *n_entries);
                 if (r < 0)
                         continue;
 
-                n++;
+                (*n_entries) ++;
         }
 
-        typesafe_qsort(array, n, boot_entry_compare);
+        return 0;
+}
+
+static int boot_entry_load_unified(
+                const char *root,
+                const char *path,
+                const char *osrelease,
+                const char *cmdline,
+                BootEntry *ret) {
 
-        *ret_entries = array;
-        *ret_n_entries = n;
+        _cleanup_free_ char *os_pretty_name = NULL, *os_id = NULL, *version_id = NULL, *build_id = NULL;
+        _cleanup_(boot_entry_free) BootEntry tmp = {};
+        _cleanup_fclose_ FILE *f = NULL;
+        const char *k;
+        int r;
+
+        assert(root);
+        assert(path);
+        assert(osrelease);
+
+        k = path_startswith(path, root);
+        if (!k)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
+
+        f = fmemopen((void*) osrelease, strlen(osrelease), "r");
+        if (!f)
+                return log_error_errno(errno, "Failed to open os-release buffer: %m");
+
+        r = parse_env_file(f, "os-release",
+                           "PRETTY_NAME", &os_pretty_name,
+                           "ID", &os_id,
+                           "VERSION_ID", &version_id,
+                           "BUILD_ID", &build_id);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
+
+        if (!os_pretty_name || !os_id || !(version_id || build_id))
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
+
+        tmp.id = strjoin(os_id, "-", version_id ?: build_id);
+        if (!tmp.id)
+                return log_oom();
+
+        tmp.path = strdup(path);
+        if (!tmp.path)
+                return log_oom();
+
+        tmp.root = strdup(root);
+        if (!tmp.root)
+                return log_oom();
+
+        tmp.kernel = strdup(skip_leading_chars(k, "/"));
+        if (!tmp.kernel)
+                return log_oom();
+
+        tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE));
+        if (!tmp.options)
+                return log_oom();
+
+        delete_trailing_chars(tmp.options[0], WHITESPACE);
+
+        tmp.title = TAKE_PTR(os_pretty_name);
+
+        *ret = tmp;
+        tmp = (BootEntry) {};
+        return 0;
+}
+
+/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
+ * the ones we do care about and we are willing to load into memory have this size limit.) */
+#define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
+
+static int find_sections(
+                int fd,
+                char **ret_osrelease,
+                char **ret_cmdline) {
+
+        _cleanup_free_ struct PeSectionHeader *sections = NULL;
+        _cleanup_free_ char *osrelease = NULL, *cmdline = NULL;
+        size_t i, n_sections;
+        struct DosFileHeader dos;
+        struct PeHeader pe;
+        uint64_t start;
+        ssize_t n;
+
+        n = pread(fd, &dos, sizeof(dos), 0);
+        if (n < 0)
+                return log_error_errno(errno, "Failed read DOS header: %m");
+        if (n != sizeof(dos))
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading DOS header, refusing.");
+
+        if (dos.Magic[0] != 'M' || dos.Magic[1] != 'Z')
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DOS executable magic missing, refusing.");
+
+        start = unaligned_read_le32(&dos.ExeHeader);
+        n = pread(fd, &pe, sizeof(pe), start);
+        if (n < 0)
+                return log_error_errno(errno, "Failed to read PE header: %m");
+        if (n != sizeof(pe))
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading PE header, refusing.");
+
+        if (pe.Magic[0] != 'P' || pe.Magic[1] != 'E' || pe.Magic[2] != 0 || pe.Magic[3] != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE executable magic missing, refusing.");
+
+        n_sections = unaligned_read_le16(&pe.FileHeader.NumberOfSections);
+        if (n_sections > 96)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE header has too many sections, refusing.");
+
+        sections = new(struct PeSectionHeader, n_sections);
+        if (!sections)
+                return log_oom();
+
+        n = pread(fd, sections,
+                  n_sections * sizeof(struct PeSectionHeader),
+                  start + sizeof(pe) + unaligned_read_le16(&pe.FileHeader.SizeOfOptionalHeader));
+        if (n < 0)
+                return log_error_errno(errno, "Failed to read section data: %m");
+        if ((size_t) n != n_sections * sizeof(struct PeSectionHeader))
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading sections, refusing.");
+
+        for (i = 0; i < n_sections; i++) {
+                _cleanup_free_ char *k = NULL;
+                uint32_t offset, size;
+                char **b;
+
+                if (strneq((char*) sections[i].Name, ".osrel", sizeof(sections[i].Name)))
+                        b = &osrelease;
+                else if (strneq((char*) sections[i].Name, ".cmdline", sizeof(sections[i].Name)))
+                        b = &cmdline;
+                else
+                        continue;
+
+                if (*b)
+                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section %s, refusing.", sections[i].Name);
+
+                offset = unaligned_read_le32(&sections[i].PointerToRawData);
+                size = unaligned_read_le32(&sections[i].VirtualSize);
+
+                if (size > PE_SECTION_SIZE_MAX)
+                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section %s too large, refusing.", sections[i].Name);
+
+                k = new(char, size+1);
+                if (!k)
+                        return log_oom();
+
+                n = pread(fd, k, size, offset);
+                if (n < 0)
+                        return log_error_errno(errno, "Failed to read section payload: %m");
+                if (n != size)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading section payload, refusing:");
+
+                /* Allow one trailing NUL byte, but nothing more. */
+                if (size > 0 && memchr(k, 0, size - 1))
+                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section contains embedded NUL byte: %m");
+
+                k[size] = 0;
+                *b = TAKE_PTR(k);
+        }
+
+        if (!osrelease)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Image lacks .osrel section, refusing.");
+
+        if (ret_osrelease)
+                *ret_osrelease = TAKE_PTR(osrelease);
+        if (ret_cmdline)
+                *ret_cmdline = TAKE_PTR(cmdline);
+
+        return 0;
+}
+
+static int boot_entries_find_unified(
+                const char *root,
+                const char *dir,
+                BootEntry **entries,
+                size_t *n_entries) {
+
+        _cleanup_(closedirp) DIR *d = NULL;
+        size_t n_allocated = *n_entries;
+        struct dirent *de;
+        int r;
+
+        assert(root);
+        assert(dir);
+        assert(entries);
+        assert(n_entries);
+
+        d = opendir(dir);
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
+
+                return log_error_errno(errno, "Failed to open %s: %m", dir);
+        }
+
+        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", dir)) {
+                _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL;
+                _cleanup_close_ int fd = -1;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                if (!endswith_no_case(de->d_name, ".efi"))
+                        continue;
+
+                if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1))
+                        return log_oom();
+
+                fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+                if (fd < 0) {
+                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", dir, de->d_name);
+                        continue;
+                }
+
+                r = fd_verify_regular(fd);
+                if (r < 0) {
+                        log_warning_errno(errno, "File %s/%s is not regular, ignoring: %m", dir, de->d_name);
+                        continue;
+                }
+
+                r = find_sections(fd, &osrelease, &cmdline);
+                if (r < 0)
+                        continue;
+
+                j = path_join(dir, de->d_name);
+                if (!j)
+                        return log_oom();
+
+                r = boot_entry_load_unified(root, j, osrelease, cmdline, *entries + *n_entries);
+                if (r < 0)
+                        continue;
+
+                (*n_entries) ++;
+        }
 
         return 0;
 }
@@ -358,22 +605,46 @@ static int boot_entries_select_default(const BootConfig *config) {
         return config->n_entries - 1; /* -1 means "no default" */
 }
 
-int boot_entries_load_config(const char *esp_path, BootConfig *config) {
+int boot_entries_load_config(
+                const char *esp_path,
+                const char *xbootldr_path,
+                BootConfig *config) {
+
         const char *p;
         int r;
 
-        assert(esp_path);
         assert(config);
 
-        p = strjoina(esp_path, "/loader/loader.conf");
-        r = boot_loader_read_conf(p, config);
-        if (r < 0)
-                return r;
+        if (esp_path) {
+                p = strjoina(esp_path, "/loader/loader.conf");
+                r = boot_loader_read_conf(p, config);
+                if (r < 0)
+                        return r;
 
-        p = strjoina(esp_path, "/loader/entries");
-        r = boot_entries_find(p, &config->entries, &config->n_entries);
-        if (r < 0)
-                return r;
+                p = strjoina(esp_path, "/loader/entries");
+                r = boot_entries_find(esp_path, p, &config->entries, &config->n_entries);
+                if (r < 0)
+                        return r;
+
+                p = strjoina(esp_path, "/EFI/Linux/");
+                r = boot_entries_find_unified(esp_path, p, &config->entries, &config->n_entries);
+                if (r < 0)
+                        return r;
+        }
+
+        if (xbootldr_path) {
+                p = strjoina(xbootldr_path, "/loader/entries");
+                r = boot_entries_find(xbootldr_path, p, &config->entries, &config->n_entries);
+                if (r < 0)
+                        return r;
+
+                p = strjoina(xbootldr_path, "/EFI/Linux/");
+                r = boot_entries_find_unified(xbootldr_path, p, &config->entries, &config->n_entries);
+                if (r < 0)
+                        return r;
+        }
+
+        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
 
         r = boot_entries_uniquify(config->entries, config->n_entries);
         if (r < 0)
@@ -395,87 +666,32 @@ int boot_entries_load_config(const char *esp_path, BootConfig *config) {
 
 /********************************************************************************/
 
-static int verify_esp(
-                const char *p,
+static int verify_esp_blkid(
+                dev_t devid,
                 bool searching,
-                bool unprivileged_mode,
                 uint32_t *ret_part,
                 uint64_t *ret_pstart,
                 uint64_t *ret_psize,
                 sd_id128_t *ret_uuid) {
+
+        sd_id128_t uuid = SD_ID128_NULL;
+        uint64_t pstart = 0, psize = 0;
+        uint32_t part = 0;
+
 #if HAVE_BLKID
         _cleanup_(blkid_free_probep) blkid_probe b = NULL;
         _cleanup_free_ char *node = NULL;
         const char *v;
-#endif
-        uint64_t pstart = 0, psize = 0;
-        struct stat st, st2;
-        const char *t2;
-        struct statfs sfs;
-        sd_id128_t uuid = SD_ID128_NULL;
-        uint32_t part = 0;
-        bool relax_checks;
         int r;
 
-        assert(p);
-
-        relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
-
-        /* Non-root user can only check the status, so if an error occured in the following, it does not cause any
-         * issues. Let's also, silence the error messages. */
-
-        if (!relax_checks) {
-                if (statfs(p, &sfs) < 0) {
-                        /* If we are searching for the mount point, don't generate a log message if we can't find the path */
-                        if (errno == ENOENT && searching)
-                                return -ENOENT;
-
-                        return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
-                                              "Failed to check file system type of \"%s\": %m", p);
-                }
-
-                if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) {
-                        if (searching)
-                                return -EADDRNOTAVAIL;
-
-                        log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
-                        return -ENODEV;
-                }
-        }
-
-        if (stat(p, &st) < 0)
-                return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
-                                      "Failed to determine block device node of \"%s\": %m", p);
-
-        if (major(st.st_dev) == 0) {
-                log_error("Block device node of %p is invalid.", p);
-                return -ENODEV;
-        }
-
-        t2 = strjoina(p, "/..");
-        r = stat(t2, &st2);
-        if (r < 0)
-                return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
-                                      "Failed to determine block device node of parent of \"%s\": %m", p);
-
-        if (st.st_dev == st2.st_dev) {
-                log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p);
-                return -ENODEV;
-        }
-
-        /* In a container we don't have access to block devices, skip this part of the verification, we trust the
-         * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */
-        if (detect_container() > 0 || unprivileged_mode || relax_checks)
-                goto finish;
-
-#if HAVE_BLKID
-        r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node);
+        r = device_path_make_major_minor(S_IFBLK, devid, &node);
         if (r < 0)
                 return log_error_errno(r, "Failed to format major/minor device path: %m");
+
         errno = 0;
         b = blkid_new_probe_from_filename(node);
         if (!b)
-                return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p);
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
 
         blkid_probe_enable_superblocks(b, 1);
         blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
@@ -484,56 +700,52 @@ static int verify_esp(
 
         errno = 0;
         r = blkid_do_safeprobe(b);
-        if (r == -2) {
-                log_error("File system \"%s\" is ambiguous.", p);
-                return -ENODEV;
-        } else if (r == 1) {
-                log_error("File system \"%s\" does not contain a label.", p);
-                return -ENODEV;
-        } else if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe file system \"%s\": %m", p);
+        if (r == -2)
+                return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
+        else if (r == 1)
+                return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
+        else if (r != 0)
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
 
         errno = 0;
         r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
         if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe file system type \"%s\": %m", p);
-        if (!streq(v, "vfat")) {
-                log_error("File system \"%s\" is not FAT.", p);
-                return -ENODEV;
-        }
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system type of \"%s\": %m", node);
+        if (!streq(v, "vfat"))
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                      "File system \"%s\" is not FAT.", node);
 
         errno = 0;
         r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
         if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe partition scheme \"%s\": %m", p);
-        if (!streq(v, "gpt")) {
-                log_error("File system \"%s\" is not on a GPT partition table.", p);
-                return -ENODEV;
-        }
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node);
+        if (!streq(v, "gpt"))
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                      "File system \"%s\" is not on a GPT partition table.", node);
 
         errno = 0;
         r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
         if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p);
-        if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) {
-                log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p);
-                return -ENODEV;
-        }
+                return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID of \"%s\": %m", node);
+        if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"))
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                       SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                       "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
 
         errno = 0;
         r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
         if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe partition entry UUID \"%s\": %m", p);
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
         r = sd_id128_from_string(v, &uuid);
-        if (r < 0) {
-                log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v);
-                return -EIO;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
 
         errno = 0;
         r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
         if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe partition number \"%s\": m", p);
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition number of \"%s\": m", node);
         r = safe_atou32(v, &part);
         if (r < 0)
                 return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
@@ -541,7 +753,7 @@ static int verify_esp(
         errno = 0;
         r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
         if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe partition offset \"%s\": %m", p);
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition offset of \"%s\": %m", node);
         r = safe_atou64(v, &pstart);
         if (r < 0)
                 return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
@@ -549,13 +761,12 @@ static int verify_esp(
         errno = 0;
         r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
         if (r != 0)
-                return log_error_errno(errno ?: EIO, "Failed to probe partition size \"%s\": %m", p);
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition size of \"%s\": %m", node);
         r = safe_atou64(v, &psize);
         if (r < 0)
                 return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
 #endif
 
-finish:
         if (ret_part)
                 *ret_part = part;
         if (ret_pstart)
@@ -568,6 +779,234 @@ finish:
         return 0;
 }
 
+static int verify_esp_udev(
+                dev_t devid,
+                bool searching,
+                uint32_t *ret_part,
+                uint64_t *ret_pstart,
+                uint64_t *ret_psize,
+                sd_id128_t *ret_uuid) {
+
+        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+        _cleanup_free_ char *node = NULL;
+        sd_id128_t uuid = SD_ID128_NULL;
+        uint64_t pstart = 0, psize = 0;
+        uint32_t part = 0;
+        const char *v;
+        int r;
+
+        r = device_path_make_major_minor(S_IFBLK, devid, &node);
+        if (r < 0)
+                return log_error_errno(r, "Failed to format major/minor device path: %m");
+
+        r = sd_device_new_from_devnum(&d, 'b', devid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device from device number: %m");
+
+        r = sd_device_get_property_value(d, "ID_FS_TYPE", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+        if (!streq(v, "vfat"))
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                      "File system \"%s\" is not FAT.", node );
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+        if (!streq(v, "gpt"))
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                      "File system \"%s\" is not on a GPT partition table.", node);
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+        if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"))
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                       SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                       "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+        r = sd_id128_from_string(v, &uuid);
+        if (r < 0)
+                return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_NUMBER", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+        r = safe_atou32(v, &part);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_OFFSET", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+        r = safe_atou64(v, &pstart);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_SIZE", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+        r = safe_atou64(v, &psize);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
+
+        if (ret_part)
+                *ret_part = part;
+        if (ret_pstart)
+                *ret_pstart = pstart;
+        if (ret_psize)
+                *ret_psize = psize;
+        if (ret_uuid)
+                *ret_uuid = uuid;
+
+        return 0;
+}
+
+static int verify_fsroot_dir(
+                const char *path,
+                bool searching,
+                bool unprivileged_mode,
+                dev_t *ret_dev) {
+
+        struct stat st, st2;
+        const char *t2, *trigger;
+        int r;
+
+        assert(path);
+        assert(ret_dev);
+
+        /* So, the ESP and XBOOTLDR partition are commonly located on an autofs mount. stat() on the
+         * directory won't trigger it, if it is not mounted yet. Let's hence explicitly trigger it here,
+         * before stat()ing */
+        trigger = strjoina(path, "/trigger"); /* Filename doesn't matter... */
+        (void) access(trigger, F_OK);
+
+        if (stat(path, &st) < 0)
+                return log_full_errno((searching && errno == ENOENT) ||
+                                      (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
+                                      "Failed to determine block device node of \"%s\": %m", path);
+
+        if (major(st.st_dev) == 0)
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                      "Block device node of \"%s\" is invalid.", path);
+
+        t2 = strjoina(path, "/..");
+        if (stat(t2, &st2) < 0) {
+                if (errno != EACCES)
+                        r = -errno;
+                else {
+                        _cleanup_free_ char *parent = NULL;
+
+                        /* If going via ".." didn't work due to EACCESS, then let's determine the parent path
+                         * directly instead. It's not as good, due to symlinks and such, but we can't do
+                         * anything better here. */
+
+                        parent = dirname_malloc(path);
+                        if (!parent)
+                                return log_oom();
+
+                        if (stat(parent, &st2) < 0)
+                                r = -errno;
+                        else
+                                r = 0;
+                }
+
+                if (r < 0)
+                        return log_full_errno(unprivileged_mode && r == -EACCES ? LOG_DEBUG : LOG_ERR, r,
+                                              "Failed to determine block device node of parent of \"%s\": %m", path);
+        }
+
+        if (st.st_dev == st2.st_dev)
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                      "Directory \"%s\" is not the root of the file system.", path);
+
+        if (ret_dev)
+                *ret_dev = st.st_dev;
+
+        return 0;
+}
+
+static int verify_esp(
+                const char *p,
+                bool searching,
+                bool unprivileged_mode,
+                uint32_t *ret_part,
+                uint64_t *ret_pstart,
+                uint64_t *ret_psize,
+                sd_id128_t *ret_uuid) {
+
+        bool relax_checks;
+        dev_t devid;
+        int r;
+
+        assert(p);
+
+        /* This logs about all errors, except:
+         *
+         *  -ENOENT        â†’ if 'searching' is set, and the dir doesn't exist
+         *  -EADDRNOTAVAIL â†’ if 'searching' is set, and the dir doesn't look like an ESP
+         *  -EACESS        â†’ if 'unprivileged_mode' is set, and we have trouble acessing the thing
+         */
+
+        relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
+
+        /* Non-root user can only check the status, so if an error occured in the following, it does not cause any
+         * issues. Let's also, silence the error messages. */
+
+        if (!relax_checks) {
+                struct statfs sfs;
+
+                if (statfs(p, &sfs) < 0)
+                        /* If we are searching for the mount point, don't generate a log message if we can't find the path */
+                        return log_full_errno((searching && errno == ENOENT) ||
+                                              (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
+                                              "Failed to check file system type of \"%s\": %m", p);
+
+                if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC))
+                        return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                              SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+                                              "File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
+        }
+
+        r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
+        if (r < 0)
+                return r;
+
+        /* In a container we don't have access to block devices, skip this part of the verification, we trust
+         * the container manager set everything up correctly on its own. */
+        if (detect_container() > 0 || relax_checks)
+                goto finish;
+
+        /* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we
+         * use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an
+         * emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell),
+         * however blkid can't work if we have no privileges to access block devices directly, which is why
+         * we use udev in that case. */
+        if (unprivileged_mode)
+                return verify_esp_udev(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
+        else
+                return verify_esp_blkid(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
+
+finish:
+        if (ret_part)
+                *ret_part = 0;
+        if (ret_pstart)
+                *ret_pstart = 0;
+        if (ret_psize)
+                *ret_psize = 0;
+        if (ret_uuid)
+                *ret_uuid = SD_ID128_NULL;
+
+        return 0;
+}
+
 int find_esp_and_warn(
                 const char *path,
                 bool unprivileged_mode,
@@ -631,35 +1070,260 @@ found:
         return 0;
 }
 
+static int verify_xbootldr_blkid(
+                dev_t devid,
+                bool searching,
+                sd_id128_t *ret_uuid) {
+
+        sd_id128_t uuid = SD_ID128_NULL;
+
+#if HAVE_BLKID
+        _cleanup_(blkid_free_probep) blkid_probe b = NULL;
+        _cleanup_free_ char *node = NULL;
+        const char *v;
+        int r;
+
+        r = device_path_make_major_minor(S_IFBLK, devid, &node);
+        if (r < 0)
+                return log_error_errno(r, "Failed to format major/minor device path: %m");
+        errno = 0;
+        b = blkid_new_probe_from_filename(node);
+        if (!b)
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
+
+        blkid_probe_enable_partitions(b, 1);
+        blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
+
+        errno = 0;
+        r = blkid_do_safeprobe(b);
+        if (r == -2)
+                return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
+        else if (r == 1)
+                return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
+        else if (r != 0)
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
+
+        errno = 0;
+        r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
+        if (r != 0)
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node);
+        if (streq(v, "gpt")) {
+
+                errno = 0;
+                r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
+                if (r != 0)
+                        return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
+                if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172"))
+                        return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                              searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
+                                              "File system \"%s\" has wrong type for extended boot loader partition.", node);
+
+                errno = 0;
+                r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
+                if (r != 0)
+                        return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
+                r = sd_id128_from_string(v, &uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
+
+        } else if (streq(v, "dos")) {
+
+                errno = 0;
+                r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
+                if (r != 0)
+                        return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
+                if (!streq(v, "0xea"))
+                        return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                              searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
+                                              "File system \"%s\" has wrong type for extended boot loader partition.", node);
+
+        } else
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
+                                      "File system \"%s\" is not on a GPT or DOS partition table.", node);
+#endif
+
+        if (ret_uuid)
+                *ret_uuid = uuid;
+
+        return 0;
+}
+
+static int verify_xbootldr_udev(
+                dev_t devid,
+                bool searching,
+                sd_id128_t *ret_uuid) {
+
+        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+        _cleanup_free_ char *node = NULL;
+        sd_id128_t uuid = SD_ID128_NULL;
+        const char *v;
+        int r;
+
+        r = device_path_make_major_minor(S_IFBLK, devid, &node);
+        if (r < 0)
+                return log_error_errno(r, "Failed to format major/minor device path: %m");
+
+        r = sd_device_new_from_devnum(&d, 'b', devid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device from device number: %m");
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device property: %m");
+
+        if (streq(v, "gpt")) {
+
+                r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get device property: %m");
+                if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172"))
+                        return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                              searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
+                                              "File system \"%s\" has wrong type for extended boot loader partition.", node);
+
+                r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get device property: %m");
+                r = sd_id128_from_string(v, &uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
+
+        } else if (streq(v, "dos")) {
+
+                r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get device property: %m");
+                if (!streq(v, "0xea"))
+                        return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                              searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
+                                              "File system \"%s\" has wrong type for extended boot loader partition.", node);
+        } else
+                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+                                      searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
+                                      "File system \"%s\" is not on a GPT or DOS partition table.", node);
+
+        if (ret_uuid)
+                *ret_uuid = uuid;
+
+        return 0;
+}
+
+static int verify_xbootldr(
+                const char *p,
+                bool searching,
+                bool unprivileged_mode,
+                sd_id128_t *ret_uuid) {
+
+        bool relax_checks;
+        dev_t devid;
+        int r;
+
+        assert(p);
+
+        relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0;
+
+        r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
+        if (r < 0)
+                return r;
+
+        if (detect_container() > 0 || relax_checks)
+                goto finish;
+
+        if (unprivileged_mode)
+                return verify_xbootldr_udev(devid, searching, ret_uuid);
+        else
+                return verify_xbootldr_blkid(devid, searching, ret_uuid);
+
+finish:
+        if (ret_uuid)
+                *ret_uuid = SD_ID128_NULL;
+
+        return 0;
+}
+
+int find_xbootldr_and_warn(
+                const char *path,
+                bool unprivileged_mode,
+                char **ret_path,
+                sd_id128_t *ret_uuid) {
+
+        int r;
+
+        /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
+
+        if (path) {
+                r = verify_xbootldr(path, false, unprivileged_mode, ret_uuid);
+                if (r < 0)
+                        return r;
+
+                goto found;
+        }
+
+        path = getenv("SYSTEMD_XBOOTLDR_PATH");
+        if (path) {
+                if (!path_is_valid(path) || !path_is_absolute(path))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
+                                               path);
+
+                goto found;
+        }
+
+        r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid);
+        if (r >= 0) {
+                path = "/boot";
+                goto found;
+        }
+        if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
+                return r;
+
+        return -ENOKEY;
+
+found:
+        if (ret_path) {
+                char *c;
+
+                c = strdup(path);
+                if (!c)
+                        return log_oom();
+
+                *ret_path = c;
+        }
+
+        return 0;
+}
+
 int find_default_boot_entry(
                 const char *esp_path,
-                char **esp_where,
+                const char *xbootldr_path,
                 BootConfig *config,
                 const BootEntry **e) {
 
-        _cleanup_free_ char *where = NULL;
+        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
         int r;
 
         assert(config);
         assert(e);
 
-        r = find_esp_and_warn(esp_path, false, &where, NULL, NULL, NULL, NULL);
+        r = find_esp_and_warn(esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
         if (r < 0)
                 return r;
 
-        r = boot_entries_load_config(where, config);
+        r = find_xbootldr_and_warn(xbootldr_path, false, &xbootldr_where, NULL);
+        if (r < 0 && r != -ENOKEY)
+                return r;
+
+        r = boot_entries_load_config(esp_where, xbootldr_where, config);
         if (r < 0)
-                return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m", where);
+                return log_error_errno(r, "Failed to load boot loader entries: %m");
 
         if (config->default_entry < 0)
                 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
-                                       "No entry suitable as default, refusing to guess.");
+                                       "No boot loader entry suitable as default, refusing to guess.");
 
         *e = &config->entries[config->default_entry];
-        log_debug("Found default boot entry in file \"%s\"", (*e)->path);
-
-        if (esp_where)
-                *esp_where = TAKE_PTR(where);
+        log_debug("Found default boot loader entry in file \"%s\"", (*e)->path);
 
         return 0;
 }
index ed576210fec55ff35788afb9a297d9462122320b..f1849025586c253b082049b3ad1ac43878effb4b 100644 (file)
@@ -10,7 +10,8 @@
 
 typedef struct BootEntry {
         char *id;       /* This is the file basename without extension */
-        char *path;     /* This is the full path to the file */
+        char *path;     /* This is the full path to the drop-in file */
+        char *root;     /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */
         char *title;
         char *show_title;
         char *version;
@@ -40,12 +41,13 @@ typedef struct BootConfig {
 } BootConfig;
 
 void boot_config_free(BootConfig *config);
-int boot_entries_load_config(const char *esp_path, BootConfig *config);
+int boot_entries_load_config(const char *esp_path, const char *xbootldr_path, BootConfig *config);
 
 static inline const char* boot_entry_title(const BootEntry *entry) {
         return entry->show_title ?: entry->title ?: entry->id;
 }
 
 int find_esp_and_warn(const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid);
+int find_xbootldr_and_warn(const char *path, bool unprivileged_mode, char **ret_path,sd_id128_t *ret_uuid);
 
-int find_default_boot_entry(const char *esp_path, char **esp_where, BootConfig *config, const BootEntry **e);
+int find_default_boot_entry(const char *esp_path, const char *xbootldr_path, BootConfig *config, const BootEntry **e);
index d3404870254d6fd1a96f7ce2479bd1f3c4acd7bb..201a515589923662e7e8b3803b013628d6f05744 100644 (file)
@@ -343,10 +343,8 @@ int dissect_image(
 
         errno = 0;
         r = blkid_do_safeprobe(b);
-        if (IN_SET(r, -2, 1)) {
-                log_debug("Failed to identify any partition table.");
-                return -ENOPKG;
-        }
+        if (IN_SET(r, -2, 1))
+                return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to identify any partition table.");
         if (r != 0)
                 return -errno ?: -EIO;
 
@@ -497,6 +495,14 @@ int dissect_image(
 
                                 designator = PARTITION_ESP;
                                 fstype = "vfat";
+
+                        } else if (sd_id128_equal(type_id, GPT_XBOOTLDR)) {
+
+                                if (pflags & GPT_FLAG_NO_AUTO)
+                                        continue;
+
+                                designator = PARTITION_XBOOTLDR;
+                                rw = !(pflags & GPT_FLAG_READ_ONLY);
                         }
 #ifdef GPT_ROOT_NATIVE
                         else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
@@ -612,21 +618,53 @@ int dissect_image(
 
                 } else if (is_mbr) {
 
-                        if (pflags != 0x80) /* Bootable flag */
-                                continue;
+                        switch (blkid_partition_get_type(pp)) {
 
-                        if (blkid_partition_get_type(pp) != 0x83) /* Linux partition */
-                                continue;
+                        case 0x83: /* Linux partition */
+
+                                if (pflags != 0x80) /* Bootable flag */
+                                        continue;
+
+                                if (generic_node)
+                                        multiple_generic = true;
+                                else {
+                                        generic_nr = nr;
+                                        generic_rw = true;
+                                        generic_node = strdup(node);
+                                        if (!generic_node)
+                                                return -ENOMEM;
+                                }
+
+                                break;
+
+                        case 0xEA: { /* Boot Loader Spec extended $BOOT partition */
+                                _cleanup_free_ char *n = NULL;
+                                sd_id128_t id = SD_ID128_NULL;
+                                const char *sid;
+
+                                /* First one wins */
+                                if (m->partitions[PARTITION_XBOOTLDR].found)
+                                        continue;
+
+                                sid = blkid_partition_get_uuid(pp);
+                                if (sid)
+                                        (void) sd_id128_from_string(sid, &id);
 
-                        if (generic_node)
-                                multiple_generic = true;
-                        else {
-                                generic_nr = nr;
-                                generic_rw = true;
-                                generic_node = strdup(node);
-                                if (!generic_node)
+                                n = strdup(node);
+                                if (!n)
                                         return -ENOMEM;
-                        }
+
+                                m->partitions[PARTITION_XBOOTLDR] = (DissectedPartition) {
+                                        .found = true,
+                                        .partno = nr,
+                                        .rw = true,
+                                        .architecture = _ARCHITECTURE_INVALID,
+                                        .node = TAKE_PTR(n),
+                                        .uuid = id,
+                                };
+
+                                break;
+                        }}
                 }
         }
 
@@ -819,11 +857,15 @@ static int mount_partition(
                         return -ENOMEM;
         }
 
-        return mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options);
+        r = mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options);
+        if (r < 0)
+                return r;
+
+        return 1;
 }
 
 int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags) {
-        int r;
+        int r, boot_mounted;
 
         assert(m);
         assert(where);
@@ -856,21 +898,26 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
         if (r < 0)
                 return r;
 
+        boot_mounted = mount_partition(m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, flags);
+        if (boot_mounted < 0)
+                return boot_mounted;
+
         if (m->partitions[PARTITION_ESP].found) {
-                const char *mp;
+                /* Mount the ESP to /efi if it exists. If it doesn't exist, use /boot instead, but only if it
+                 * exists and is empty, and we didn't already mount the XBOOTLDR partition into it. */
 
-                /* Mount the ESP to /efi if it exists and is empty. If it doesn't exist, use /boot instead. */
+                r = chase_symlinks("/efi", where, CHASE_PREFIX_ROOT, NULL);
+                if (r >= 0) {
+                        r = mount_partition(m->partitions + PARTITION_ESP, where, "/efi", uid_shift, flags);
+                        if (r < 0)
+                                return r;
 
-                FOREACH_STRING(mp, "/efi", "/boot") {
+                } else if (boot_mounted <= 0) {
                         _cleanup_free_ char *p = NULL;
 
-                        r = chase_symlinks(mp, where, CHASE_PREFIX_ROOT, &p);
-                        if (r < 0)
-                                continue;
-
-                        r = dir_is_empty(p);
-                        if (r > 0) {
-                                r = mount_partition(m->partitions + PARTITION_ESP, where, mp, uid_shift, flags);
+                        r = chase_symlinks("/boot", where, CHASE_PREFIX_ROOT, &p);
+                        if (r >= 0 && dir_is_empty(p) > 0) {
+                                r = mount_partition(m->partitions + PARTITION_ESP, where, "/boot", uid_shift, flags);
                                 if (r < 0)
                                         return r;
                         }
@@ -1499,6 +1546,7 @@ static const char *const partition_designator_table[] = {
         [PARTITION_HOME] = "home",
         [PARTITION_SRV] = "srv",
         [PARTITION_ESP] = "esp",
+        [PARTITION_XBOOTLDR] = "xbootldr",
         [PARTITION_SWAP] = "swap",
         [PARTITION_ROOT_VERITY] = "root-verity",
         [PARTITION_ROOT_SECONDARY_VERITY] = "root-secondary-verity",
index f50b40ea1154775380aec52a1bd68ffd454a2906..ded43f331a50839e93c3b245c3664566c9de07ca 100644 (file)
@@ -29,6 +29,7 @@ enum  {
         PARTITION_HOME,
         PARTITION_SRV,
         PARTITION_ESP,
+        PARTITION_XBOOTLDR,
         PARTITION_SWAP,
         PARTITION_ROOT_VERITY, /* verity data for the PARTITION_ROOT partition */
         PARTITION_ROOT_SECONDARY_VERITY, /* verity data for the PARTITION_ROOT_SECONDARY partition */
index fd953fabc735c568f6de20a9b45f477be2da8af5..31e01bd5a5cc9afd2cf87fce25c34de7f83b5528 100644 (file)
@@ -15,6 +15,7 @@
 #define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae)
 #define GPT_ROOT_IA64   SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97)
 #define GPT_ESP         SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b)
+#define GPT_XBOOTLDR    SD_ID128_MAKE(bc,13,c2,ff,59,e6,42,62,a3,52,b2,75,fd,6f,71,72)
 #define GPT_SWAP        SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f)
 #define GPT_HOME        SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15)
 #define GPT_SRV         SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
index 7ae1ffb1b2df9af10efd63e67e498b14a474d861..f46ea682042751d3d9f869400b69b74f4c093ac5 100644 (file)
@@ -3108,8 +3108,6 @@ finish:
 
         free(stack);
 
-        va_end(ap);
-
         return r;
 }
 
index af06ab22e88bbb6de10cd5cb72dd8f6ad433b14d..3d61221056aaa907948c0dcffa5d8135c993149e 100644 (file)
@@ -870,7 +870,7 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
         case IMAGE_RAW:
                 new_path = strjoina("/var/lib/machines/", new_name, ".raw");
 
-                r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, COPY_REFLINK);
+                r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, COPY_REFLINK|COPY_CRTIME);
                 break;
 
         case IMAGE_BLOCK:
index 99d6ba14f19c44847b9a01dbffee2c6375db8319..1a5ba8c0f4e276e4fcf2c9c1007291ef89d8271a 100644 (file)
@@ -117,6 +117,7 @@ shared_sources = files('''
         pager.h
         path-lookup.c
         path-lookup.h
+        pe-header.h
         pretty-print.c
         pretty-print.h
         ptyfwd.c
diff --git a/src/shared/pe-header.h b/src/shared/pe-header.h
new file mode 100644 (file)
index 0000000..a362917
--- /dev/null
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <inttypes.h>
+
+#include "macro.h"
+#include "sparse-endian.h"
+
+struct DosFileHeader {
+        uint8_t Magic[2];
+        le16_t LastSize;
+        le16_t nBlocks;
+        le16_t nReloc;
+        le16_t HdrSize;
+        le16_t MinAlloc;
+        le16_t MaxAlloc;
+        le16_t ss;
+        le16_t sp;
+        le16_t Checksum;
+        le16_t ip;
+        le16_t cs;
+        le16_t RelocPos;
+        le16_t nOverlay;
+        le16_t reserved[4];
+        le16_t OEMId;
+        le16_t OEMInfo;
+        le16_t reserved2[10];
+        le32_t ExeHeader;
+} _packed_;
+
+#define PE_HEADER_MACHINE_I386 0x014cU
+#define PE_HEADER_MACHINE_X64  0x8664U
+
+struct PeFileHeader {
+        le16_t Machine;
+        le16_t NumberOfSections;
+        le32_t TimeDateStamp;
+        le32_t PointerToSymbolTable;
+        le32_t NumberOfSymbols;
+        le16_t SizeOfOptionalHeader;
+        le16_t Characteristics;
+} _packed_;
+
+struct PeHeader {
+        uint8_t Magic[4];
+        struct PeFileHeader FileHeader;
+} _packed_;
+
+struct PeSectionHeader {
+        uint8_t Name[8];
+        le32_t VirtualSize;
+        le32_t VirtualAddress;
+        le32_t SizeOfRawData;
+        le32_t PointerToRawData;
+        le32_t PointerToRelocations;
+        le32_t PointerToLinenumbers;
+        le16_t NumberOfRelocations;
+        le16_t NumberOfLinenumbers;
+        le32_t Characteristics;
+ } _packed_;
index 4d75bc0e96e2eb0556f83e6bd93b0f0106439701..5ca6ab3376c573e24091ba89a8870ddd59468e75 100644 (file)
 
 int query_volatile_mode(VolatileMode *ret) {
         _cleanup_free_ char *mode = NULL;
-        VolatileMode m = VOLATILE_NO;
         int r;
 
         r = proc_cmdline_get_key("systemd.volatile", PROC_CMDLINE_VALUE_OPTIONAL, &mode);
         if (r < 0)
                 return r;
-        if (r == 0)
-                goto finish;
+        if (r == 0) {
+                *ret = VOLATILE_NO;
+                return 0;
+        }
 
         if (mode) {
+                VolatileMode m;
+
                 m = volatile_mode_from_string(mode);
                 if (m < 0)
                         return -EINVAL;
-        } else
-                m = VOLATILE_YES;
 
-        r = 1;
+                *ret = m;
+        } else
+                *ret = VOLATILE_YES;
 
-finish:
-        *ret = m;
-        return r;
+        return 1;
 }
 
 static const char* const volatile_mode_table[_VOLATILE_MODE_MAX] = {
         [VOLATILE_NO] = "no",
         [VOLATILE_YES] = "yes",
         [VOLATILE_STATE] = "state",
+        [VOLATILE_OVERLAY] = "overlay",
 };
 
 DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(volatile_mode, VolatileMode, VOLATILE_YES);
index 8761c44ab8553fe25c4d6509d618c84826515495..2d31bb11749d339a1b1d918663e5f4bf17c6ed08 100644 (file)
@@ -5,6 +5,7 @@ typedef enum VolatileMode {
         VOLATILE_NO,
         VOLATILE_YES,
         VOLATILE_STATE,
+        VOLATILE_OVERLAY,
         _VOLATILE_MODE_MAX,
         _VOLATILE_MODE_INVALID = -1
 } VolatileMode;
index 63dae2c87285667bc833206bacb83989b4d22fe1..57e3111b8d661ae662b34ab2c83d6a170e6c20dd 100644 (file)
@@ -134,7 +134,6 @@ static const char *arg_kill_who = NULL;
 static int arg_signal = SIGTERM;
 static char *arg_root = NULL;
 static usec_t arg_when = 0;
-static char *arg_esp_path = NULL;
 static char *argv_cmdline = NULL;
 static enum action {
         ACTION_SYSTEMCTL,
@@ -3519,7 +3518,7 @@ static int prepare_firmware_setup(void) {
 
 static int load_kexec_kernel(void) {
         _cleanup_(boot_config_free) BootConfig config = {};
-        _cleanup_free_ char *where = NULL, *kernel = NULL, *initrd = NULL, *options = NULL;
+        _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL;
         const BootEntry *e;
         pid_t pid;
         int r;
@@ -3532,21 +3531,27 @@ static int load_kexec_kernel(void) {
         if (access(KEXEC, X_OK) < 0)
                 return log_error_errno(errno, KEXEC" is not available: %m");
 
-        r = find_default_boot_entry(arg_esp_path, &where, &config, &e);
+        r = find_default_boot_entry(NULL, NULL, &config, &e);
         if (r == -ENOKEY) /* find_default_boot_entry() doesn't warn about this case */
                 return log_error_errno(r, "Cannot find the ESP partition mount point.");
         if (r < 0)
                 /* But it logs about all these cases, hence don't log here again */
                 return r;
 
-        if (strv_length(e->initrd) > 1) {
-                log_error("Boot entry specifies multiple initrds, which is not supported currently.");
-                return -EINVAL;
+        if (strv_length(e->initrd) > 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Boot entry specifies multiple initrds, which is not supported currently.");
+
+        kernel = path_join(e->root, e->kernel);
+        if (!kernel)
+                return log_oom();
+
+        if (!strv_isempty(e->initrd)) {
+                initrd = path_join(e->root, *e->initrd);
+                if (!initrd)
+                        return log_oom();
         }
 
-        kernel = path_join(where, e->kernel);
-        if (!strv_isempty(e->initrd))
-                initrd = path_join(where, *e->initrd);
         options = strv_join(e->options, " ");
         if (!options)
                 return log_oom();
@@ -8814,7 +8819,6 @@ finish:
 
         strv_free(arg_wall);
         free(arg_root);
-        free(arg_esp_path);
 
         /* Note that we return r here, not 0, so that we can implement the LSB-like return codes */
         return r;
index 86d8a30afa4b0b87e82b4f1e033f90d3ec2da959..c53b9653f95222bacdc9a025da5f99ccb85829e9 100644 (file)
@@ -867,6 +867,10 @@ tests += [
          [],
          [threads]],
 
+        [['src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c'],
+         [],
+         [threads]],
+
         [['src/libsystemd/sd-bus/test-bus-watch-bind.c'],
          [],
          [threads], '', 'timeout=120'],
index 1e010c08fb8c7c7e15c9508307659050c8895fbf..18ef56ac39176e86971672b11e004fa8eb32c24f 100644 (file)
@@ -59,15 +59,22 @@ static void test_long_lines(void) {
                             "asdfasdf %s asdfasdfa", "foobar");
 }
 
+static void test_log_syntax(void) {
+        assert_se(log_syntax("unit", LOG_ERR, "filename", 10, EINVAL, "EINVAL: %s: %m", "hogehoge") == -EINVAL);
+        assert_se(log_syntax("unit", LOG_ERR, "filename", 10, -ENOENT, "ENOENT: %s: %m", "hogehoge") == -ENOENT);
+        assert_se(log_syntax("unit", LOG_ERR, "filename", 10, SYNTHETIC_ERRNO(ENOTTY), "ENOTTY: %s: %m", "hogehoge") == -ENOTTY);
+}
+
 int main(int argc, char* argv[]) {
         int target;
 
-        for (target = 0; target <  _LOG_TARGET_MAX; target++) {
+        for (target = 0; target < _LOG_TARGET_MAX; target++) {
                 log_set_target(target);
                 log_open();
 
                 test_log_struct();
                 test_long_lines();
+                test_log_syntax();
         }
 
         assert_se(log_info_errno(SYNTHETIC_ERRNO(EUCLEAN), "foo") == -EUCLEAN);
index 5da9ce1681de676de9217ee73b0cf23ed806e39c..701f5a283260f2d69eedd5f9fd3a01b6d9ed860d 100644 (file)
@@ -3,6 +3,8 @@
 #include <sys/mount.h>
 
 #include "alloc-util.h"
+#include "blockdev-util.h"
+#include "escape.h"
 #include "fs-util.h"
 #include "main-func.h"
 #include "mkdir.h"
@@ -17,20 +19,7 @@ static int make_volatile(const char *path) {
         _cleanup_free_ char *old_usr = NULL;
         int r;
 
-        r = path_is_mount_point(path, NULL, AT_SYMLINK_FOLLOW);
-        if (r < 0)
-                return log_error_errno(r, "Couldn't determine whether %s is a mount point: %m", path);
-        if (r == 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "%s is not a mount point.", path);
-
-        r = path_is_temporary_fs(path);
-        if (r < 0)
-                return log_error_errno(r, "Couldn't determine whether %s is a temporary file system: %m", path);
-        if (r > 0) {
-                log_info("%s already is a temporary file system.", path);
-                return 0;
-        }
+        assert(path);
 
         r = chase_symlinks("/usr", path, CHASE_PREFIX_ROOT, &old_usr);
         if (r < 0)
@@ -45,7 +34,7 @@ static int make_volatile(const char *path) {
                 goto finish_rmdir;
 
         if (mkdir("/run/systemd/volatile-sysroot/usr", 0755) < 0) {
-                r = -errno;
+                r = log_error_errno(errno, "Failed to create /usr directory: %m");
                 goto finish_umount;
         }
 
@@ -54,8 +43,10 @@ static int make_volatile(const char *path) {
                 goto finish_umount;
 
         r = bind_remount_recursive("/run/systemd/volatile-sysroot/usr", true, NULL);
-        if (r < 0)
+        if (r < 0) {
+                log_error_errno(r, "Failed to remount /usr read-only: %m");
                 goto finish_umount;
+        }
 
         r = umount_recursive(path, 0);
         if (r < 0) {
@@ -64,7 +55,7 @@ static int make_volatile(const char *path) {
         }
 
         if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0)
-                log_warning_errno(errno, "Failed to remount %s MS_SLAVE|MS_REC: %m", path);
+                log_warning_errno(errno, "Failed to remount %s MS_SLAVE|MS_REC, ignoring: %m", path);
 
         r = mount_verbose(LOG_ERR, "/run/systemd/volatile-sysroot", path, NULL, MS_MOVE, NULL);
 
@@ -77,9 +68,55 @@ finish_rmdir:
         return r;
 }
 
+static int make_overlay(const char *path) {
+        _cleanup_free_ char *escaped_path = NULL;
+        bool tmpfs_mounted = false;
+        const char *options = NULL;
+        int r;
+
+        assert(path);
+
+        r = mkdir_p("/run/systemd/overlay-sysroot", 0700);
+        if (r < 0)
+                return log_error_errno(r, "Couldn't create overlay sysroot directory: %m");
+
+        r = mount_verbose(LOG_ERR, "tmpfs", "/run/systemd/overlay-sysroot", "tmpfs", MS_STRICTATIME, "mode=755");
+        if (r < 0)
+                goto finish;
+
+        tmpfs_mounted = true;
+
+        if (mkdir("/run/systemd/overlay-sysroot/upper", 0755) < 0) {
+                r = log_error_errno(errno, "Failed to create /run/systemd/overlay-sysroot/upper: %m");
+                goto finish;
+        }
+
+        if (mkdir("/run/systemd/overlay-sysroot/work", 0755) < 0) {
+                r = log_error_errno(errno, "Failed to create /run/systemd/overlay-sysroot/work: %m");
+                goto finish;
+        }
+
+        escaped_path = shell_escape(path, ",:");
+        if (!escaped_path) {
+                r = log_oom();
+                goto finish;
+        }
+
+        options = strjoina("lowerdir=", escaped_path, ",upperdir=/run/systemd/overlay-sysroot/upper,workdir=/run/systemd/overlay-sysroot/work");
+        r = mount_verbose(LOG_ERR, "overlay", path, "overlay", 0, options);
+
+finish:
+        if (tmpfs_mounted)
+                (void) umount_verbose("/run/systemd/overlay-sysroot");
+
+        (void) rmdir("/run/systemd/overlay-sysroot");
+        return r;
+}
+
 static int run(int argc, char *argv[]) {
         VolatileMode m = _VOLATILE_MODE_INVALID;
         const char *path;
+        dev_t devt;
         int r;
 
         log_setup_service();
@@ -94,10 +131,8 @@ static int run(int argc, char *argv[]) {
         if (r == 0 && argc >= 2) {
                 /* The kernel command line always wins. However if nothing was set there, the argument passed here wins instead. */
                 m = volatile_mode_from_string(argv[1]);
-                if (m < 0) {
-                        log_error("Couldn't parse volatile mode: %s", argv[1]);
-                        r = -EINVAL;
-                }
+                if (m < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Couldn't parse volatile mode: %s", argv[1]);
         }
 
         if (argc < 3)
@@ -116,10 +151,47 @@ static int run(int argc, char *argv[]) {
                                                "Directory cannot be the root directory.");
         }
 
-        if (m != VOLATILE_YES)
+        if (!IN_SET(m, VOLATILE_YES, VOLATILE_OVERLAY))
                 return 0;
 
-        return make_volatile(path);
+        r = path_is_mount_point(path, NULL, AT_SYMLINK_FOLLOW);
+        if (r < 0)
+                return log_error_errno(r, "Couldn't determine whether %s is a mount point: %m", path);
+        if (r == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a mount point.", path);
+
+        r = path_is_temporary_fs(path);
+        if (r < 0)
+                return log_error_errno(r, "Couldn't determine whether %s is a temporary file system: %m", path);
+        if (r > 0) {
+                log_info("%s already is a temporary file system.", path);
+                return 0;
+        }
+
+        /* We are about to replace the root directory with something else. Later code might want to know what we
+         * replaced here, hence let's save that information as a symlink we can later use. (This is particularly
+         * relevant for the overlayfs case where we'll fully obstruct the view onto the underlying device, hence
+         * querying the backing device node from the file system directly is no longer possible. */
+        r = get_block_device_harder(path, &devt);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine device major/minor of %s: %m", path);
+        else if (r > 0) {
+                _cleanup_free_ char *dn = NULL;
+
+                r = device_path_make_major_minor(S_IFBLK, devt, &dn);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to format device node path: %m");
+
+                if (symlink(dn, "/run/systemd/volatile-root") < 0)
+                        log_warning_errno(errno, "Failed to create symlink /run/systemd/volatile-root: %m");
+        }
+
+        if (m == VOLATILE_YES)
+                return make_volatile(path);
+        else {
+                assert(m == VOLATILE_OVERLAY);
+                return make_overlay(path);
+        }
 }
 
 DEFINE_MAIN_FUNCTION(run);