Support booting from rootfs acquired via HTTP (#36314)

This extends systemd-import-generator to not only download a disk image
at boot, but also attach it to a loopback device, so that we can boot
from it.

We have most of the pieces already in place, this just polishes some
things, to make this round.

The topmost commit contains example command lines that just work to make
`systemd-vmspawn` boot from a `mkosi serve` call.

Note that this does not address how to get the UKI running on the target
system, this only deals with the later boot phase once the UKI is
already running.

This is WIP, because it lacks docs, and I want to do some more
polishing. But it works great.

Ultimate goal, provide a complete solution so that we also can do uefi
http boot for ukis
This commit is contained in:
Lennart Poettering
2025-02-21 11:26:14 +01:00
committed by GitHub
64 changed files with 1603 additions and 407 deletions
-61
View File
@@ -179,9 +179,6 @@ Features:
Net result: people who try to debug why their process gets killed should have
some minimal, nice metadata directly on the signal event.
* import-generator: add option to download into /run/ rather than /var/, and
make it default in the initrd
* sd-boot/sd-stub: install a uefi "handle" to a sidecar dir of bls type #1
entries with an "uki" or "uki-url" stanza, and make sd-stub look for
that. That way we can parameterize type #1 entries nicely.
@@ -212,19 +209,11 @@ Features:
own image loading code paths. Given that everything's statically linked
anyway on UEFI it should be easy to just jump into the already loaded image.
* introduce smbios type 11 variables for inserting additional entries into boot
loader spec type 1 menu list. Usecase is in particular with http boot: allow
VMMs to insert additional cheap payloads that are acquired on demand via
http. for example: always install diskomator or such.
* storagetm: maybe also serve the specified disk via HTTP? we have glue for
microhttpd anyway already. Idea would also be serve currently booted UKI as
separate HTTP resource, so that EFI http boot on another system could
directly boot from our system, with full access to the hdd.
* support boot-into-tarball in systemd-import-generator: optionally bind mount
downloaded image to /sysroot/
* support specifying download hash sum in systemd-import-generator expression
to pin image/tarball.
@@ -274,14 +263,6 @@ Features:
* systemd-cryptenroll: add --firstboot or so, that will interactively ask user
whether recovery key shall be enrolled and do so
* sd-boot: when looking for a BLS type #1 resource and it cannot be found in
the ESP check if ESP is backed by ramdisk/http and then request it from same
http base. Then: make mkosi build a 2nd esp maybe called the "netesp" that
contains bls type #1 entries pointing to the UKIs which would then be
requested via http this way. also set an efi var indicating that this
happened, so that initrd can recognize this and take into consdiration when
looking for root fs
* bootctl: add tool for registering BootXXX entry that boots from some http
server of your choice (i.e. like kernel-bootcfg --add-uri=)
@@ -1044,30 +1025,12 @@ Features:
into two of the three PAM stacks gdm provides.
See discussion at https://github.com/authselect/authselect/pull/311
* sd-boot: make boot loader spec type #1 accept http urls in "linux"
lines. Then, do the uefi http dance to download kernels and boot them. This
is then useful for network boot, by embedding a cpio with type #1 snippets
in sd-boot, which reference remote kernels.
* maybe prohibit setuid() to the nobody user, to lock things down, via seccomp.
the nobody is not a user any code should run under, ever, as that user would
possibly get a lot of access to resources it really shouldn't be getting
access to due to the userns + nfs semantics of the user. Alternatively: use
the seccomp log action, and allow it.
* sd-boot: add a new PE section .bls or so that carries a cpio with additional
boot loader entries (both type1 and type2). Then when initializing, find this
section, iterate through it and populate menu with it. cpio is simple enough
to make a parser for this reasonably robust. use same path structures as in
the ESP. Similar add one for signature key drop-ins.
* sd-boot: also allow passing in the cpio as in the previous item via SMBIOS
* add a new EFI tool "sd-fetch" or so. It looks in a PE section ".url" for an
URL, then downloads the file from it using UEFI HTTP APIs, and executes it.
Use case: provide a minimal ESP with sd-boot and a couple of these sd-fetch
binaries in place of UKIs, and download them on-the-fly.
* maybe: systemd-loop-generator that sets up loopback devices if requested via kernel
cmdline. use case: include encrypted/verity root fs in UKI.
@@ -1307,9 +1270,6 @@ Features:
* systemd-measure tool:
- pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11
* in sd-boot: load EFI drivers from a new PE section. That way, one can have a
"supercharged" sd-boot binary, that could carry ext4 drivers built-in.
* sd-device: add an API for acquiring list of child devices, given a device
objects (i.e. all child dirents that dirs or symlinks to dirs)
@@ -1503,27 +1463,6 @@ Features:
* sd-event: add 1st class event source for timezone changes
* support uefi/http boots with sd-boot: instead of looking for dropin files in
/loader/entries/ dir, look for a file /loader/entries/SHA256SUMS and use that
as directory manifest. The file would be a standard directory listing as
generated by GNU sha256sums.
* sd-boot: maybe add support for embedding the various auxiliary resources we
look for right in the sd-boot binary. i.e. take inspiration from sd-stub
logic: allow combining sd-boot via ukify with kernels to enumerate, .conf
files, drivers, keys to enroll and so on. Then, add whatever we find that way
to the menu. Use case: allow building a single PE image you can boot into via
UEFI HTTP boot.
* maybe add a new UEFI stub binary "sd-http". It works similar to sd-stub, but
all it does is download a file from a http server, and execute it, after
optionally checking its hash sum. idea would be: combine this "sd-http" stub
binary with some minimal info about a URL + hash sum, plus .osrel data, and
drop it into the unified kernel dir in the ESP. And bam you have something
that is tiny, feels a lot like a unified kernel, but all it does is chainload
the real kernel. benefit: downloading these stubs would be tiny and quick,
hence cheap for enumeration.
* sysext: measure all activated sysext into a TPM PCR
* systemd-dissect: show available versions inside of a disk image, i.e. if
+3
View File
@@ -97,6 +97,9 @@ variables. All EFI variables use the vendor UUID
generally only written once, by the OS installer, and is usually not touched
after that.
* The EFI variable `LoaderDeviceURL` contains the URL the boot loader was
downloaded from, in UTF-16 format. Only set in case of network boots.
If `LoaderTimeInitUSec` and `LoaderTimeExecUSec` are set, `systemd-analyze`
will include them in its boot-time analysis. If `LoaderDevicePartUUID` is set,
systemd will mount the ESP that was used for the boot to `/boot`, but only if
+1
View File
@@ -997,6 +997,7 @@ manpages = [
['systemd-keyutil', '1', [], ''],
['systemd-localed.service', '8', ['systemd-localed'], 'ENABLE_LOCALED'],
['systemd-logind.service', '8', ['systemd-logind'], 'ENABLE_LOGIND'],
['systemd-loop@.service', '8', [], ''],
['systemd-machine-id-commit.service', '8', [], ''],
['systemd-machine-id-setup', '1', [], ''],
['systemd-machined.service', '8', ['systemd-machined'], 'ENABLE_MACHINED'],
+10
View File
@@ -74,6 +74,16 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>io.systemd.boot-entries.extra:</varname><replaceable>ID=DEFINITION</replaceable></term>
<listitem><para>This allows inserting additional entries into the <command>systemd-boot</command>
menu. For details see
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry></para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
+32
View File
@@ -440,6 +440,16 @@
<xi:include href="version-info.xml" xpointer="v220"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderDeviceURL</varname></term>
<listitem><para>If the boot loader has been invoked via network booting this variable contains the
originating URL. This may be used to automatically acquire additional resources from the same
source.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderEntries</varname></term>
@@ -584,6 +594,28 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>io.systemd.boot-entries.extra:</varname><replaceable>ID=DEFINITION</replaceable></term>
<listitem><para>This allows inserting additional entries into the <command>systemd-boot</command>
menu. Take a pair of menu entry identifier and menu entry definition string. The former should be
suitable for use as a filename of a Boot Loader Specification Type #1 entry filename (note that it is
used for identification purposes only, no file of this name is actually accessed), the latter shall
follow the syntax of the contents of a Type #1 entry. Any menu entry defined this way is processed
and shown in pretty much the same way as a Type #1 entry read from the ESP or XBOOTLDR
partition. Example:</para>
<programlisting>io.systemd.boot-entries.extra:fooos-current.conf=title FooOS (Current)
uki-url http://example.com/somedir/fooos.efi</programlisting>
<para>Note that this example contains a newline character. When generating this string from a shell
care must be taken to encode it correctly.</para>
<para>Pass multiple strings formatted this way to generate multiple menu entries.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
+25 -3
View File
@@ -206,9 +206,12 @@
<listitem><para>Attach the specified disk image to an automatically allocated loopback block device,
and print the path to the loopback block device to standard output. This is similar to an invocation
of <command>losetup --find --show</command>, but will validate the image as DDI before attaching, and
derive the correct sector size to use automatically. Moreover, it ensures the per-partition block
devices are created before returning. Takes a path to a disk image file.</para>
of <command>losetup --find --show --partscan</command>, but will validate the image as DDI before
attaching, and derive the correct sector size to use automatically. Moreover, it ensures the
per-partition block devices are created before returning and attempts to enable direct IO mode, if
possible. Takes a path to a disk image file.</para>
<para>If combined with <option>--quiet</option> output of the block device name is suppressed.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
@@ -517,6 +520,15 @@
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--loop-ref-auto</option></term>
<listitem><para>Similar to <option>--loop-ref=</option>, but automatically derive the reference
string from the specified backing filename, truncating it if necessary.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--mtree-hash=no</option></term>
@@ -547,6 +559,16 @@
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--quiet</option></term>
<term><option>-q</option></term>
<listitem><para>If combined with <option>--attach</option>, suppresses output of the used loopback
block device path.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
+4
View File
@@ -99,6 +99,10 @@
separate, immutable <filename>/usr/</filename> file system. Also see
<varname>systemd.volatile=</varname> below.</para>
<para>Use <literal>bind:…</literal> to bind mount another directory as operating system root
filesystems (added in v258). Expects an absolute path name referencing an existing directory within the initrd's file
hierarchy to boot into.</para>
<xi:include href="version-info.xml" xpointer="v217"/></listitem>
</varlistentry>
+45 -16
View File
@@ -50,19 +50,49 @@
<para>If the units this generator creates are overridden, for example by units in directories with higher
precedence, drop-ins and additional dependencies created by this generator might still be used.</para>
<para>This generator will only look for the root partition on the same physical disk where the EFI System
Partition (ESP) is located. Note that support from the boot loader is required: the EFI variable
<varname>LoaderDevicePartUUID</varname> of the <constant>4a67b082-0a4c-41cf-b6c7-440b29bb8c4f</constant>
vendor UUID is used to determine from which partition, and hence the disk, from which the system was
booted. If the boot loader does not set this variable, this generator will not be able to detect the root
partition. See the <ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>
for details.</para>
<para>When run in the initial RAM disk (initrd) this generator can automatically search for the root file
system. Specifically:
<para>Similarly, this generator will only look for the other partitions on the same physical disk as the
root partition. In this case, boot loader support is not required. These partitions will not be searched
for on systems where the root file system is distributed on multiple disks, for example via btrfs RAID.
<itemizedlist>
<listitem><para>It will look for the root partition on the same physical disk where the EFI System
Partition (ESP) is located. Note that support from the boot loader is required for this to work: the
EFI variable <varname>LoaderDevicePartUUID</varname> of the
<constant>4a67b082-0a4c-41cf-b6c7-440b29bb8c4f</constant> vendor UUID is used to determine from which
partition (and hence disk) the system was booted. If the boot loader does not set this variable, this
generator will not be able to detect the root partition. See the <ulink
url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink> for
details.</para></listitem>
<listitem><para>Alternatively, it will look for the root file system on a loopback block device whose
<literal>.lo_name</literal> field is set to one of the literal strings <literal>rootdisk</literal> or
<literal>rootdisk.raw</literal>. This field can be set via <command>losetup</command>'s
<option>--loop-ref=</option> string. For images downloaded via
<citerefentry><refentrytitle>systemd-import-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
make sure to set the <literal>blockdev</literal> option and set the local name string to
<literal>rootdisk</literal> to achieve this effect. Note that discovery of the root file system on
loopback block devices like this is only done if <literal>root=gpt-auto</literal> is specified
explicitly on the kernel command line, unlike the discovery based on the boot loader reported ESP which
is also enabled if no <literal>root=</literal> parameter is specified at all. (The latter relies on
<command>systemd-udevd.service</command>'s <filename>/dev/gpt-auto-root</filename> block device symlink
generation).</para></listitem>
</itemizedlist>
</para>
<para>When run on the host system (i.e. after successfully transitioning out of the initrd into the root
filesystem) this generator will look for all other partitions on the same physical disk as the root
partition. For this discovery, boot loader support is not required. Moreover, it is not required that the
root partition was automatically discovered by the initrd (as described above) for the discovery of the
non-root file partitions to take place. Or in other words: automatic discovery of the root file system
and of the non-root file systems are independent operations, that do not rely on each other, and are done
during two distinct phases of the boot process (one in the initrd, the other after). These partitions will
not be searched for on systems where the root file system is distributed on multiple disks, for example
via btrfs RAID.</para>
<para>The root partition can be configured explicitly by symlinking
<filename>/run/systemd/volatile-root</filename> to <filename>/dev/block/$major:$minor</filename>. This is
especially useful if the root mount has been replaced by some form of volatile file system
(overlayfs).</para>
<para><filename>systemd-gpt-auto-generator</filename> is useful for centralizing file system
configuration in the partition table and making configuration in <filename>/etc/fstab</filename> or on
the kernel command line unnecessary.</para>
@@ -225,11 +255,6 @@
<citerefentry><refentrytitle>bootup</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
<para>The root partition can be specified by symlinking <filename>/run/systemd/volatile-root</filename>
to <filename>/dev/block/$major:$minor</filename>. This is especially useful if the root mount has been
replaced by some form of volatile file system (overlayfs).
</para>
<para>Mount and automount units for the EFI System Partition (ESP) and Extended Boot Loader Partition
(XBOOTLDR) are generated on EFI systems. If the disk contains an XBOOTLDR partition, as defined in the
<ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader
@@ -297,10 +322,14 @@
<term><varname>rootflags=</varname></term>
<listitem><para>When <varname>root=</varname> is used with the special value
<literal>gpt-auto</literal> (or if the parameter is not used at all), automatic discovery of the root
<literal>gpt-auto</literal>, full automatic discovery of the root
partition based on the GPT partition type is enabled. Any other value disables this
logic.</para>
<para>If <varname>root=</varname> is not specified at all on the kernel command line automatic
discovery of the root partition via the boot loader reported ESP is also enabled, however in this
case discovery based on the loopback block device <literal>.lo_name</literal> field is not enabled.</para>
<para>The <varname>rootfstype=</varname> and <varname>rootflags=</varname> are used to select the
file system type and options when the root file system is automatically discovered.</para>
+88
View File
@@ -117,6 +117,55 @@
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term>blockdev</term>
<listitem><para>If this option is specified the downloaded image is attached to a loopback block
device (via <filename>systemd-loop@.service</filename>) after completion. This permits booting
from downloaded disk images. This is only supported for <literal>raw</literal> disk images.</para>
<para>Note when this option is used with the purpose of mounting a disk image conforming to the
<ulink url="https://uapi-group.org/specifications/specs/discoverable_disk_image/">Discoverable
Disk Image Specification</ulink> as root file system, and the automatic GPT partition discovery
logic as implemented by
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
shall process it, it's essential to specify <literal>rootdisk</literal> as the local name for the
import. Moreover, <literal>root=gpt-auto</literal> must be specified on the kernel command line
explicitly. Also, prefix the <literal>systemd.pull=</literal> commmand line option with
<literal>rd.</literal> to ensure it is executed in the initial RAM disk (initrd) already, also
see below.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term>bootorigin</term>
<listitem><para>If this option is specified, in place of the URL a simple filename may be
specified. If the system is booted via UEFI HTTP network booting the last component of the
network boot origin URL is replaced by this filename and used as download source. This hence
allows to automatically derive the URLs for disk images from the original boot URL used to invoke
the kernel or boot loader.</para>
<para>If this option is used and the system is not actually booted via UEFI HTTP network booting,
the download is gracefully skipped. Or in other words without other modifications it is possible
to put together an initrd image that will boot from a local disk if available, or from downloaded
disk image if used via network booting.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term>runtime=</term>
<listitem><para>Takes a boolean argument. If set to true, the image is downloaded below the
<filename>/run/</filename> hierarchy, if set to false below the <filename>/var/lib/</filename>
hierarchy. If not specified defaults to true in the initial RAM disk (initrd) and to false on the
host system.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
</variablelist>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
@@ -136,6 +185,10 @@
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
</variablelist>
<para>These kernel command line options are interpreted by the host system only. If these options are
prefixed with <literal>rd.</literal> they are interpreted by the initial RAM disk (initrd)
instead.</para>
</refsect1>
<refsect1>
@@ -179,6 +232,39 @@
validation. This is useful for development purposes in virtual machines and containers. Warning: do not
deploy a system with validation disabled like this!</para>
</example>
<example>
<title>Download root disk image (raw) into memory, for booting into it</title>
<programlisting>rd.systemd.pull=raw,machine,verify=no,blockdev:image:https://example.com/image.raw.xz root=/dev/disk/by-loop-ref/image.raw-part2</programlisting>
<para>This downloads the specified disk image, saving it locally under the name
<literal>image</literal>, and attaches it to a loopback block device on completion. It then boots from
the 2nd partition in the image.</para>
</example>
<example>
<title>Boot into disk image (raw), with URL derived from UEFI HTTP network booting</title>
<programlisting>rd.systemd.pull=raw,machine,verify=no,blockdev,bootorigin:rootdisk:image.raw.xz root=gpt-auto</programlisting>
<para>This is similar to the previous example, but this time the source URL is automatically derived
from the UEFI HTTP network boot URL. For example, if an UKI is booted from an URL
<literal>http://example.com/image.efi</literal> this would result in a root disk being downloaded from
<literal>http://example.com/image.raw.xz</literal>. Moreover this uses the
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
logic to mount the root file system from the disk image.</para>
</example>
<example>
<title>Boot into disk image (tar), with URL derived from UEFI HTTP network booting</title>
<programlisting>rd.systemd.pull=tar,machine,verify=no,bootorigin:root:image.tar.xz root=bind:/run/machines/root</programlisting>
<para>This is similar to the previous example, but instead of a raw (i.e. block device based) disk
image the system boots into a tarball that is downloaded from the originating UEFI network
server.</para>
</example>
</refsect1>
<refsect1>
@@ -189,6 +275,8 @@
<member><citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>importctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-loop@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-loop_.service">
<refentryinfo>
<title>systemd-loop@.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-loop@.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-loop@.service</refname>
<refpurpose>Attach a loopback block device</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-loop@<replaceable>path</replaceable>.service</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-loop@.service</filename> is a template service that may be used to automatically
attach a loopback block device at boot (or later). Its instance string may reference an (escaped) file
system path pointing to a disk image to attach as loopback block device. Use
<citerefentry><refentrytitle>systemd-escape</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
<option>--path</option> to properly escape a file system path.</para>
<para>This unit invokes <command>systemd-dissect --attach --quiet --loop-ref-auto</command> on the
specified files, see
<citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-import-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+10
View File
@@ -550,6 +550,16 @@
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>StubDeviceURL</varname></term>
<listitem><para>If the kernel image has been invoked via network booting this variable contains the
originating URL. This may be used to automatically acquire additional resources from the same
source.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>StubInfo</varname></term>
+12
View File
@@ -265,6 +265,18 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--smbios11=<replaceable>STRING</replaceable></option></term>
<term><option>-s <replaceable>STRING</replaceable></option></term>
<listitem><para>Passes the specified string into the VM as an SMBIOS Type #11 vendor string. This
is useful to parameterize the invoked VM in various ways. See
<citerefentry><refentrytitle>smbios-type-11</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for details.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
</variablelist>
</refsect2>
+20
View File
@@ -386,6 +386,16 @@
directly.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>imports.target</filename></term>
<listitem>
<para>A target unit that pulls in all disk image download jobs to execute on system boot. This is
used by
<citerefentry><refentrytitle>systemd-import-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>init.scope</filename></term>
<listitem>
@@ -1075,6 +1085,16 @@
<xi:include href="version-info.xml" xpointer="v235"/>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>imports-pre.target</filename></term>
<listitem>
<para>A passive unit that is ordered before all disk image download jobs to execute on system
boot. This is used by
<citerefentry><refentrytitle>systemd-import-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>local-fs-pre.target</filename></term>
<listitem>
+2
View File
@@ -53,3 +53,5 @@ Packages=
tgt
tpm2-tools
vim
# kernel-bootcfg --add-uri= is just too useful
virt-firmware
+2
View File
@@ -59,3 +59,5 @@ Packages=
tpm2-tools
veritysetup
vim-common
# kernel-bootcfg --add-uri= is just too useful
virt-firmware
+2
View File
@@ -68,6 +68,8 @@ Packages=
psmisc
python3-pexpect
python3-psutil
# kernel-bootcfg --add-uri= is just too useful
python3-virt-firmware
qrencode
quota
softhsm2
+2
View File
@@ -79,5 +79,7 @@ Packages=
tpm2.0-tools
veritysetup
vim
# kernel-bootcfg --add-uri= is just too useful
virt-firmware
xz
zypper
@@ -12,3 +12,7 @@ VolatilePackages=
systemd
systemd-libs
systemd-udev
# Pull in systemd-container so that the import-generator is available
systemd-container
# Pull in systemd-networkd so that we can do network boot
systemd-networkd
@@ -14,6 +14,8 @@ VolatilePackages=
libsystemd0
libudev1
systemd
# Pull in systemd-container so that the import-generator is available
systemd-container
systemd-cryptsetup
systemd-repart
udev
@@ -14,5 +14,8 @@ VolatilePackages=
libudev1
systemd
systemd-boot # TODO: Drop once opensuse moves the systemd-stub tmpfiles snippet back to the systemd package
# Pull in systemd-container so that the import-generator is available
systemd-container
systemd-experimental
systemd-network
udev
+316 -61
View File
@@ -18,6 +18,7 @@
#include "pe.h"
#include "proto/block-io.h"
#include "proto/device-path.h"
#include "proto/load-file.h"
#include "proto/simple-text-io.h"
#include "random-seed.h"
#include "sbat.h"
@@ -27,6 +28,7 @@
#include "ticks.h"
#include "tpm2-pcr.h"
#include "uki.h"
#include "url-discovery.h"
#include "util.h"
#include "version.h"
#include "vmm.h"
@@ -47,13 +49,26 @@ DECLARE_SBAT(SBAT_BOOT_SECTION_TEXT);
typedef enum LoaderType {
LOADER_UNDEFINED,
LOADER_AUTO,
LOADER_EFI,
LOADER_LINUX, /* Boot loader spec type #1 entries */
LOADER_UNIFIED_LINUX, /* Boot loader spec type #2 entries */
LOADER_EFI, /* Boot loader spec type #1 entries with "efi" line */
LOADER_LINUX, /* Boot loader spec type #1 entries with "linux" line */
LOADER_UKI, /* Boot loader spec type #1 entries with "uki" line */
LOADER_UKI_URL, /* Boot loader spec type #1 entries with "uki-url" line */
LOADER_TYPE2_UKI, /* Boot loader spec type #2 entries */
LOADER_SECURE_BOOT_KEYS,
LOADER_BAD, /* Marker: this boot loader spec type #1 entry is invalid */
LOADER_IGNORE, /* Marker: this boot loader spec type #1 entry does not match local host */
_LOADER_TYPE_MAX,
} LoaderType;
/* Which loader types permit command line editing */
#define LOADER_TYPE_ALLOW_EDITOR(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI)
/* Which loader types allow command line editing in SecureBoot mode */
#define LOADER_TYPE_ALLOW_EDITOR_IN_SB(t) IN_SET(t, LOADER_EFI, LOADER_LINUX)
/* Which loader types shall be considered for automatic selection */
#define LOADER_TYPE_MAY_AUTO_SELECT(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI)
typedef struct {
char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry, possibly suffixed with a profile id) */
char16_t *id_without_profile; /* same, but without any profile id suffixed */
@@ -65,6 +80,7 @@ typedef struct {
EFI_HANDLE *device;
LoaderType type;
char16_t *loader;
char16_t *url;
char16_t *devicetree;
char16_t *options;
bool options_implied; /* If true, these options are implied if we invoke the PE binary without any parameters (as in: UKI). If false we must specify these options explicitly. */
@@ -608,6 +624,8 @@ static void print_status(Config *config, char16_t *loaded_image_path) {
printf(" device: %ls\n", dp_str);
if (entry->loader)
printf(" loader: %ls\n", entry->loader);
if (entry->url)
printf(" url: %ls\n", entry->url);
STRV_FOREACH(initrd, entry->initrd)
printf(" initrd: %ls\n", *initrd);
if (entry->devicetree)
@@ -994,7 +1012,7 @@ static bool menu_run(
case KEYPRESS(0, 0, 'E'):
/* only the options of configured entries can be edited */
if (!config->editor ||
!IN_SET(config->entries[idx_highlight]->type, LOADER_EFI, LOADER_LINUX, LOADER_UNIFIED_LINUX)) {
!LOADER_TYPE_ALLOW_EDITOR(config->entries[idx_highlight]->type)) {
status = xstrdup16(u"Entry does not support editing the command line.");
break;
}
@@ -1002,7 +1020,7 @@ static bool menu_run(
/* Unified kernels that are signed as a whole will not accept command line options
* when secure boot is enabled unless there is none embedded in the image. Do not try
* to pretend we can edit it to only have it be ignored. */
if (config->entries[idx_highlight]->type == LOADER_UNIFIED_LINUX &&
if (!LOADER_TYPE_ALLOW_EDITOR_IN_SB(config->entries[idx_highlight]->type) &&
secure_boot_enabled() &&
config->entries[idx_highlight]->options) {
status = xstrdup16(u"Entry not editable in SecureBoot mode.");
@@ -1199,6 +1217,7 @@ static BootEntry* boot_entry_free(BootEntry *entry) {
free(entry->version);
free(entry->machine_id);
free(entry->loader);
free(entry->url);
free(entry->devicetree);
free(entry->options);
strv_free(entry->initrd);
@@ -1445,7 +1464,6 @@ static void boot_entry_add_type1(
assert(config);
assert(device);
assert(root_dir);
assert(path);
assert(file);
assert(content);
@@ -1473,26 +1491,76 @@ static void boot_entry_add_type1(
entry->machine_id = xstr8_to_16(value);
} else if (streq8(key, "linux")) {
if (!IN_SET(entry->type, LOADER_UNDEFINED, LOADER_LINUX)) {
entry->type = LOADER_BAD;
break;
}
free(entry->loader);
entry->type = LOADER_LINUX;
entry->loader = xstr8_to_path(value);
entry->key = 'l';
} else if (streq8(key, "uki")) {
if (!IN_SET(entry->type, LOADER_UNDEFINED, LOADER_UKI)) {
entry->type = LOADER_BAD;
break;
}
free(entry->loader);
entry->type = LOADER_UKI;
entry->loader = xstr8_to_path(value);
entry->key = 'l';
} else if (streq8(key, "uki-url")) {
if (!IN_SET(entry->type, LOADER_UNDEFINED, LOADER_UKI_URL)) {
entry->type = LOADER_BAD;
break;
}
_cleanup_free_ char16_t *p = xstr8_to_16(value);
const char16_t *e = startswith(p, u":");
if (e) {
_cleanup_free_ char16_t *origin = disk_get_url(device);
if (!origin) {
/* Automatically hide entries that require an original URL but where none is available. */
entry->type = LOADER_IGNORE;
break;
}
entry->url = url_replace_last_component(origin, p);
} else
entry->url = TAKE_PTR(p);
entry->type = LOADER_UKI_URL;
entry->key = 'l';
} else if (streq8(key, "efi")) {
if (!IN_SET(entry->type, LOADER_UNDEFINED, LOADER_EFI)) {
entry->type = LOADER_BAD;
break;
}
entry->type = LOADER_EFI;
free(entry->loader);
entry->loader = xstr8_to_path(value);
/* do not add an entry for ourselves */
if (strcaseeq16(entry->loader, loaded_image_path)) {
entry->type = LOADER_UNDEFINED;
entry->type = LOADER_IGNORE;
break;
}
} else if (streq8(key, "architecture")) {
/* do not add an entry for an EFI image of architecture not matching with that of the image */
if (!strcaseeq8(value, EFI_MACHINE_TYPE_NAME)) {
entry->type = LOADER_UNDEFINED;
entry->type = LOADER_IGNORE;
break;
}
@@ -1520,14 +1588,17 @@ static void boot_entry_add_type1(
entry->options = TAKE_PTR(new);
}
if (entry->type == LOADER_UNDEFINED)
/* Filter all entries that are badly defined or don't apply to the local system. */
if (IN_SET(entry->type, LOADER_UNDEFINED, LOADER_BAD, LOADER_IGNORE))
return;
/* check existence */
_cleanup_file_close_ EFI_FILE *handle = NULL;
err = root_dir->Open(root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL);
if (err != EFI_SUCCESS)
return;
/* Check existence of loader file */
if (entry->loader) {
_cleanup_file_close_ EFI_FILE *handle = NULL;
err = root_dir->Open(root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL);
if (err != EFI_SUCCESS)
return;
}
entry->device = device;
entry->id = xstrdup16(file);
@@ -1535,7 +1606,8 @@ static void boot_entry_add_type1(
config_add_entry(config, entry);
boot_entry_parse_tries(entry, path, file, u".conf");
if (path)
boot_entry_parse_tries(entry, path, file, u".conf");
TAKE_PTR(entry);
}
@@ -1645,6 +1717,19 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
(void) efivar_get_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", &config->entry_saved);
}
static bool valid_type1_filename(const char16_t *fname) {
assert(fname);
if (IN_SET(fname[0], u'.', u'\0'))
return false;
if (!endswith_no_case(fname, u".conf"))
return false;
if (startswith_no_case(fname, u"auto-"))
return false;
return true;
}
static void config_load_type1_entries(
Config *config,
EFI_HANDLE *device,
@@ -1675,13 +1760,9 @@ static void config_load_type1_entries(
if (err != EFI_SUCCESS || !f)
break;
if (f->FileName[0] == '.')
continue;
if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY))
continue;
if (!endswith_no_case(f->FileName, u".conf"))
continue;
if (startswith_no_case(f->FileName, u"auto-"))
if (!valid_type1_filename(f->FileName))
continue;
err = file_read(entries_dir,
@@ -1697,6 +1778,41 @@ static void config_load_type1_entries(
}
}
static void config_load_smbios_entries(
Config *config,
EFI_HANDLE *device,
EFI_FILE *root_dir,
const char16_t *loaded_image_path) {
assert(config);
assert(device);
assert(root_dir);
/* Loads Boot Loader Type #1 entries from SMBIOS 11 */
if (is_confidential_vm())
return; /* Don't consume SMBIOS in CoCo contexts */
for (const char *after = NULL, *extra;; after = extra) {
extra = smbios_find_oem_string("io.systemd.boot.entries-extra:", after);
if (!extra)
break;
const char *eq = strchr8(extra, '=');
if (!eq)
continue;
_cleanup_free_ char16_t *fname = xstrn8_to_16(extra, eq - extra);
if (!valid_type1_filename(fname))
continue;
/* Make a copy, since boot_entry_add_type1() wants to modify it */
_cleanup_free_ char *contents = xstrdup8(eq + 1);
boot_entry_add_type1(config, device, root_dir, /* path= */ NULL, fname, contents, loaded_image_path);
}
}
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
int r;
@@ -1806,7 +1922,7 @@ static void config_select_default_entry(Config *config) {
/* select the first suitable entry */
for (i = 0; i < config->n_entries; i++)
if (config->entries[i]->type != LOADER_AUTO && !config->entries[i]->call) {
if (LOADER_TYPE_MAY_AUTO_SELECT(config->entries[i]->type)) {
config->idx_default = i;
return;
}
@@ -2323,7 +2439,7 @@ static void boot_entry_add_type2(
*entry = (BootEntry) {
.id = strtolower16(TAKE_PTR(id)),
.id_without_profile = profile > 0 ? strtolower16(xstrdup16(filename)) : NULL,
.type = LOADER_UNIFIED_LINUX,
.type = LOADER_TYPE2_UKI,
.title = TAKE_PTR(title),
.version = xstrdup16(good_version),
.device = device,
@@ -2523,13 +2639,77 @@ static EFI_STATUS initrd_prepare(
return EFI_SUCCESS;
}
static EFI_STATUS expand_path(
EFI_HANDLE parent_image,
EFI_DEVICE_PATH *path,
EFI_DEVICE_PATH **ret_expanded_path) {
EFI_STATUS err;
assert(parent_image);
assert(path);
assert(ret_expanded_path);
_cleanup_free_ EFI_HANDLE *handles = NULL;
size_t n_handles = 0;
err = BS->LocateHandleBuffer(
ByProtocol,
MAKE_GUID_PTR(EFI_LOAD_FILE_PROTOCOL),
/* SearchKey= */ NULL,
&n_handles,
&handles);
if (!IN_SET(err, EFI_SUCCESS, EFI_NOT_FOUND))
return log_error_status(err, "Failed to get list of LoadFile protocol handles: %m");
FOREACH_ARRAY(h, handles, n_handles) {
EFI_LOAD_FILE_PROTOCOL *load_file = NULL;
err = BS->OpenProtocol(
*h,
MAKE_GUID_PTR(EFI_LOAD_FILE_PROTOCOL),
(void**) &load_file,
parent_image,
/* ControllerHandler= */ NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (IN_SET(err, EFI_NOT_FOUND, EFI_INVALID_PARAMETER))
continue; /* Skip over LoadFile() handles that are not suitable for this kind of device path */
if (err != EFI_SUCCESS) {
log_warning_status(err, "Failed to get LoadFile() protocol, ignoring: %m");
continue;
}
/* Issue a LoadFile() request without interest in the actual data (i.e. size is zero and
* buffer pointer is NULL), but with BootPolicy set to true, this has the effect of
* downloading the URL and establishing a handle for it. */
size_t size = 0;
err = load_file->LoadFile(load_file, path, /* BootPolicy= */ true, &size, /* Buffer= */ NULL);
if (IN_SET(err, EFI_NOT_FOUND, EFI_INVALID_PARAMETER))
continue; /* Skip over LoadFile() handles that after all don't consider themselves
* appropriate for this kind of path */
if (err != EFI_BUFFER_TOO_SMALL) {
log_warning_status(err, "Failed to get file via LoadFile() protocol, ignoring: %m");
continue;
}
/* Now read the updated file path */
EFI_DEVICE_PATH *load_file_path = NULL;
err = BS->HandleProtocol(*h, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &load_file_path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Failed to get LoadFile() device path: %m");
/* And return a copy */
*ret_expanded_path = device_path_dup(load_file_path);
return EFI_SUCCESS;
}
return EFI_NOT_FOUND;
}
static EFI_STATUS image_start(
EFI_HANDLE parent_image,
const BootEntry *entry) {
_cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {};
_cleanup_(unload_imagep) EFI_HANDLE image = NULL;
_cleanup_free_ EFI_DEVICE_PATH *path = NULL;
EFI_STATUS err;
assert(entry);
@@ -2539,38 +2719,94 @@ static EFI_STATUS image_start(
(void) entry->call();
_cleanup_file_close_ EFI_FILE *image_root = NULL;
err = open_volume(entry->device, &image_root);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error opening root path: %m");
err = make_file_device_path(entry->device, entry->loader, &path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error making file device path: %m");
size_t initrd_size = 0;
_cleanup_pages_ Pages initrd_pages = {};
_cleanup_free_ char16_t *options_initrd = NULL;
err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error preparing initrd: %m");
err = shim_load_image(parent_image, path, &image);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error loading %ls: %m", entry->loader);
/* DTBs are loaded by the kernel before ExitBootServices, and they can be used to map and assign
* arbitrary memory ranges, so skip them when secure boot is enabled as the DTB here is unverified.
*/
if (entry->devicetree && !secure_boot_enabled()) {
err = devicetree_install(&dtstate, image_root, entry->devicetree);
_cleanup_free_ EFI_DEVICE_PATH *path = NULL;
bool boot_policy;
if (entry->url) {
/* Generate a device path that only contains the URL */
err = make_url_device_path(entry->url, &path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error loading %ls: %m", entry->devicetree);
return log_error_status(err, "Error making URL device path: %m");
/* Try to expand this path on all available NICs and IP protocols */
_cleanup_free_ EFI_DEVICE_PATH *expanded_path = NULL;
for (unsigned n_attempt = 0;; n_attempt++) {
err = expand_path(parent_image, path, &expanded_path);
if (err == EFI_SUCCESS) {
/* If this worked then let's try to boot with the expanded path. */
free(path);
path = TAKE_PTR(expanded_path);
break;
}
if (err != EFI_NOT_FOUND || n_attempt > 5) {
log_warning_status(err, "Failed to expand device path, ignoring: %m");
break;
}
/* Maybe the network devices have been configured for this yet (because we are the
* first piece of code trying to do networking)? Then let's connect them, and try
* again. */
reconnect_all_drivers();
}
/* Note: if the path expansion doesn't work, we'll continue with the unexpanded path. Which
* will probably fail on many (most?) firmwares, but it's worth a try. */
boot_policy = true; /* Set BootPolicy parameter to LoadImage() to true, which ultimately
* controls whether the LoadFile (and thus HTTP boot) or LoadFile2 (which
* does not set up HTTP boot) protocol shall be used. */
} else {
assert(entry->device);
assert(entry->loader);
err = open_volume(entry->device, &image_root);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error opening root path: %m");
err = make_file_device_path(entry->device, entry->loader, &path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error making file device path: %m");
boot_policy = false;
}
/* Authenticate the image before we continue with initrd or DT stuff */
err = shim_load_image(parent_image, path, boot_policy, &image);
if (err != EFI_SUCCESS) {
if (entry->url) {
/* EFI_NOT_FOUND typically indicates that no network stack or NIC was available, let's give the user a hint. */
if (err == EFI_NOT_FOUND) {
log_info("Unable to boot remote UKI %ls, is networking available?", entry->url);
return err;
}
return log_error_status(err, "Error loading loading remote UKI %ls: %m", entry->url);
}
return log_error_status(err, "Error loading EFI binary %ls: %m", entry->loader);
}
_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
err = initrd_register(PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr), initrd_size, &initrd_handle);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error registering initrd: %m");
_cleanup_free_ char16_t *options_initrd = NULL;
_cleanup_pages_ Pages initrd_pages = {};
size_t initrd_size = 0;
if (image_root) {
err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error preparing initrd: %m");
/* DTBs are loaded by the kernel before ExitBootServices(), and they can be used to map and
* assign arbitrary memory ranges, so skip them when secure boot is enabled as the DTB here
* is unverified. */
if (entry->devicetree && !secure_boot_enabled()) {
err = devicetree_install(&dtstate, image_root, entry->devicetree);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error loading %ls: %m", entry->devicetree);
}
err = initrd_register(PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr), initrd_size, &initrd_handle);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error registering initrd: %m");
}
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
@@ -2583,7 +2819,7 @@ static EFI_STATUS image_start(
_cleanup_free_ char16_t *options = xstrdup16(options_initrd ?: entry->options_implied ? NULL : entry->options);
if (entry->type == LOADER_LINUX && !is_confidential_vm()) {
const char *extra = smbios_find_oem_string("io.systemd.boot.kernel-cmdline-extra");
const char *extra = smbios_find_oem_string("io.systemd.boot.kernel-cmdline-extra=", /* after= */ NULL);
if (extra) {
_cleanup_free_ char16_t *tmp = TAKE_PTR(options), *extra16 = xstr8_to_16(extra);
if (isempty(tmp))
@@ -2636,7 +2872,7 @@ static EFI_STATUS image_start(
err = EFI_UNSUPPORTED;
}
return log_error_status(err, "Failed to execute %ls (%ls): %m", entry->title_show, entry->loader);
return log_error_status(err, "Failed to execute %ls (%ls): %m", entry->title_show, entry->loader ?: entry->url);
}
static void config_free(Config *config) {
@@ -2764,6 +3000,9 @@ static void export_loader_variables(
EFI_LOADER_FEATURE_RETAIN_SHIM |
EFI_LOADER_FEATURE_MENU_DISABLE |
EFI_LOADER_FEATURE_MULTI_PROFILE_UKI |
EFI_LOADER_FEATURE_REPORT_URL |
EFI_LOADER_FEATURE_TYPE1_UKI |
EFI_LOADER_FEATURE_TYPE1_UKI_URL |
0;
assert(loaded_image);
@@ -2794,6 +3033,9 @@ static void config_load_all_entries(
/* Similar, but on any XBOOTLDR partition */
config_load_xbootldr(config, loaded_image->DeviceHandle);
/* Pick up entries defined via SMBIOS Type #11 */
config_load_smbios_entries(config, loaded_image->DeviceHandle, root_dir, loaded_image_path);
/* Sort entries after version number */
sort_pointer_array((void **) config->entries, config->n_entries, (compare_pointer_func_t) boot_entry_compare);
@@ -2876,21 +3118,34 @@ static EFI_STATUS run(EFI_HANDLE image) {
init_usec = time_usec();
/* Ask Shim to leave its protocol around, so that the stub can use it to validate PEs.
* By default, Shim uninstalls its protocol when calling StartImage(). */
shim_retain_protocol();
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
err = discover_root_dir(loaded_image, &root_dir);
if (err != EFI_SUCCESS) {
log_error_status(err, "Unable to open root directory: %m");
/* If opening the root directory fails this typically means someone is trying to boot our
* systemd-boot EFI PE binary as network boot NBP. That cannot work however, since we
* wouldn't find any menu entries. Provide a helpful message what to try instead. */
if (err == EFI_UNSUPPORTED)
log_info("| Note that invoking systemd-boot directly as UEFI network boot NBP is not\n"
"| supported. Instead of booting the systemd-boot PE binary (i.e. an .efi file)\n"
"| via the network, use an EFI GPT disk image (i.e. a file with .img suffix)\n"
"| containing systemd-boot instead.");
return err;
}
/* Ask Shim to leave its protocol around, so that the stub can use it to validate PEs.
* By default, Shim uninstalls its protocol when calling StartImage(). */
shim_retain_protocol();
export_common_variables(loaded_image);
export_loader_variables(loaded_image, init_usec);
err = discover_root_dir(loaded_image, &root_dir);
if (err != EFI_SUCCESS)
return log_error_status(err, "Unable to open root directory: %m");
(void) load_drivers(image, loaded_image, root_dir);
_cleanup_free_ char16_t *loaded_image_path = NULL;
+43 -1
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "device-path-util.h"
#include "efi-string.h"
#include "util.h"
EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp) {
@@ -38,6 +39,38 @@ EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DE
return EFI_SUCCESS;
}
EFI_STATUS make_url_device_path(const char16_t *url, EFI_DEVICE_PATH **ret) {
assert(url);
assert(ret);
/* Turns a URL into a simple one-element URL device path. */
_cleanup_free_ char* u = xstr16_to_ascii(url);
if (!u)
return EFI_INVALID_PARAMETER;
size_t l = strlen8(u);
size_t t = offsetof(URI_DEVICE_PATH, Uri) + l + sizeof(EFI_DEVICE_PATH);
EFI_DEVICE_PATH *dp = xmalloc(t);
URI_DEVICE_PATH *udp = (URI_DEVICE_PATH*) dp;
udp->Header = (EFI_DEVICE_PATH) {
.Type = MESSAGING_DEVICE_PATH,
.SubType = MSG_URI_DP,
.Length = offsetof(URI_DEVICE_PATH, Uri) + l,
};
memcpy(udp->Uri, u, l);
EFI_DEVICE_PATH *end = device_path_next_node(dp);
*end = DEVICE_PATH_END_NODE;
assert(((uint8_t*) end + sizeof(EFI_DEVICE_PATH)) == ((uint8_t*) dp + t));
*ret = TAKE_PTR(dp);
return EFI_SUCCESS;
}
static char16_t *device_path_to_str_internal(const EFI_DEVICE_PATH *dp) {
char16_t *str = NULL;
@@ -90,7 +123,7 @@ EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret) {
return EFI_SUCCESS;
}
str = dp_to_text->ConvertDevicePathToText(dp, false, false);
str = dp_to_text->ConvertDevicePathToText(dp, /* DisplayOnly=*/ false, /* AllowShortcuts= */ false);
if (!str)
return EFI_OUT_OF_RESOURCES;
@@ -136,3 +169,12 @@ EFI_DEVICE_PATH *device_path_replace_node(
*end = DEVICE_PATH_END_NODE;
return ret;
}
size_t device_path_size(const EFI_DEVICE_PATH *dp) {
const EFI_DEVICE_PATH *i = ASSERT_PTR(dp);
for (; !device_path_is_end(i); i = device_path_next_node(i))
;
return (const uint8_t*) i - (const uint8_t*) dp + sizeof(EFI_DEVICE_PATH);
}
+8
View File
@@ -2,8 +2,10 @@
#pragma once
#include "proto/device-path.h"
#include "util.h"
EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp);
EFI_STATUS make_url_device_path(const char16_t *url, EFI_DEVICE_PATH **ret);
EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret);
bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start);
EFI_DEVICE_PATH *device_path_replace_node(
@@ -25,3 +27,9 @@ static inline bool device_path_is_end(const EFI_DEVICE_PATH *dp) {
.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, \
.Length = sizeof(EFI_DEVICE_PATH) \
}
size_t device_path_size(const EFI_DEVICE_PATH *dp);
static inline EFI_DEVICE_PATH *device_path_dup(const EFI_DEVICE_PATH *dp) {
return xmemdup(ASSERT_PTR(dp), device_path_size(dp));
}
+12 -4
View File
@@ -55,15 +55,23 @@ EFI_STATUS reconnect_all_drivers(void) {
/* Reconnects all handles, so that any loaded drivers can take effect. */
err = BS->LocateHandleBuffer(AllHandles, NULL, NULL, &n_handles, &handles);
if (err != EFI_SUCCESS)
err = BS->LocateHandleBuffer(
AllHandles,
/* Protocol= */ NULL,
/* SearchKey= */ NULL,
&n_handles, &handles);
if (!IN_SET(err, EFI_SUCCESS, EFI_NOT_FOUND))
return log_error_status(err, "Failed to get list of handles: %m");
for (size_t i = 0; i < n_handles; i++)
FOREACH_ARRAY(h, handles, n_handles)
/* Some firmware gives us some bogus handles (or they might become bad due to
* reconnecting everything). Security policy may also prevent us from doing so too.
* There is nothing we can realistically do on errors anyways, so just ignore them. */
(void) BS->ConnectController(handles[i], NULL, NULL, true);
(void) BS->ConnectController(
*h,
/* DriverImageHandle= */ NULL,
/* RemainingDevicePath= */ NULL,
/* Recursive= */ true);
return EFI_SUCCESS;
}
+53 -3
View File
@@ -188,8 +188,10 @@ static unsigned utf8_to_unichar(const char *utf8, size_t n, char32_t *c) {
/* Convert UTF-8 to UCS-2, skipping any invalid or short byte sequences. */
char16_t *xstrn8_to_16(const char *str8, size_t n) {
if (!str8 || n == 0)
return NULL;
assert(str8 || n == 0);
if (n == SIZE_MAX)
n = strlen8(str8);
size_t i = 0;
char16_t *str16 = xnew(char16_t, n + 1);
@@ -209,10 +211,34 @@ char16_t *xstrn8_to_16(const char *str8, size_t n) {
}
}
str16[i] = '\0';
str16[i] = u'\0';
return str16;
}
char *xstrn16_to_ascii(const char16_t *str16, size_t n) {
assert(str16 || n == 0);
if (n == SIZE_MAX)
n = strlen16(str16);
_cleanup_free_ char *str8 = xnew(char, n + 1);
size_t i = 0;
while (n > 0 && *str16 != u'\0') {
if ((uint16_t) *str16 > 127U) /* Not ASCII? Fail! */
return NULL;
str8[i++] = (char) (uint16_t) *str16;
str16++;
n--;
}
str8[i] = '\0';
return TAKE_PTR(str8);
}
char* startswith8(const char *s, const char *prefix) {
size_t l;
@@ -1083,3 +1109,27 @@ void *memset(void *p, int c, size_t n) {
return p;
}
size_t strspn16(const char16_t *p, const char16_t *good) {
assert(p);
assert(good);
const char16_t *i = p;
for (; *i != 0; i++)
if (!strchr16(good, *i))
break;
return i - p;
}
size_t strcspn16(const char16_t *p, const char16_t *bad) {
assert(p);
assert(bad);
const char16_t *i = p;
for (; *i != 0; i++)
if (strchr16(bad, *i))
break;
return i - p;
}
+9 -1
View File
@@ -98,11 +98,19 @@ static inline char16_t *xstrdup16(const char16_t *s) {
char16_t *xstrn8_to_16(const char *str8, size_t n);
static inline char16_t *xstr8_to_16(const char *str8) {
return xstrn8_to_16(str8, strlen8(str8));
return xstrn8_to_16(str8, SIZE_MAX);
}
char *xstrn16_to_ascii(const char16_t *str16, size_t n);
static inline char *xstr16_to_ascii(const char16_t *str16) {
return xstrn16_to_ascii(str16, SIZE_MAX);
}
char* startswith8(const char *s, const char *prefix);
size_t strspn16(const char16_t *p, const char16_t *good);
size_t strcspn16(const char16_t *p, const char16_t *bad);
bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack);
bool parse_number8(const char *s, uint64_t *ret_u, const char **ret_tail);
+13 -5
View File
@@ -4,17 +4,25 @@
#include "efivars.h"
#include "export-vars.h"
#include "part-discovery.h"
#include "url-discovery.h"
#include "util.h"
void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
assert(loaded_image);
/* Export the device path this image is started from, if it's not set yet */
if (loaded_image->DeviceHandle &&
efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) {
_cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle);
if (uuid)
efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0);
if (loaded_image->DeviceHandle) {
if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", /* ret_data= */ NULL, /* ret_size= */ NULL) != EFI_SUCCESS) {
_cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle);
if (uuid)
efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0);
}
if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDeviceURL", /* ret_data= */ NULL, /* ret_size= */ NULL) != EFI_SUCCESS) {
_cleanup_free_ char16_t *url = disk_get_url(loaded_image->DeviceHandle);
if (url)
efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderDeviceURL", url, 0);
}
}
/* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the
+1
View File
@@ -300,6 +300,7 @@ libefi_sources = files(
'shim.c',
'smbios.c',
'ticks.c',
'url-discovery.c',
'util.c',
'vmm.c',
)
+7
View File
@@ -30,6 +30,8 @@ enum {
MEDIA_FILEPATH_DP = 0x04,
MEDIA_PIWG_FW_FILE_DP = 0x06,
MEDIA_PIWG_FW_VOL_DP = 0x07,
MSG_URI_DP = 24,
};
struct _packed_ EFI_DEVICE_PATH_PROTOCOL {
@@ -67,6 +69,11 @@ typedef struct {
char16_t PathName[];
} _packed_ FILEPATH_DEVICE_PATH;
typedef struct {
EFI_DEVICE_PATH Header;
char Uri[];
} _packed_ URI_DEVICE_PATH;
typedef struct {
char16_t* (EFIAPI *ConvertDeviceNodeToText)(
const EFI_DEVICE_PATH *DeviceNode,
+12 -3
View File
@@ -81,7 +81,12 @@ static bool shim_validate(
return shim_lock->shim_verify(file_buffer, file_size) == EFI_SUCCESS;
}
EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path, EFI_HANDLE *ret_image) {
EFI_STATUS shim_load_image(
EFI_HANDLE parent,
const EFI_DEVICE_PATH *device_path,
bool boot_policy,
EFI_HANDLE *ret_image) {
assert(device_path);
assert(ret_image);
@@ -91,8 +96,12 @@ EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path
install_security_override(shim_validate, NULL);
EFI_STATUS ret = BS->LoadImage(
/*BootPolicy=*/false, parent, (EFI_DEVICE_PATH *) device_path, NULL, 0, ret_image);
/* BootPolicy= */ boot_policy,
parent,
(EFI_DEVICE_PATH *) device_path,
/* SourceBuffer= */ NULL,
/* SourceSize= */ 0,
ret_image);
if (have_shim)
uninstall_security_override();
+1 -1
View File
@@ -12,5 +12,5 @@
#include "efi.h"
bool shim_loaded(void);
EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path, EFI_HANDLE *ret_image);
EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path, bool boot_policy, EFI_HANDLE *ret_image);
void shim_retain_protocol(void);
+4 -4
View File
@@ -182,7 +182,7 @@ bool smbios_in_hypervisor(void) {
return FLAGS_SET(type0->bios_characteristics_ext[1], 1 << 4);
}
const char* smbios_find_oem_string(const char *name) {
const char* smbios_find_oem_string(const char *name, const char *after) {
uint64_t left;
assert(name);
@@ -199,9 +199,9 @@ const char* smbios_find_oem_string(const char *name) {
if (!e || e == p) /* Double NUL byte means we've reached the end of the OEM strings. */
break;
const char *eq = startswith8(p, name);
if (eq && *eq == '=')
return eq + 1;
const char *suffix = startswith8(p, name);
if (suffix && (!after || suffix > after))
return suffix;
p = e + 1;
}
+1 -1
View File
@@ -5,7 +5,7 @@
bool smbios_in_hypervisor(void);
const char* smbios_find_oem_string(const char *name);
const char* smbios_find_oem_string(const char *name, const char *after);
typedef struct RawSmbiosInfo {
const char *manufacturer;
+8 -2
View File
@@ -21,6 +21,7 @@
#include "splash.h"
#include "tpm2-pcr.h"
#include "uki.h"
#include "url-discovery.h"
#include "util.h"
#include "version.h"
#include "vmm.h"
@@ -151,6 +152,7 @@ static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsig
EFI_STUB_FEATURE_DEVICETREE_ADDONS | /* We pick up .dtb addons */
EFI_STUB_FEATURE_MULTI_PROFILE_UKI | /* We grok the "@1" profile command line argument */
EFI_STUB_FEATURE_REPORT_STUB_PARTITION | /* We set StubDevicePartUUID + StubImageIdentifier */
EFI_STUB_FEATURE_REPORT_URL | /* We set StubDeviceURL + LoaderDeviceURL */
0;
assert(loaded_image);
@@ -167,6 +169,10 @@ static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsig
_cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle);
if (uuid)
efivar_set_str16(MAKE_GUID_PTR(LOADER), u"StubDevicePartUUID", uuid, 0);
_cleanup_free_ char16_t *url = disk_get_url(loaded_image->DeviceHandle);
if (url)
efivar_set_str16(MAKE_GUID_PTR(LOADER), u"StubDeviceURL", url, 0);
}
if (loaded_image->FilePath) {
@@ -592,7 +598,7 @@ static EFI_STATUS load_addons(
/* By using shim_load_image, we cover both the case where the PE files are signed with MoK
* and with DB, and running with or without shim. */
err = shim_load_image(stub_image, addon_path, &addon);
err = shim_load_image(stub_image, addon_path, /* boot_policy= */ false, &addon);
if (err != EFI_SUCCESS) {
log_error_status(err,
"Failed to read '%ls' from '%ls', ignoring: %m",
@@ -777,7 +783,7 @@ static void cmdline_append_and_measure_smbios(char16_t **cmdline, int *parameter
if (is_confidential_vm())
return;
const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra");
const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra=", /* after= */ NULL);
if (!extra)
return;
+70 -7
View File
@@ -332,30 +332,70 @@ TEST(xstrdup16) {
TEST(xstrn8_to_16) {
char16_t *s = NULL;
assert_se(xstrn8_to_16(NULL, 1) == NULL);
assert_se(xstrn8_to_16("a", 0) == NULL);
assert_se(s = xstrn8_to_16(NULL, 0));
ASSERT_TRUE(streq16(s, u""));
free(s);
assert_se(s = xstrn8_to_16("", 0));
ASSERT_TRUE(streq16(s, u""));
free(s);
assert_se(s = xstrn8_to_16("a", 0));
ASSERT_TRUE(streq16(s, u""));
free(s);
assert_se(s = xstrn8_to_16("", 1));
assert_se(streq16(s, u""));
ASSERT_TRUE(streq16(s, u""));
free(s);
assert_se(s = xstrn8_to_16("1", 1));
assert_se(streq16(s, u"1"));
ASSERT_TRUE(streq16(s, u"1"));
free(s);
assert_se(s = xstr8_to_16("abcxyzABCXYZ09 .,-_#*!\"§$%&/()=?`~"));
assert_se(streq16(s, u"abcxyzABCXYZ09 .,-_#*!\"§$%&/()=?`~"));
ASSERT_TRUE(streq16(s, u"abcxyzABCXYZ09 .,-_#*!\"§$%&/()=?`~"));
free(s);
assert_se(s = xstr8_to_16("ÿⱿ𝇉 😺"));
assert_se(streq16(s, u"ÿⱿ "));
ASSERT_TRUE(streq16(s, u"ÿⱿ "));
free(s);
assert_se(s = xstrn8_to_16("¶¶", 3));
assert_se(streq16(s, u""));
ASSERT_TRUE(streq16(s, u""));
free(s);
}
TEST(xstrn16_to_ascii) {
char *s;
assert_se(s = xstrn16_to_ascii(NULL, 0));
ASSERT_TRUE(streq8(s, ""));
free(s);
assert_se(s = xstrn16_to_ascii(u"", 0));
ASSERT_TRUE(streq8(s, ""));
free(s);
assert_se(s = xstrn16_to_ascii(u"a", 0));
ASSERT_TRUE(streq8(s, ""));
free(s);
assert_se(s = xstrn16_to_ascii(u"", 1));
ASSERT_TRUE(streq8(s, ""));
free(s);
assert_se(s = xstrn16_to_ascii(u"1", 1));
ASSERT_TRUE(streq8(s, "1"));
free(s);
assert_se(s = xstr16_to_ascii(u"abcxyzABCXYZ09 .,-_#*!\"$%&/()=?`~"));
ASSERT_TRUE(streq8(s, "abcxyzABCXYZ09 .,-_#*!\"$%&/()=?`~"));
free(s);
assert_se(!xstr16_to_ascii(u"ÿⱿ𝇉 😺"));
assert_se(!xstr16_to_ascii(u"¶¶"));
}
TEST(startswith8) {
assert_se(streq8(startswith8("", ""), ""));
assert_se(streq8(startswith8("x", ""), "x"));
@@ -791,4 +831,27 @@ TEST(efi_memset) {
assert_se(memcmp(buf, "aaaaaaaaaa", 10) == 0);
}
TEST(efi_strspn) {
ASSERT_EQ(strspn16(u"foobar", u"fo"), 3U);
ASSERT_EQ(strspn16(u"foobar", u"ob"), 0U);
ASSERT_EQ(strspn16(u"foobar", u"fxayzy"), 1U);
ASSERT_EQ(strspn16(u"", u"abcdefef"), 0U);
ASSERT_EQ(strspn16(u"", u""), 0U);
ASSERT_EQ(strspn16(u"foobar", u""), 0U);
ASSERT_EQ(strspn16(u"foffffffffffoobar", u"fofo"), 14U);
}
TEST(efi_strcspn) {
ASSERT_EQ(strcspn16(u"foobar", u"fo"), 0U);
ASSERT_EQ(strcspn16(u"foobar", u"bar"), 3U);
ASSERT_EQ(strcspn16(u"foobar", u"xhyfzy"), 0U);
ASSERT_EQ(strcspn16(u"foobar", u"xhyozy"), 1U);
ASSERT_EQ(strcspn16(u"foobar", u"xhyzy"), 6U);
ASSERT_EQ(strcspn16(u"", u"abcdefef"), 0U);
ASSERT_EQ(strcspn16(u"", u""), 0U);
ASSERT_EQ(strcspn16(u"foobar", u""), 6U);
ASSERT_EQ(strcspn16(u"foffffffffffoobar", u"fofo"), 0U);
ASSERT_EQ(strcspn16(u"foffffffffffoobar", u"a"), 15U);
}
DEFINE_TEST_MAIN(LOG_INFO);
+30
View File
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "device-path-util.h"
#include "efi-string.h"
#include "proto/device-path.h"
#include "url-discovery.h"
char16_t *disk_get_url(EFI_HANDLE *handle) {
EFI_STATUS err;
EFI_DEVICE_PATH *dp;
/* export the device path this image is started from */
if (!handle)
return NULL;
err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp);
if (err != EFI_SUCCESS)
return NULL;
for (; !device_path_is_end(dp); dp = device_path_next_node(dp)) {
if (dp->Type != MESSAGING_DEVICE_PATH || dp->SubType != MSG_URI_DP)
continue;
URI_DEVICE_PATH *udp = (URI_DEVICE_PATH*) dp;
return xstrn8_to_16(udp->Uri, dp->Length);
}
return NULL;
}
+4
View File
@@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
char16_t *disk_get_url(EFI_HANDLE *handle);
+39 -1
View File
@@ -1,13 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "device-path-util.h"
#include "efi-string.h"
#include "efivars.h"
#include "memory-util-fundamental.h"
#include "proto/device-path.h"
#include "proto/simple-text-io.h"
#include "ticks.h"
#include "util.h"
#include "version.h"
#include "efivars.h"
/* Never try to read more than 16G into memory (and on 32bit 1G) */
#define FILE_READ_MAX MIN(SIZE_MAX/4, UINT64_C(16)*1024U*1024U*1024U)
@@ -511,3 +512,40 @@ bool free_and_xstrdup16(char16_t **p, const char16_t *s) {
*p = t;
return true;
}
char16_t *url_replace_last_component(const char16_t *url, const char16_t *filename) {
assert(url);
assert(filename);
/* Find colon separating protocol and hostname */
const char16_t *d = strchr16(url, ':');
if (!d || url == d)
return NULL;
d++;
/* Skip slashes after colon */
d += strspn(d, u"/");
/* Skip everything till next slash or end (i.e. the hostname) */
size_t n = strcspn(d, u"/?#");
if (n == 0)
return NULL;
d += n;
const char16_t *e = d + strcspn(d, u"?#"); /* Cut off "Query" and "Fragment" */
while (e > d && e[-1] == '/') /* Eat trailing slashes */
e--;
const char16_t *p = e;
while (p > d && p[-1] != '/') /* Find component before that */
p--;
if (e <= p)
return NULL;
_cleanup_free_ char16_t *chopped = xstrndup16(url, p - url);
return xasprintf("%ls/%ls", chopped, filename);
}
+2
View File
@@ -251,3 +251,5 @@ char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path);
#define xnew0(type, n) ASSERT_PTR(new0(type, n))
#endif
char16_t *url_replace_last_component(const char16_t *url, const char16_t *filename);
+21 -2
View File
@@ -390,12 +390,17 @@ int verb_status(int argc, char *argv[], void *userdata) {
{ EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" },
{ EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" },
{ EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" },
{ EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" },
{ EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" },
{ EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" },
};
static const struct {
uint64_t flag;
const char *name;
} stub_flags[] = {
{ EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets loader partition information" },
{ EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub reports loader partition information" },
{ EFI_STUB_FEATURE_REPORT_STUB_PARTITION, "Stub reports stub partition information" },
{ EFI_STUB_FEATURE_REPORT_URL, "Stub reports network boot URL" },
{ EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" },
{ EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" },
{ EFI_STUB_FEATURE_PICK_UP_CONFEXTS, "Picks up configuration extension images from boot partition" },
@@ -405,7 +410,6 @@ int verb_status(int argc, char *argv[], void *userdata) {
{ EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" },
{ EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" },
{ EFI_STUB_FEATURE_MULTI_PROFILE_UKI, "Stub understands profile selector" },
{ EFI_STUB_FEATURE_REPORT_STUB_PARTITION, "Stub sets stub partition information" },
};
_cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL,
*current_entry = NULL, *oneshot_entry = NULL, *default_entry = NULL;
@@ -480,6 +484,10 @@ int verb_status(int argc, char *argv[], void *userdata) {
(void) efi_loader_get_device_part_uuid(&loader_partition_uuid);
print_yes_no_line(/* first= */ false, !sd_id128_is_null(loader_partition_uuid), "Boot loader set partition information");
_cleanup_free_ char *loader_url = NULL;
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderDeviceURL"), &loader_url);
print_yes_no_line(/* first= */ false, !!loader_url, "Boot loader set network boot URL information");
if (!sd_id128_is_null(loader_partition_uuid)) {
if (!sd_id128_is_null(esp_uuid) && !sd_id128_equal(esp_uuid, loader_partition_uuid))
printf("WARNING: The boot loader reports a different partition UUID than the detected ESP ("SD_ID128_UUID_FORMAT_STR" vs. "SD_ID128_UUID_FORMAT_STR")!\n",
@@ -493,6 +501,9 @@ int verb_status(int argc, char *argv[], void *userdata) {
if (loader_path)
printf(" Loader: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path));
if (loader_url)
printf(" Net Boot URL: %s\n", loader_url);
if (current_entry)
printf("Current Entry: %s\n", current_entry);
if (default_entry)
@@ -513,6 +524,10 @@ int verb_status(int argc, char *argv[], void *userdata) {
(void) efi_stub_get_device_part_uuid(&stub_partition_uuid);
print_yes_no_line(/* first= */ false, !sd_id128_is_null(stub_partition_uuid), "Stub loader set partition information");
_cleanup_free_ char *stub_url = NULL;
(void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("StubDeviceURL"), &stub_url);
print_yes_no_line(/* first= */ false, !!stub_url, "Stub set network boot URL information");
if (!sd_id128_is_null(stub_partition_uuid)) {
if (!(!sd_id128_is_null(esp_uuid) && sd_id128_equal(esp_uuid, stub_partition_uuid)) &&
!(!sd_id128_is_null(xbootldr_uuid) && sd_id128_equal(xbootldr_uuid, stub_partition_uuid)))
@@ -526,6 +541,10 @@ int verb_status(int argc, char *argv[], void *userdata) {
if (stub_path)
printf(" Stub: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(stub_path));
if (stub_url)
printf(" Net Boot URL: %s\n", stub_url);
printf("\n");
}
+31 -4
View File
@@ -94,12 +94,14 @@ static bool arg_rmdir = false;
static bool arg_in_memory = false;
static char **arg_argv = NULL;
static char *arg_loop_ref = NULL;
static bool arg_loop_ref_auto = false;
static ImagePolicy *arg_image_policy = NULL;
static bool arg_mtree_hash = true;
static bool arg_via_service = false;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
static bool arg_all = false;
static uid_t arg_uid_base = UID_INVALID;
static bool arg_quiet = false;
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
@@ -156,6 +158,7 @@ static int help(void) {
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --loop-ref=NAME Set reference string for loopback device\n"
" --loop-ref-auto Derive reference string from image file name\n"
" --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n"
" --user Discover user images\n"
" --system Discover system images\n"
@@ -179,6 +182,7 @@ static int help(void) {
" --discover Discover DDIs in well known directories\n"
" --validate Validate image and image policy\n"
" --shift Shift UID range to selected base\n"
" -q --quiet Suppress output of chosen loopback block device\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@@ -280,6 +284,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ATTACH,
ARG_DETACH,
ARG_LOOP_REF,
ARG_LOOP_REF_AUTO,
ARG_IMAGE_POLICY,
ARG_VALIDATE,
ARG_MTREE_HASH,
@@ -317,6 +322,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "json", required_argument, NULL, ARG_JSON },
{ "discover", no_argument, NULL, ARG_DISCOVER },
{ "loop-ref", required_argument, NULL, ARG_LOOP_REF },
{ "loop-ref-auto", no_argument, NULL, ARG_LOOP_REF_AUTO },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "validate", no_argument, NULL, ARG_VALIDATE },
{ "mtree-hash", required_argument, NULL, ARG_MTREE_HASH },
@@ -325,6 +331,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{ "all", no_argument, NULL, ARG_ALL },
{ "quiet", no_argument, NULL, 'q' },
{}
};
@@ -339,7 +346,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
while ((c = getopt_long(argc, argv, "hmurMUlxa", options, NULL)) >= 0) {
while ((c = getopt_long(argc, argv, "hmurMUlxaq", options, NULL)) >= 0) {
switch (c) {
@@ -522,6 +529,7 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_LOOP_REF:
if (isempty(optarg)) {
arg_loop_ref = mfree(arg_loop_ref);
arg_loop_ref_auto = false;
break;
}
@@ -531,6 +539,13 @@ static int parse_argv(int argc, char *argv[]) {
r = free_and_strdup_warn(&arg_loop_ref, optarg);
if (r < 0)
return r;
arg_loop_ref_auto = false;
break;
case ARG_LOOP_REF_AUTO:
arg_loop_ref = mfree(arg_loop_ref);
arg_loop_ref_auto = true;
break;
case ARG_IMAGE_POLICY:
@@ -573,6 +588,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_all = true;
break;
case 'q':
arg_quiet = true;
break;
case '?':
return -EINVAL;
@@ -1979,7 +1998,9 @@ static int action_attach(DissectedImage *m, LoopDevice *d) {
if (r < 0)
return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
puts(d->node);
if (!arg_quiet)
puts(d->node);
return 0;
}
@@ -2149,6 +2170,12 @@ static int run(int argc, char *argv[]) {
* support */
}
if (!arg_loop_ref && arg_loop_ref_auto) {
r = path_extract_filename(arg_image, &arg_loop_ref);
if (r < 0)
return log_error_errno(r, "Failed to extract file name from image path '%s': %m", arg_image);
}
if (arg_action == ACTION_VALIDATE)
return action_validate();
@@ -2214,8 +2241,8 @@ static int run(int argc, char *argv[]) {
if (arg_in_memory)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--in-memory= not supported when operating via systemd-mountfsd.");
if (arg_loop_ref)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--loop-ref= not supported when operating via systemd-mountfsd.");
if (arg_loop_ref || arg_loop_ref_auto) /* yes, the 2nd check is strictly speaking redundant, given the normalization we did above, but let's be explicit here */
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--loop-ref=/--loop-ref-auto not supported when operating via systemd-mountfsd.");
if (verity_settings_set(&arg_verity_settings))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Externally configured verity settings not supported when operating via systemd-mountfsd.");
+151 -117
View File
@@ -494,7 +494,8 @@ static int add_mount(
const char *opts,
int passno,
MountPointFlags flags,
const char *target_unit) {
const char *target_unit,
const char *extra_after) {
_cleanup_free_ char *name = NULL, *automount_name = NULL, *filtered = NULL, *where_escaped = NULL,
*opts_root_filtered = NULL;
@@ -596,6 +597,9 @@ static int add_mount(
if (target_unit && !FLAGS_SET(flags, MOUNT_NOFAIL))
fprintf(f, "Before=%s\n", target_unit);
if (extra_after)
fprintf(f, "After=%s\n", extra_after);
if (passno != 0) {
r = generator_write_fsck_deps(f, dest, what, where, fstype);
if (r < 0)
@@ -814,15 +818,16 @@ static bool sysfs_check(void) {
static int add_sysusr_sysroot_usr_bind_mount(const char *source) {
return add_mount(source,
arg_dest,
"/sysusr/usr",
"/sysroot/usr",
NULL,
NULL,
"bind",
0,
0,
SPECIAL_INITRD_FS_TARGET);
arg_dest,
"/sysusr/usr",
"/sysroot/usr",
/* original_where= */ NULL,
/* fstype= */ NULL,
"bind",
/* passno= */ 0,
/* flags= */ 0,
SPECIAL_INITRD_FS_TARGET,
/* extra_after= */ NULL);
}
static MountPointFlags fstab_options_to_flags(const char *options, bool is_swap) {
@@ -1000,7 +1005,8 @@ static int parse_fstab_one(
options,
passno,
flags,
target_unit);
target_unit,
/* extra_after= */ NULL);
if (r <= 0)
return r;
@@ -1052,12 +1058,12 @@ static int parse_fstab(bool prefix_sysroot) {
return ret;
}
static int sysroot_is_nfsroot(void) {
static int mount_source_is_nfsroot(const char *what) {
union in_addr_union u;
const char *sep, *a;
int r;
assert(arg_root_what);
assert(what);
/* From dracut.cmdline(7).
*
@@ -1070,18 +1076,18 @@ static int sysroot_is_nfsroot(void) {
* If server-ip is an IPv6 address it has to be put in brackets, e.g. [2001:DB8::1]. NFS options
* can be appended with the prefix ":" or "," and are separated by ",". */
if (path_equal(arg_root_what, "/dev/nfs") ||
STR_IN_SET(arg_root_what, "dhcp", "dhcp6") ||
STARTSWITH_SET(arg_root_what, "nfs:", "nfs4:"))
if (path_equal(what, "/dev/nfs") ||
STR_IN_SET(what, "dhcp", "dhcp6") ||
STARTSWITH_SET(what, "nfs:", "nfs4:"))
return true;
/* IPv6 address */
if (arg_root_what[0] == '[') {
sep = strchr(arg_root_what + 1, ']');
if (what[0] == '[') {
sep = strchr(what + 1, ']');
if (!sep)
return -EINVAL;
a = strndupa_safe(arg_root_what + 1, sep - arg_root_what - 1);
a = strndupa_safe(what + 1, sep - what - 1);
r = in_addr_from_string(AF_INET6, a, &u);
if (r < 0)
@@ -1091,65 +1097,90 @@ static int sysroot_is_nfsroot(void) {
}
/* IPv4 address */
sep = strchr(arg_root_what, ':');
sep = strchr(what, ':');
if (sep) {
a = strndupa_safe(arg_root_what, sep - arg_root_what);
a = strndupa_safe(what, sep - what);
if (in_addr_from_string(AF_INET, a, &u) >= 0)
return true;
}
/* root directory without address */
return path_is_absolute(arg_root_what) && !path_startswith(arg_root_what, "/dev");
return path_is_absolute(what) && !path_startswith(what, "/dev");
}
static bool validate_root_or_usr_mount_source(const char *what, const char *switch_name) {
int r;
assert(switch_name);
if (isempty(what)) {
log_debug("No %s switch specified on the kernel command line.", switch_name);
return false;
}
if (streq(what, "gpt-auto")) {
/* This is handled by gpt-auto-generator */
log_debug("Skipping %s directory handling, as gpt-auto was requested.", switch_name);
return false;
}
if (streq(what, "fstab")) {
/* This is handled by parse_fstab() */
log_debug("Using initrd's fstab for %s configuration.", switch_name);
return false;
}
r = mount_source_is_nfsroot(what);
if (r < 0)
log_debug_errno(r, "Failed to determine if the %s directory is on NFS, assuming not: %m", switch_name);
else if (r > 0) {
/* This is handled by the kernel or the initrd */
log_debug("Skipping %s directory handling, as root on NFS was requested.", switch_name);
return false;
}
if (startswith(what, "cifs://")) {
log_debug("Skipping %s directory handling, as root on CIFS was requested.", switch_name);
return false;
}
if (startswith(what, "iscsi:")) {
log_debug("Skipping %s directory handling, as root on iSCSI was requested.", switch_name);
return false;
}
if (startswith(what, "live:")) {
log_debug("Skipping %s directory handling, as root on live image was requested.", switch_name);
return false;
}
return true;
}
static int add_sysroot_mount(void) {
_cleanup_free_ char *what = NULL;
const char *opts, *fstype;
bool default_rw, makefs;
const char *extra_opts = NULL, *fstype = NULL;
bool default_rw = true, makefs = false;
MountPointFlags flags;
int r;
if (isempty(arg_root_what)) {
log_debug("Could not find a root= entry on the kernel command line.");
if (!validate_root_or_usr_mount_source(arg_root_what, "root="))
return 0;
}
if (streq(arg_root_what, "gpt-auto")) {
/* This is handled by gpt-auto-generator */
log_debug("Skipping root directory handling, as gpt-auto was requested.");
return 0;
} else if (streq(arg_root_what, "fstab")) {
/* This is handled by parse_fstab */
log_debug("Using initrd's fstab for /sysroot/ configuration.");
return 0;
}
const char *bind = startswith(arg_root_what, "bind:");
if (bind) {
if (!path_is_valid(bind) || !path_is_absolute(bind)) {
log_debug("Invalid root=bind: source path, ignoring: %s", bind);
return 0;
}
r = sysroot_is_nfsroot();
if (r < 0)
log_debug_errno(r, "Failed to determine if the root directory is on NFS, assuming not: %m");
else if (r > 0) {
/* This is handled by the kernel or the initrd */
log_debug("Skipping root directory handling, as root on NFS was requested.");
return 0;
}
what = strdup(bind);
if (!what)
return log_oom();
if (startswith(arg_root_what, "cifs://")) {
log_debug("Skipping root directory handling, as root on CIFS was requested.");
return 0;
}
extra_opts = "bind";
if (startswith(arg_root_what, "iscsi:")) {
log_debug("Skipping root directory handling, as root on iSCSI was requested.");
return 0;
}
if (startswith(arg_root_what, "live:")) {
log_debug("Skipping root directory handling, as root on live image was requested.");
return 0;
}
if (streq(arg_root_what, "tmpfs")) {
} else if (streq(arg_root_what, "tmpfs")) {
/* If root=tmpfs is specified, then take this as shortcut for a writable tmpfs mount as root */
what = strdup("rootfs"); /* just a pretty name, to show up in /proc/self/mountinfo */
@@ -1157,8 +1188,6 @@ static int add_sysroot_mount(void) {
return log_oom();
fstype = arg_root_fstype ?: "tmpfs"; /* tmpfs, unless overridden */
default_rw = true; /* writable, unless overridden */;
} else {
what = fstab_node_to_udev_node(arg_root_what);
@@ -1166,40 +1195,44 @@ static int add_sysroot_mount(void) {
return log_oom();
fstype = arg_root_fstype; /* if not specified explicitly, don't default to anything here */
default_rw = false; /* read-only, unless overridden */
}
if (!arg_root_options)
opts = arg_root_rw > 0 || (arg_root_rw < 0 && default_rw) ? "rw" : "ro";
else if (arg_root_rw >= 0 ||
!fstab_test_option(arg_root_options, "ro\0" "rw\0"))
opts = strjoina(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro");
else
opts = arg_root_options;
_cleanup_free_ char *combined_options = NULL;
if (strdup_to(&combined_options, arg_root_options) < 0)
return log_oom();
log_debug("Found entry what=%s where=/sysroot type=%s opts=%s", what, strna(arg_root_fstype), strempty(opts));
if (arg_root_rw >= 0 || !fstab_test_option(combined_options, "ro\0" "rw\0"))
if (!strextend_with_separator(&combined_options, ",", arg_root_rw > 0 || (arg_root_rw < 0 && default_rw) ? "rw" : "ro"))
return log_oom();
makefs = fstab_test_option(opts, "x-systemd.makefs\0");
if (extra_opts)
if (!strextend_with_separator(&combined_options, ",", extra_opts))
return log_oom();
log_debug("Found entry what=%s where=/sysroot type=%s opts=%s", what, strna(arg_root_fstype), strempty(combined_options));
makefs = fstab_test_option(combined_options, "x-systemd.makefs\0");
flags = makefs * MOUNT_MAKEFS;
return add_mount("/proc/cmdline",
arg_dest,
what,
"/sysroot",
NULL,
/* original_where= */ NULL,
fstype,
opts,
is_device_path(what) ? 1 : 0, /* passno */
flags, /* makefs off, pcrfs off, quota off, noauto off, nofail off, automount off */
SPECIAL_INITRD_ROOT_FS_TARGET);
combined_options,
/* passno= */ is_device_path(what) ? 1 : 0,
flags,
SPECIAL_INITRD_ROOT_FS_TARGET,
"imports.target");
}
static int add_sysroot_usr_mount(void) {
_cleanup_free_ char *what = NULL;
const char *opts;
bool makefs;
const char *extra_opts = NULL;
MountPointFlags flags;
bool makefs;
int r;
/* Returns 0 if we didn't do anything, > 0 if we either generated a unit for the /usr/ mount, or we
@@ -1227,39 +1260,38 @@ static int add_sysroot_usr_mount(void) {
return log_oom();
}
if (isempty(arg_usr_what)) {
log_debug("Could not find a mount.usr= entry on the kernel command line.");
if (!validate_root_or_usr_mount_source(arg_usr_what, "mount.usr="))
return 0;
const char *bind = startswith(arg_usr_what, "bind:");
if (bind) {
if (!path_is_valid(bind) || !path_is_absolute(bind)) {
log_debug("Invalid mount.usr=bind: source path, ignoring: %s", bind);
return 0;
}
what = strdup(bind);
if (!what)
return log_oom();
extra_opts = "bind";
} else {
what = fstab_node_to_udev_node(arg_usr_what);
if (!what)
return log_oom();
}
if (streq(arg_usr_what, "gpt-auto")) {
/* This is handled by the gpt-auto generator */
log_debug("Skipping /usr/ directory handling, as gpt-auto was requested.");
return 1; /* systemd-gpt-auto-generator will generate a unit for this, hence report that a
* unit file is being created for the host /usr/ mount. */
} else if (streq(arg_usr_what, "fstab")) {
/* This is handled by parse_fstab */
log_debug("Using initrd's fstab for /sysroot/usr/ configuration.");
return 1; /* parse_fstab will generate a unit for this, hence report that a
* unit file is being created for the host /usr/ mount. */
}
if (path_equal(arg_usr_what, "/dev/nfs")) {
/* This is handled by the initrd (if at all supported, that is) */
log_debug("Skipping /usr/ directory handling, as /dev/nfs was requested.");
return 1; /* As above, report that NFS code will create the unit */
}
what = fstab_node_to_udev_node(arg_usr_what);
if (!what)
_cleanup_free_ char *combined_options = NULL;
if (strdup_to(&combined_options, arg_usr_options) < 0)
return log_oom();
if (!arg_usr_options)
opts = arg_root_rw > 0 ? "rw" : "ro";
else if (!fstab_test_option(arg_usr_options, "ro\0" "rw\0"))
opts = strjoina(arg_usr_options, ",", arg_root_rw > 0 ? "rw" : "ro");
else
opts = arg_usr_options;
if (!fstab_test_option(combined_options, "ro\0" "rw\0"))
if (!strextend_with_separator(&combined_options, ",", arg_root_rw > 0 ? "rw" : "ro"))
return log_oom();
if (extra_opts)
if (!strextend_with_separator(&combined_options, ",", extra_opts))
return log_oom();
/* When mounting /usr from the initrd, we add an extra level of indirection: we first mount the /usr/
* partition to /sysusr/usr/, and then afterwards bind mount that to /sysroot/usr/. We do this so
@@ -1268,21 +1300,22 @@ static int add_sysroot_usr_mount(void) {
* this should order itself after initrd-usr-fs.target and before initrd-fs.target; and it should
* look into both /sysusr/ and /sysroot/ for the configuration data to apply. */
log_debug("Found entry what=%s where=/sysusr/usr type=%s opts=%s", what, strna(arg_usr_fstype), strempty(opts));
log_debug("Found entry what=%s where=/sysusr/usr type=%s opts=%s", what, strna(arg_usr_fstype), strempty(combined_options));
makefs = fstab_test_option(opts, "x-systemd.makefs\0");
makefs = fstab_test_option(combined_options, "x-systemd.makefs\0");
flags = makefs * MOUNT_MAKEFS;
r = add_mount("/proc/cmdline",
arg_dest,
what,
"/sysusr/usr",
NULL,
/* original_where= */ NULL,
arg_usr_fstype,
opts,
is_device_path(what) ? 1 : 0, /* passno */
combined_options,
/* passno= */ is_device_path(what) ? 1 : 0,
flags,
SPECIAL_INITRD_USR_FS_TARGET);
SPECIAL_INITRD_USR_FS_TARGET,
"imports.target");
if (r < 0)
return r;
@@ -1336,12 +1369,13 @@ static int add_volatile_var(void) {
arg_dest_late,
"tmpfs",
"/var",
NULL,
/* original_where= */ NULL,
"tmpfs",
"mode=0755" TMPFS_LIMITS_VAR,
0,
0,
SPECIAL_LOCAL_FS_TARGET);
/* passno= */ 0,
/* flags= */ 0,
SPECIAL_LOCAL_FS_TARGET,
/* extra_after= */ NULL);
}
static int add_mounts_from_cmdline(void) {
+4
View File
@@ -24,6 +24,9 @@
#define EFI_LOADER_FEATURE_RETAIN_SHIM (UINT64_C(1) << 12)
#define EFI_LOADER_FEATURE_MENU_DISABLE (UINT64_C(1) << 13)
#define EFI_LOADER_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 14)
#define EFI_LOADER_FEATURE_REPORT_URL (UINT64_C(1) << 15)
#define EFI_LOADER_FEATURE_TYPE1_UKI (UINT64_C(1) << 16)
#define EFI_LOADER_FEATURE_TYPE1_UKI_URL (UINT64_C(1) << 17)
/* Features of the stub, i.e. systemd-stub */
#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0)
@@ -37,6 +40,7 @@
#define EFI_STUB_FEATURE_PICK_UP_CONFEXTS (UINT64_C(1) << 8)
#define EFI_STUB_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 9)
#define EFI_STUB_FEATURE_REPORT_STUB_PARTITION (UINT64_C(1) << 10)
#define EFI_STUB_FEATURE_REPORT_URL (UINT64_C(1) << 11)
typedef enum SecureBootMode {
SECURE_BOOT_UNSUPPORTED,
+2
View File
@@ -8,7 +8,9 @@ struct iovec {
size_t iov_len;
};
DISABLE_WARNING_REDUNDANT_DECLS;
static inline void free(void *p);
REENABLE_WARNING;
#endif
/* This accepts both const and non-const pointers */
+4
View File
@@ -52,6 +52,10 @@
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"")
#define DISABLE_WARNING_REDUNDANT_DECLS \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wredundant-decls\"")
#if HAVE_WARNING_ZERO_LENGTH_BOUNDS
# define DISABLE_WARNING_ZERO_LENGTH_BOUNDS \
_Pragma("GCC diagnostic push"); \
@@ -16,6 +16,8 @@
# define strncmp strncmp16
# define strcasecmp strcasecmp16
# define strncasecmp strncasecmp16
# define strspn strspn16
# define strcspn strcspn16
# define STR_C(str) (L ## str)
typedef char16_t sd_char;
#else
+28 -19
View File
@@ -39,7 +39,7 @@
static const char *arg_dest = NULL;
static bool arg_enabled = true;
static bool arg_root_enabled = true;
static int arg_root_enabled = -1; /* tristate */
static bool arg_swap_enabled = true;
static char *arg_root_fstype = NULL;
static char *arg_root_options = NULL;
@@ -673,21 +673,29 @@ static int add_root_mount(void) {
_cleanup_free_ char *options = NULL;
int r;
if (!is_efi_boot()) {
log_debug("Not an EFI boot, not creating root mount.");
/* Explicitly disabled? Then exit immediately */
if (arg_root_enabled == 0)
return 0;
/* Neither explicitly enabled nor disabled? Then decide based on the EFI partition variables to be set */
if (arg_root_enabled < 0) {
if (!is_efi_boot()) {
log_debug("Not an EFI boot, not creating root mount.");
return 0;
}
r = efi_loader_get_device_part_uuid(/* ret_uuid= */ NULL);
if (r == -ENOENT) {
log_notice("EFI loader partition unknown, exiting.\n"
"(The boot loader did not set EFI variable LoaderDevicePartUUID.)");
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to read loader partition UUID: %m");
}
r = efi_loader_get_device_part_uuid(NULL);
if (r == -ENOENT) {
log_notice("EFI loader partition unknown, exiting.\n"
"(The boot loader did not set EFI variable LoaderDevicePartUUID.)");
return 0;
} else if (r < 0)
return log_error_errno(r, "Failed to read loader partition UUID: %m");
/* OK, we have an ESP/XBOOTLDR partition, this is fantastic, so let's wait for a root device to show up.
* A udev rule will create the link for us under the right name. */
/* OK, we shall look for a root device, so let's wait for a root device to show up. A udev rule will
* create the link for us under the right name. */
if (in_initrd()) {
r = generator_write_initrd_root_device_deps(arg_dest, "/dev/gpt-auto-root");
@@ -904,9 +912,12 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
/* Disable root disk logic if there's a root= value
* specified (unless it happens to be "gpt-auto") */
if (!streq(value, "gpt-auto")) {
if (streq(value, "gpt-auto")) {
arg_root_enabled = true;
log_debug("Enabling root partition auto-detection, root= is explicitly set to 'gpt_auto'.");
} else {
arg_root_enabled = false;
log_debug("Disabling root partition auto-detection, root= is defined.");
log_debug("Disabling root partition auto-detection, root= is neither unset, nor set to 'gpt-auto'.");
}
} else if (streq(key, "roothash")) {
@@ -917,6 +928,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
/* Disable root disk logic if there's roothash= defined (i.e. verity enabled) */
arg_root_enabled = false;
log_debug("Disabling root partition auto-detection, roothash= is set.");
} else if (streq(key, "rootfstype")) {
@@ -975,10 +987,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
return 0;
}
if (arg_root_enabled)
r = add_root_mount();
else
r = 0;
r = add_root_mount();
if (!in_initrd())
RET_GATHER(r, add_mounts());
+168 -28
View File
@@ -4,24 +4,49 @@
#include "creds-util.h"
#include "discover-image.h"
#include "efivars.h"
#include "fd-util.h"
#include "fileio.h"
#include "generator.h"
#include "import-util.h"
#include "initrd-util.h"
#include "json-util.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "special.h"
#include "specifier.h"
#include "unit-name.h"
#include "web-util.h"
typedef struct Transfer {
ImageClass class;
ImportType type;
char *local;
char *remote;
const char *image_root;
bool blockdev;
sd_json_variant *json;
} Transfer;
static const char *arg_dest = NULL;
static char *arg_success_action = NULL;
static char *arg_failure_action = NULL;
static sd_json_variant **arg_transfers = NULL;
static Transfer *arg_transfers = NULL;
static size_t arg_n_transfers = 0;
static void transfer_destroy_many(Transfer *transfers, size_t n) {
FOREACH_ARRAY(t, transfers, n) {
free(t->local);
free(t->remote);
sd_json_variant_unref(t->json);
}
free(transfers);
}
STATIC_DESTRUCTOR_REGISTER(arg_success_action, freep);
STATIC_DESTRUCTOR_REGISTER(arg_failure_action, freep);
STATIC_ARRAY_DESTRUCTOR_REGISTER(arg_transfers, arg_n_transfers, sd_json_variant_unref_many);
STATIC_ARRAY_DESTRUCTOR_REGISTER(arg_transfers, arg_n_transfers, transfer_destroy_many);
static int parse_pull_expression(const char *v) {
const char *p = v;
@@ -43,8 +68,6 @@ static int parse_pull_expression(const char *v) {
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No local string in pull expression '%s': %m", v);
if (!http_url_is_valid(p) && !file_url_is_valid(p))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid URL, refusing: %s", p);
_cleanup_free_ char *remote = strdup(p);
if (!remote)
return log_oom();
@@ -57,7 +80,7 @@ static int parse_pull_expression(const char *v) {
ImportType type = _IMPORT_TYPE_INVALID;
ImageClass class = _IMAGE_CLASS_INVALID;
ImportVerify verify = IMPORT_VERIFY_SIGNATURE;
bool ro = false;
bool ro = false, blockdev = false, bootorigin = false, runtime = in_initrd();
const char *o = options;
for (;;) {
@@ -75,8 +98,17 @@ static int parse_pull_expression(const char *v) {
ro = true;
else if (streq(opt, "rw"))
ro = false;
else if ((suffix = startswith(opt, "verify="))) {
else if (streq(opt, "blockdev"))
blockdev = true;
else if (streq(opt, "bootorigin"))
bootorigin = true;
else if ((suffix = startswith(opt, "runtime="))) {
r = parse_boolean(suffix);
if (r < 0)
log_warning_errno(r, "Unknown runtime= parameter, ignoring: %s", suffix);
else
runtime = r;
} else if ((suffix = startswith(opt, "verify="))) {
ImportVerify w = import_verify_from_string(suffix);
if (w < 0)
@@ -100,28 +132,94 @@ static int parse_pull_expression(const char *v) {
}
}
if (bootorigin) {
_cleanup_free_ char *stub_url = NULL;
r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("StubDeviceURL"), &stub_url);
if (r == -ENOENT) {
log_debug("Option 'bootorigin' specified, but StubDeviceURL EFI variable not set, not scheduling import job for '%s'.", remote);
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to read 'StubDeviceURL' EFI variable: %m");
if (!http_url_is_valid(stub_url))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Value of 'StubDeviceURL' is not a valid URL, refusing: %s", stub_url);
_cleanup_free_ char *result = NULL;
r = import_url_change_last_component(stub_url, remote, &result);
if (r < 0)
return log_error_errno(r, "Failed to replace last component of URL '%s': %m", stub_url);
log_info("URL reported by StubDeviceURL is '%s', derived download URL '%s' from it.", stub_url, result);
free_and_replace(remote, result);
}
if (!http_url_is_valid(remote) && !file_url_is_valid(remote))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid URL, refusing: %s", remote);
if (type < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No image type (raw, tar) specified in pull expression, refusing: %s", v);
if (class < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No image class (machine, portable, sysext, confext) specified in pull expression, refusing: %s", v);
if (!local) {
_cleanup_free_ char *c = NULL;
r = import_url_last_component(remote, &c);
if (r < 0)
return log_error_errno(r, "Failed to generate local name from URL '%s': %m", remote);
switch (type) {
case IMPORT_RAW:
r = raw_strip_suffixes(c, &local);
break;
case IMPORT_TAR:
r = tar_strip_suffixes(c, &local);
break;
default:
assert_not_reached();
break;
}
if (r < 0)
return log_error_errno(r, "Failed to strip suffix from URL '%s': %m", remote);
log_info("Saving downloaded file under local name '%s'.", local);
}
if (blockdev && type != IMPORT_RAW)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option 'blockdev' only available for raw images, refusing: %s", v);
if (!GREEDY_REALLOC(arg_transfers, arg_n_transfers + 1))
return log_oom();
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
const char *image_root = runtime ? image_root_runtime_to_string(class) : image_root_to_string(class);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
r = sd_json_buildo(
&j,
SD_JSON_BUILD_PAIR("remote", SD_JSON_BUILD_STRING(remote)),
SD_JSON_BUILD_PAIR_CONDITION(!!local, "local", SD_JSON_BUILD_STRING(local)),
SD_JSON_BUILD_PAIR("local", SD_JSON_BUILD_STRING(local)),
SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(class))),
SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(import_type_to_string(type))),
SD_JSON_BUILD_PAIR("readOnly", SD_JSON_BUILD_BOOLEAN(ro)),
SD_JSON_BUILD_PAIR("verify", JSON_BUILD_STRING_UNDERSCORIFY(import_verify_to_string(verify))));
SD_JSON_BUILD_PAIR("verify", JSON_BUILD_STRING_UNDERSCORIFY(import_verify_to_string(verify))),
SD_JSON_BUILD_PAIR("imageRoot", SD_JSON_BUILD_STRING(image_root)));
if (r < 0)
return log_error_errno(r, "Failed to build import JSON object: %m");
arg_transfers[arg_n_transfers++] = TAKE_PTR(j);
arg_transfers[arg_n_transfers++] = (Transfer) {
.class = class,
.type = type,
.local = TAKE_PTR(local),
.remote = TAKE_PTR(remote),
.image_root = image_root,
.json = TAKE_PTR(j),
.blockdev = blockdev,
};
return 0;
}
@@ -191,33 +289,55 @@ static int parse_credentials(void) {
return 0;
}
static int transfer_generate(sd_json_variant *v, size_t c) {
static char *transfer_get_local_path(const Transfer *t) {
assert(t);
assert(t->image_root);
assert(t->local);
switch (t->type) {
case IMPORT_RAW:
return strjoin(t->image_root, "/", t->local, ".raw");
case IMPORT_TAR:
return path_join(t->image_root, t->local);
default:
assert_not_reached();
}
}
static int transfer_generate(const Transfer *t) {
int r;
assert(v);
assert(t);
_cleanup_free_ char *service = NULL;
if (asprintf(&service, "import%zu.service", c) < 0)
_cleanup_free_ char *local_path = transfer_get_local_path(t);
if (!local_path)
return log_oom();
/* Give this unit a clear name derived from the file system object we are installed into the OS, so
* that other components can nicely have dependencies on this. */
_cleanup_free_ char *service = NULL;
r = unit_name_from_path_instance("systemd-import", local_path, ".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to build import unit name from '%s': %m", local_path);
_cleanup_fclose_ FILE *f = NULL;
r = generator_open_unit_file(arg_dest, /* source = */ NULL, service, &f);
if (r < 0)
return r;
const char *remote = sd_json_variant_string(sd_json_variant_by_key(v, "remote"));
fprintf(f,
"[Unit]\n"
"Description=Download of %s\n"
"Documentation=man:systemd-import-generator(8)\n"
"SourcePath=/proc/cmdline\n"
"Requires=systemd-importd.socket\n"
"After=systemd-importd.socket\n"
"After=imports-pre.target systemd-importd.socket\n"
"Conflicts=shutdown.target\n"
"Before=shutdown.target\n"
"Before=imports.target shutdown.target\n"
"DefaultDependencies=no\n",
remote);
t->remote);
if (arg_success_action)
fprintf(f, "SuccessAction=%s\n",
@@ -227,24 +347,35 @@ static int transfer_generate(sd_json_variant *v, size_t c) {
fprintf(f, "FailureAction=%s\n",
arg_failure_action);
const char *class = sd_json_variant_string(sd_json_variant_by_key(v, "class"));
if (streq_ptr(class, "sysext"))
if (t->class == IMAGE_SYSEXT)
fputs("Before=systemd-sysext.service\n", f);
else if (streq_ptr(class, "confext"))
else if (t->class == IMAGE_CONFEXT)
fputs("Before=systemd-confext.service\n", f);
/* Assume network resource unless URL is file:// */
if (!file_url_is_valid(remote))
if (!file_url_is_valid(t->remote))
fputs("Wants=network-online.target\n"
"After=network-online.target\n", f);
_cleanup_free_ char *loop_service = NULL;
if (t->blockdev) {
assert(t->type == IMPORT_RAW);
r = unit_name_from_path_instance("systemd-loop", local_path, ".service", &loop_service);
if (r < 0)
return log_error_errno(r, "Failed to build systemd-loop@.service instance name from path '%s': %m", local_path);
/* Make sure download completes before the loopback service is activated */
fprintf(f, "Before=%s\n", loop_service);
}
fputs("\n"
"[Service]\n"
"Type=oneshot\n"
"NotifyAccess=main\n", f);
_cleanup_free_ char *formatted = NULL;
r = sd_json_variant_format(v, /* flags= */ 0, &formatted);
r = sd_json_variant_format(t->json, /* flags= */ 0, &formatted);
if (r < 0)
return log_error_errno(r, "Failed to format import JSON data: %m");
@@ -259,15 +390,24 @@ static int transfer_generate(sd_json_variant *v, size_t c) {
if (r < 0)
return log_error_errno(r, "Failed to write unit %s: %m", service);
return generator_add_symlink(arg_dest, "multi-user.target", "wants", service);
r = generator_add_symlink(arg_dest, "imports.target", "wants", service);
if (r < 0)
return r;
if (loop_service) {
r = generator_add_symlink(arg_dest, "imports.target", "wants", loop_service);
if (r < 0)
return r;
}
return 0;
}
static int generate(void) {
size_t c = 0;
int r = 0;
FOREACH_ARRAY(i, arg_transfers, arg_n_transfers)
RET_GATHER(r, transfer_generate(*i, c++));
RET_GATHER(r, transfer_generate(i));
return r;
}
+17
View File
@@ -73,6 +73,7 @@ struct Transfer {
char *remote;
char *local;
char *image_root;
ImageClass class;
ImportFlags flags;
char *format;
@@ -146,6 +147,7 @@ static Transfer *transfer_unref(Transfer *t) {
free(t->remote);
free(t->local);
free(t->format);
free(t->image_root);
free(t->object_path);
pidref_done_sigkill_wait(&t->pidref);
@@ -455,6 +457,8 @@ static int transfer_start(Transfer *t) {
NULL, /* if so: the actual URL */
NULL, /* maybe --format= */
NULL, /* if so: the actual format */
NULL, /* maybe --image-root= */
NULL, /* if so: the image root path */
NULL, /* remote */
NULL, /* local */
NULL
@@ -548,6 +552,11 @@ static int transfer_start(Transfer *t) {
cmd[k++] = t->format;
}
if (t->image_root) {
cmd[k++] = "--image-root";
cmd[k++] = t->image_root;
}
if (!IN_SET(t->type, TRANSFER_EXPORT_TAR, TRANSFER_EXPORT_RAW)) {
if (t->remote)
cmd[k++] = t->remote;
@@ -1838,6 +1847,7 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl
bool force;
bool read_only;
bool keep_download;
const char *image_root;
} p = {
.class = _IMAGE_CLASS_INVALID,
.verify = IMPORT_VERIFY_SIGNATURE,
@@ -1852,6 +1862,7 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl
{ "force", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, force), 0 },
{ "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, read_only), 0 },
{ "keepDownload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, keep_download), 0 },
{ "imageRoot", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(struct p, image_root), SD_JSON_STRICT },
VARLINK_DISPATCH_POLKIT_FIELD,
{},
};
@@ -1918,6 +1929,12 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl
return -ENOMEM;
}
if (p.image_root) {
t->image_root = strdup(p.image_root);
if (!t->image_root)
return -ENOMEM;
}
r = transfer_start(t);
if (r < 0)
return r;
+9
View File
@@ -110,6 +110,15 @@ static const char *const image_root_table[_IMAGE_CLASS_MAX] = {
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass);
static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = {
[IMAGE_MACHINE] = "/run/machines",
[IMAGE_PORTABLE] = "/run/portables",
[IMAGE_SYSEXT] = "/run/extensions",
[IMAGE_CONFEXT] = "/run/confexts",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass);
static Image *image_free(Image *i) {
assert(i);
+1
View File
@@ -122,6 +122,7 @@ static inline bool IMAGE_IS_HOST(const struct Image *i) {
int image_to_json(const struct Image *i, sd_json_variant **ret);
const char* image_root_to_string(ImageClass c) _const_;
const char* image_root_runtime_to_string(ImageClass c) _const_;
extern const struct hash_ops image_hash_ops;
+2
View File
@@ -91,6 +91,8 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
SD_VARLINK_DEFINE_INPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether to keep a pristine copy of the download separate from the locally installed image. Defaults to false."),
SD_VARLINK_DEFINE_INPUT(keepDownload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Root directory for images. If not specified derived from the image class, and located below /var/lib/."),
SD_VARLINK_DEFINE_INPUT(imageRoot, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("A progress update, as percent value"),
SD_VARLINK_DEFINE_OUTPUT(progress, SD_VARLINK_FLOAT, SD_VARLINK_NULLABLE),
+52 -25
View File
@@ -125,12 +125,13 @@ static void print_property(UdevEvent *event, const char *name, const char *value
}
}
static int find_gpt_root(UdevEvent *event, blkid_probe pr) {
static int find_gpt_root(UdevEvent *event, blkid_probe pr, const char *loop_backing_fname) {
#if defined(SD_GPT_ROOT_NATIVE) && ENABLE_EFI
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
sd_id128_t esp_or_xbootldr = SD_ID128_NULL;
_cleanup_free_ char *root_label = NULL;
bool found_esp_or_xbootldr = false;
bool found_esp_or_xbootldr = false, need_esp_or_xbootldr;
sd_id128_t root_id = SD_ID128_NULL;
int r;
@@ -138,13 +139,42 @@ static int find_gpt_root(UdevEvent *event, blkid_probe pr) {
assert(pr);
/* Iterate through the partitions on this disk, and see if the UEFI ESP or XBOOTLDR partition we
* booted from is on it. If so, find the first root disk, and add a property indicating its partition
* UUID. */
* booted from is on it. If so, find the newest root partition, and add a property indicating its
* partition UUID. We also do this if we are dealing with a loopback block device whose "backing
* filename" field is set to the string "root". In the latter case we do not search for ESP or
* XBOOTLDR. */
if (!device_is_devtype(dev, "disk")) {
log_device_debug(dev, "Skipping GPT root logic on partition block device.");
return 0;
}
r = efi_loader_get_device_part_uuid(&esp_or_xbootldr);
if (r < 0) {
if (r != -ENOENT && !ERRNO_IS_NOT_SUPPORTED(r))
return log_debug_errno(r, "Unable to determine loader partition UUID: %m");
log_device_debug(dev, "No loader partition UUID EFI variable set, not using partition data for search for default root block device.");
/* NB: if an ESP/xbootldr field is set, we always use that. We do this in order to guarantee
* systematic behaviour. */
if (!STRPTR_IN_SET(loop_backing_fname, "rootdisk", "rootdisk.raw")) {
log_device_debug(dev, "Device is not a loopback block device with reference string 'root', not considering block device as default root block device.");
return 0;
}
/* OK, we have now sufficiently identified this device as the right root "whole" device,
* hence no need to bother with searching for ESP/XBOOTLDR */
need_esp_or_xbootldr = false;
} else
/* We now know the the ESP/xbootldr UUID, but we cannot be sure yet it's on this block
* device, hence look for it among partitions now */
need_esp_or_xbootldr = true;
errno = 0;
blkid_partlist pl = blkid_probe_get_partitions(pr);
if (!pl)
return errno_or_else(ENOMEM);
return log_device_debug_errno(dev, errno_or_else(ENOMEM), "Failed to probe partitions: %m");
int nvals = blkid_partlist_numof_partitions(pl);
for (int i = 0; i < nvals; i++) {
@@ -158,27 +188,21 @@ static int find_gpt_root(UdevEvent *event, blkid_probe pr) {
r = blkid_partition_get_uuid_id128(pp, &id);
if (r < 0) {
log_debug_errno(r, "Failed to get partition UUID, ignoring: %m");
log_device_debug_errno(dev, r, "Failed to get partition UUID, ignoring: %m");
continue;
}
r = blkid_partition_get_type_id128(pp, &type);
if (r < 0) {
log_debug_errno(r, "Failed to get partition type UUID, ignoring: %m");
log_device_debug_errno(dev, r, "Failed to get partition type UUID, ignoring: %m");
continue;
}
label = blkid_partition_get_name(pp); /* returns NULL if empty */
if (sd_id128_in_set(type, SD_GPT_ESP, SD_GPT_XBOOTLDR)) {
sd_id128_t esp_or_xbootldr;
if (need_esp_or_xbootldr && sd_id128_in_set(type, SD_GPT_ESP, SD_GPT_XBOOTLDR)) {
/* We found an ESP or XBOOTLDR, let's see if it matches the ESP/XBOOTLDR we booted from. */
r = efi_loader_get_device_part_uuid(&esp_or_xbootldr);
if (r < 0)
return r;
if (sd_id128_equal(id, esp_or_xbootldr))
found_esp_or_xbootldr = true;
@@ -195,17 +219,20 @@ static int find_gpt_root(UdevEvent *event, blkid_probe pr) {
if (sd_id128_is_null(root_id) || strverscmp_improved(label, root_label) > 0) {
root_id = id;
r = free_and_strdup(&root_label, label);
if (r < 0)
return r;
if (free_and_strdup(&root_label, label) < 0)
return log_oom_debug();
}
}
}
/* We found the ESP/XBOOTLDR on this disk, and also found a root partition, nice! Let's export its
* UUID */
if (found_esp_or_xbootldr && !sd_id128_is_null(root_id))
udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_UUID", SD_ID128_TO_UUID_STRING(root_id));
if (!need_esp_or_xbootldr || found_esp_or_xbootldr) {
/* We found the ESP/XBOOTLDR on this disk (or we didn't need it) */
udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK", "1");
/* We found a root partition, nice! Let's export its UUID. */
if (!sd_id128_is_null(root_id))
udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_UUID", SD_ID128_TO_UUID_STRING(root_id));
}
#endif
return 0;
@@ -437,9 +464,6 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) {
udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT", "1");
}
if (is_gpt)
find_gpt_root(event, pr);
r = read_loopback_backing_inode(
dev,
fd,
@@ -466,6 +490,9 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) {
}
}
if (is_gpt)
find_gpt_root(event, pr, backing_fname);
return 0;
}
+94 -37
View File
@@ -117,6 +117,7 @@ static bool arg_pass_ssh_key = true;
static char *arg_ssh_key_type = NULL;
static bool arg_discard_disk = true;
struct ether_addr arg_network_provided_mac = {};
static char **arg_smbios11 = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -133,6 +134,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep);
STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -167,6 +169,7 @@ static int help(void) {
" --secure-boot=BOOL Enable searching for firmware supporting SecureBoot\n"
" --firmware=PATH|list Select firmware definition file (or list available)\n"
" --discard-disk=BOOL Control processing of discard requests\n"
" -s --smbios11=STRING Pass an arbitrary SMBIOS Type #11 string to the VM\n"
"\n%3$sSystem Identity:%4$s\n"
" -M --machine=NAME Set the machine name for the VM\n"
" --uuid=UUID Set a specific machine UUID for the VM\n"
@@ -295,6 +298,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "firmware", required_argument, NULL, ARG_FIRMWARE },
{ "discard-disk", required_argument, NULL, ARG_DISCARD_DISK },
{ "background", required_argument, NULL, ARG_BACKGROUND },
{ "smbios11", required_argument, NULL, 's' },
{}
};
@@ -304,7 +308,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argv);
optind = 0;
while ((c = getopt_long(argc, argv, "+hD:i:M:nq", options, NULL)) >= 0)
while ((c = getopt_long(argc, argv, "+hD:i:M:nqs:", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
@@ -564,6 +568,20 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case 's':
if (isempty(optarg)) {
arg_smbios11 = strv_free(arg_smbios11);
break;
}
if (!utf8_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", optarg);
if (strv_extend(&arg_smbios11, optarg) < 0)
return log_oom();
break;
case '?':
return -EINVAL;
@@ -908,9 +926,9 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) {
union sockaddr_union addr;
socklen_t addr_len = sizeof addr.vm;
r = getsockname(vsock_fd, &addr.sa, &addr_len);
if (r < 0)
if (getsockname(vsock_fd, &addr.sa, &addr_len) < 0)
return -errno;
assert(addr_len >= sizeof addr.vm);
assert(addr.vm.svm_family == AF_VSOCK);
@@ -921,6 +939,73 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) {
return 0;
}
static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel) {
assert(cmdline);
if (strv_isempty(arg_kernel_cmdline_extra))
return 0;
_cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " ");
if (!kcl)
return log_oom();
if (kernel) {
if (strv_extend_many(cmdline, "-append", kcl) < 0)
return log_oom();
} else {
if (!ARCHITECTURE_SUPPORTS_SMBIOS) {
log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring.");
return 0;
}
_cleanup_free_ char *escaped_kcl = NULL;
escaped_kcl = escape_qemu_value(kcl);
if (!escaped_kcl)
return log_oom();
if (strv_extend(cmdline, "-smbios") < 0)
return log_oom();
if (strv_extendf(cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", escaped_kcl) < 0)
return log_oom();
if (strv_extend(cmdline, "-smbios") < 0)
return log_oom();
if (strv_extendf(cmdline, "type=11,value=io.systemd.boot.kernel-cmdline-extra=%s", escaped_kcl) < 0)
return log_oom();
}
return 0;
}
static int cmdline_add_smbios11(char ***cmdline) {
assert(cmdline);
if (strv_isempty(arg_smbios11))
return 0;
if (!ARCHITECTURE_SUPPORTS_SMBIOS) {
log_warning("Cannot issue SMBIOS Type #11 strings, native architecture doesn't support SMBIOS, ignoring.");
return 0;
}
STRV_FOREACH(i, arg_smbios11) {
_cleanup_free_ char *escaped = NULL;
escaped = escape_qemu_value(*i);
if (!escaped)
return log_oom();
if (strv_extend(cmdline, "-smbios") < 0)
return log_oom();
if (strv_extendf(cmdline, "type=11,value=%s", escaped) < 0)
return log_oom();
}
return 0;
}
static int start_tpm(
sd_bus *bus,
const char *scope,
@@ -1864,41 +1949,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return log_oom();
}
if (ARCHITECTURE_SUPPORTS_SMBIOS) {
_cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " "), *escaped_kcl = NULL;
if (!kcl)
return log_oom();
r = cmdline_add_kernel_cmdline(&cmdline, kernel);
if (r < 0)
return r;
if (kernel) {
r = strv_extend_many(&cmdline, "-append", kcl);
if (r < 0)
return log_oom();
} else {
if (ARCHITECTURE_SUPPORTS_SMBIOS) {
escaped_kcl = escape_qemu_value(kcl);
if (!escaped_kcl)
log_oom();
r = strv_extend(&cmdline, "-smbios");
if (r < 0)
return log_oom();
r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", escaped_kcl);
if (r < 0)
return log_oom();
r = strv_extend(&cmdline, "-smbios");
if (r < 0)
return log_oom();
r = strv_extendf(&cmdline, "type=11,value=io.systemd.boot.kernel-cmdline-extra=%s", escaped_kcl);
if (r < 0)
return log_oom();
} else
log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring");
}
} else
log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS");
r = cmdline_add_smbios11(&cmdline);
if (r < 0)
return r;
/* disable TPM autodetection if the user's hardware doesn't support it */
if (!ARCHITECTURE_SUPPORTS_TPM) {
@@ -4,6 +4,7 @@
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/proc/cmdline
Before=initrd-root-fs.target
After=imports.target
Requires=systemd-fsck-root.service
After=systemd-fsck-root.service
After=blockdev@dev-sdx1.target
@@ -4,6 +4,7 @@
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/proc/cmdline
Before=initrd-root-fs.target
After=imports.target
Requires=systemd-fsck-root.service
After=systemd-fsck-root.service
After=blockdev@dev-disk-by\x2dlabel-Root.target
@@ -4,6 +4,7 @@
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/proc/cmdline
Before=initrd-root-fs.target
After=imports.target
Requires=systemd-fsck-root.service
After=systemd-fsck-root.service
After=blockdev@dev-disk-by\x2duuid-3f5ad593\x2d4546\x2d4a94\x2da374\x2dbcfb68aa11f7.target
@@ -4,6 +4,7 @@
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/proc/cmdline
Before=initrd-root-fs.target
After=imports.target
Requires=systemd-fsck-root.service
After=systemd-fsck-root.service
After=blockdev@dev-disk-by\x2dpartuuid-3f5ad593\x2d4546\x2d4a94\x2da374\x2dbcfb68aa11f7.target
@@ -4,6 +4,7 @@
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/proc/cmdline
Before=initrd-root-fs.target
After=imports.target
[Mount]
What=rootfs
+1 -1
View File
@@ -81,7 +81,7 @@ cat /proc/cmdline
systemctl daemon-reload
systemctl start import0.service
systemctl start systemd-import@var-lib-confexts-importtest9.service
cmp /var/tmp/importtest /var/lib/confexts/importtest9/importtest
# Verify generic service calls, too
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Image Downloads (Pre)
Documentation=man:systemd.special(7)
Before=imports.target
RefuseManualStart=yes
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Image Downloads
Documentation=man:systemd.special(7)
+10
View File
@@ -382,6 +382,15 @@ units = [
'conditions' : ['ENABLE_IMPORTD'],
'symlinks' : ['sockets.target.wants/'],
},
{
'file' : 'imports-pre.target',
'conditions' : ['ENABLE_IMPORTD'],
},
{
'file' : 'imports.target',
'conditions' : ['ENABLE_IMPORTD'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-initctl.service.in',
'conditions' : ['HAVE_SYSV_COMPAT'],
@@ -447,6 +456,7 @@ units = [
'conditions' : ['ENABLE_LOGIND'],
'symlinks' : ['multi-user.target.wants/', 'dbus-org.freedesktop.login1.service'],
},
{ 'file' : 'systemd-loop@.service' },
{
'file' : 'systemd-machine-id-commit.service',
'symlinks' : ['sysinit.target.wants/'],
+24
View File
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Attach File %f to Loopback Block Device
Documentation=man:systemd-loop@.service(8)
DefaultDependencies=no
RequiresMountsFor=%f
IgnoreOnIsolate=yes
Wants=modprobe@loop.service
After=modprobe@loop.service
[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutSec=infinity
ExecStart=systemd-dissect --attach --loop-ref-auto --quiet %f
ExecStop=systemd-dissect --detach %f