* 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
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
* 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.
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`.
<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>
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>
<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>
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>
<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,
<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>
<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>
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
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 ]
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
} 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;
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;
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);
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;
}
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);
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);
r = -errno;
}
- unlink(p);
+ (void) unlink(p);
return r;
}
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;
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);
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);
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) \
#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) {
"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"
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;
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;
}
}
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);
if (r < 0)
return r;
- r = acquire_esp();
+ r = acquire_path();
if (r < 0)
return r;
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);
if (r < 0)
return r;
- r = acquire_esp();
+ r = acquire_path();
if (r < 0)
return r;
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;
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:
#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,
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 */
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;
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;
}
}
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);
}
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;
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++;
}
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) {
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;
"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;
}
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);
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;
_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"))
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);
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;
}
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;
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);
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;
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);
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 },
{}
};
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:
}
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 */
} 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;
}
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;
}
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;
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;
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);
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;
}
/* 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"
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;
}
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;
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;
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, ©, &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,
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;
}
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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
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 = '''
boot.c
console.c
shim.c
+ crc32.c
'''.split()
stub_sources = '''
#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);
}
/* 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);
}
/* 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);
}
/* 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)
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;
EFI_STATUS err;
err = efivar_get(name, &val);
- if (!EFI_ERROR(err))
+ if (!EFI_ERROR(err) && i)
*i = Atoi(val);
return err;
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;
}
#define UINTN_MAX (~(UINTN)0)
#define INTN_MAX ((INTN)(UINTN_MAX>>1))
+
+#define TAKE_PTR(ptr) \
+ ({ \
+ typeof(ptr) _ptr_ = (ptr); \
+ (ptr) = NULL; \
+ _ptr_; \
+ })
}
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",
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size > 65536)
+ return 0;
+
/* This triggers client_receive_advertise */
fuzz_client(data, size, false);
--- /dev/null
+[libfuzzer]
+max_len = 65536
#include "efivars.h"
#include "fd-util.h"
#include "fileio.h"
+#include "fs-util.h"
#include "fstab-util.h"
#include "generator.h"
#include "gpt.h"
return generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, "wants", name);
}
-#if ENABLE_EFI
static int add_automount(
const char *id,
const char *what,
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);
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;
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",
120 * USEC_PER_SEC);
}
#else
-static int add_esp(DissectedPartition *p) {
+static int add_esp(DissectedPartition *p, bool has_xbootldr) {
return 0;
}
#endif
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;
_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;
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;
}
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.");
}
}
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);
}
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);
}
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);
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;
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);
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;
if (!m)
return -ENOMEM;
- m->n_ref = 1;
m->sealed = true;
m->header = header;
m->header_accessible = header_accessible;
m->creds.mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
}
+ m->n_ref = 1;
m->bus = sd_bus_ref(bus);
+
*ret = TAKE_PTR(m);
return 0;
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;
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;
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);
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;
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);
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);
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);
};
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;
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);
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;
}
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;
.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;
}
/* 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;
* 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;
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;
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;
}
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;
}
* 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;
}
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:
_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);
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;
}
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;
}
SD_BUS_ERROR_UNKNOWN_METHOD,
"Unknown method '%s' on interface '%s'.", m->member, m->interface);
}
-
if (r < 0)
return r;
return r;
*ret = TAKE_PTR(m);
-
return 1;
}
-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]",
--- /dev/null
+/* 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);
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"
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)
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)
return -EINVAL;
}
+ if (empty_or_root(destination))
+ return -EINVAL;
+
m = custom_mount_add(l, n, CUSTOM_MOUNT_OVERLAY);
if (!m)
return -ENOMEM;
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) {
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)
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) {
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");
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;
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;
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);
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. */
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 &&
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.");
"/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;
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
*/
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
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;
"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,
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.
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");
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;
#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) {
free(entry->id);
free(entry->path);
+ free(entry->root);
free(entry->title);
free(entry->show_title);
free(entry->version);
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);
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);
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)
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)
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(§ions[i].PointerToRawData);
+ size = unaligned_read_le32(§ions[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;
}
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)
/********************************************************************************/
-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);
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.");
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.");
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)
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,
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;
}
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;
} 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);
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;
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)) {
} 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;
+ }}
}
}
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);
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;
}
[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",
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 */
#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)
free(stack);
- va_end(ap);
-
return r;
}
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:
pager.h
path-lookup.c
path-lookup.h
+ pe-header.h
pretty-print.c
pretty-print.h
ptyfwd.c
--- /dev/null
+#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_;
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);
VOLATILE_NO,
VOLATILE_YES,
VOLATILE_STATE,
+ VOLATILE_OVERLAY,
_VOLATILE_MODE_MAX,
_VOLATILE_MODE_INVALID = -1
} VolatileMode;
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,
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;
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();
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;
[],
[threads]],
+ [['src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c'],
+ [],
+ [threads]],
+
[['src/libsystemd/sd-bus/test-bus-watch-bind.c'],
[],
[threads], '', 'timeout=120'],
"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);
#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"
_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)
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;
}
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) {
}
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);
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();
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)
"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);