* since they don't do anything illegal even when the variable is uninitialized
*/
predicate cleanupFunctionDenyList(string fun) {
- fun = "erase_char"
+ fun = "erase_char" or fun = "erase_obj"
}
/**
branches:
- main
- v[0-9]+-stable
+ paths:
+ - '**'
+ - '!README*'
+ - '!LICENSE*'
+ - '!LICENSES/**'
+ - '!TODO'
+ - '!docs/**'
+ - '!man/**'
+ - '!catalog/**'
+ - '!shell-completion/**'
+ - '!po/**'
+ - '!.**'
+ - '.github/**'
+
pull_request:
branches:
- main
- v[0-9]+-stable
+ paths:
+ - '**'
+ - '!README*'
+ - '!LICENSE*'
+ - '!LICENSES/**'
+ - '!TODO'
+ - '!docs/**'
+ - '!man/**'
+ - '!catalog/**'
+ - '!shell-completion/**'
+ - '!po/**'
+ - '!.**'
+ - '.github/**'
permissions:
contents: read
EOF
- name: Build ${{ matrix.distro }}
- run: sudo python3 -m mkosi build
+ run: sudo mkosi build
- name: Show ${{ matrix.distro }} image summary
- run: sudo python3 -m mkosi summary
+ run: sudo mkosi summary
- name: Boot ${{ matrix.distro }} systemd-nspawn
- run: sudo python3 -m mkosi boot ${{ env.KERNEL_CMDLINE }}
+ run: sudo mkosi boot ${{ env.KERNEL_CMDLINE }}
- name: Check ${{ matrix.distro }} systemd-nspawn
- run: sudo python3 -m mkosi shell bash -c "[[ -e /testok ]] || { cat /failed-services; exit 1; }"
+ run: sudo mkosi shell bash -c "[[ -e /testok ]] || { cat /failed-services; exit 1; }"
- name: Boot ${{ matrix.distro }} QEMU
- run: sudo timeout -k 30 10m python3 -m mkosi qemu
+ run: sudo timeout -k 30 10m mkosi qemu
- name: Check ${{ matrix.distro }} QEMU
- run: sudo python3 -m mkosi shell bash -c "[[ -e /testok ]] || { cat /failed-services; exit 1; }"
+ run: sudo mkosi shell bash -c "[[ -e /testok ]] || { cat /failed-services; exit 1; }"
systemd System and Service Manager
+CHANGES WITH 253 in spe:
+
+ Changes in sd-boot, bootctl, and the Boot Loader Specification:
+
+ * systemd-boot now passes its random seed directly to the kernel's RNG
+ via the LINUX_EFI_RANDOM_SEED_TABLE_GUID configuration table, which
+ means the RNG gets seeded very early in boot before userspace has
+ started.
+
+ * systemd-boot will pass a random seed when secure boot is enabled if
+ it can additionally get a random seed from EFI itself, via EFI's RNG
+ protocol or a prior seed in LINUX_EFI_RANDOM_SEED_TABLE_GUID from a
+ preceding bootloader.
+
+ * The random seed stored in ESP is now refreshed whenever
+ systemd-random-seed.service is run.
+
+ * systemd-boot handles various seed inputs using a domain- and
+ field-separated hashing scheme.
+
CHANGES WITH 252 🎃:
Announcements of Future Feature Removals:
https://github.com/systemd/systemd/issues
OLDER DOCUMENTATION:
- http://0pointer.de/blog/projects/systemd.html
+ https://0pointer.de/blog/projects/systemd.html
https://www.freedesktop.org/wiki/Software/systemd
AUTHOR:
Required for signed Verity images support:
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG
+ Required to verify signed Verity images using keys enrolled in the MoK
+ (Machine-Owner Key) keyring:
+ CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING
+ CONFIG_IMA_ARCH_POLICY
+ CONFIG_INTEGRITY_MACHINE_KEYRING
Required for RestrictFileSystems= in service units:
CONFIG_BPF
* H2 2023: remove support for unmerged-usr
+* Remove /dev/mem ACPI FPDT parsing when /sys/firmware/acpi/fpdt is ubiquitous.
+ That requires distros to enable CONFIG_ACPI_FPDT, and have kernels v5.12 for
+ x86 and v6.2 for arm.
+
Features:
+* 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.
+
+* 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.
+ Usecase: 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.
+
+* bootctl: warn if ESP is mounted world-readable (and in particular the seed).
+
+* sd-stub: call process_random_seed() the same way sd-boot does.
+
* maybe: systemd-loop-generator that sets up loopback devices if requested via kernel
cmdline. usecase: include encrypted/verity root fs in UKI.
* pick up creds from EFI vars
-* sd-stub/sd-boot: write RNG seed to LINUX_EFI_RANDOM_SEED_TABLE_GUID config
- table as well. (and possibly drop our efi var). Current kernels will pick up
- the seed from there already, if EFI_RNG_PROTOCOL is not implemented by
- firmware.
-
-* sd-boot: include domain specific hash string in hash function for random seed
- plus sizes of everything. also include DMI/SMBIOS blob
-
-* sd-stub: invoke random seed logic the same way as in sd-boot, except if
- random seed EFI variable is already set. That way, the variable set will be
- set in all cases: if you just use sd-stub, or just sd-boot, or both.
-
* sd-boot: we probably should include all BootXY EFI variable defined boot
entries in our menu, and then suppress ourselves. Benefit: instant
compatibility with all other OSes which register things there, in particular
extending the command line to enable vsock on the VM, and using fw_cfg to
configure socket address.
-* sd-boot: rework random seed handling following recent kernel changes: always
- pass seed to kernel, but credit only if secure boot is used
-
-* sd-boot: also include the hyperv "vm generation id" in the random seed hash,
- to cover nicely for machine clones. It's found in the ACPI tables, which
- should be easily accessible from UEFI.
-
* sd-boot: add menu item for shutdown? or hotkey?
* sd-device has an API to create an sd_device object from a device id, but has
* `1 << 5` → The boot loader supports looking for boot menu entries in the Extended Boot Loader Partition.
* `1 << 6` → The boot loader supports passing a random seed to the OS.
-* The EFI variable `LoaderRandomSeed` contains a binary random seed if set. It
- is set by the boot loader to pass an entropy seed read from the ESP to the OS.
- The system manager then credits this seed to the kernel's entropy pool. It is
- the responsibility of the boot loader to ensure the quality and integrity of
- the random seed.
-
* The EFI variable `LoaderSystemToken` contains binary random data,
persistently set by the OS installer. Boot loaders that support passing
random seeds to the OS should use this data and combine it with the random
there. The `systemctl reboot --boot-loader-entry=…` and `systemctl reboot
--boot-loader-menu=…` commands rely on the `LoaderFeatures` ,
`LoaderConfigTimeoutOneShot`, `LoaderEntries`, `LoaderEntryOneShot`
-variables. `LoaderRandomSeed` is read by PID during early boot and credited to
-the kernel's random pool.
+variables.
## Boot Loader Entry Identifiers
* `$SYSTEMD_MEMPOOL=0` — if set, the internal memory caching logic employed by
hash tables is turned off, and libc `malloc()` is used for all allocations.
+* `$SYSTEMD_UTF8=` — takes a boolean value, and overrides whether to generate
+ non-ASCII special glyphs at various places (i.e. "→" instead of
+ "->"). Usually this is deterined automatically, based on $LC_CTYPE, but in
+ scenarios where locale definitions are not installed it might make sense to
+ override this check explicitly.
+
* `$SYSTEMD_EMOJI=0` — if set, tools such as `systemd-analyze security` will
not output graphical smiley emojis, but ASCII alternatives instead. Note that
this only controls use of Unicode emoji glyphs, and has no effect on other
`systemd-journald`:
-* `$SYSTEMD_JOURNAL_COMPACT` - Takes a boolean. If enabled, journal files are written
+* `$SYSTEMD_JOURNAL_COMPACT` – Takes a boolean. If enabled, journal files are written
in a more compact format that reduces the amount of disk space required by the
journal. Note that journal files in compact mode are limited to 4G to allow use of
32-bit offsets. Enabled by default.
+
+`systemd-pcrphase`:
+
+* `$SYSTEMD_PCRPHASE_STUB_VERIFY` – Takes a boolean. If false the requested
+ measurement is done even if no EFI stub usage was reported via EFI variables.
generate sufficient data), to generate a new random seed file to store in
the ESP as well as a random seed to pass to the OS kernel. The new random
seed file for the ESP is then written to the ESP, ensuring this is completed
- before the OS is invoked. Very early during initialization PID 1 will read
- the random seed provided in the EFI variable and credit it fully to the
- kernel's entropy pool.
-
- This mechanism is able to safely provide an initialized entropy pool already
- in the `initrd` and guarantees that different seeds are passed from the boot
- loader to the OS on every boot (in a way that does not allow regeneration of
- an old seed file from a new seed file). Moreover, when an OS image is
- replicated between multiple images and the random seed is not reset, this
- will still result in different random seeds being passed to the OS, as the
- per-machine 'system token' is specific to the physical host, and not
- included in OS disk images. If the 'system token' is properly initialized
- and kept sufficiently secret it should not be possible to regenerate the
- entropy pool of different machines, even if this seed is the only source of
- entropy.
+ before the OS is invoked.
+
+ The kernel then reads the random seed that the boot loader passes to it, via
+ the EFI configuration table entry, `LINUX_EFI_RANDOM_SEED_TABLE_GUID`
+ (1ce1e5bc-7ceb-42f2-81e5-8aadf180f57b), which is allocated with pool memory
+ of type `EfiACPIReclaimMemory`. Its contents have the form:
+ ```
+ struct linux_efi_random_seed {
+ u32 size; // of the 'seed' array in bytes
+ u8 seed[];
+ };
+ ```
+ The size field is generally set to 32 bytes, and the seed field includes a
+ hashed representation of any prior seed in `LINUX_EFI_RANDOM_SEED_TABLE_GUID`
+ together with the new seed.
+
+ This mechanism is able to safely provide an initialized entropy pool before
+ userspace even starts and guarantees that different seeds are passed from
+ the boot loader to the OS on every boot (in a way that does not allow
+ regeneration of an old seed file from a new seed file). Moreover, when an OS
+ image is replicated between multiple images and the random seed is not
+ reset, this will still result in different random seeds being passed to the
+ OS, as the per-machine 'system token' is specific to the physical host, and
+ not included in OS disk images. If the 'system token' is properly
+ initialized and kept sufficiently secret it should not be possible to
+ regenerate the entropy pool of different machines, even if this seed is the
+ only source of entropy.
Note that the writes to the ESP needed to maintain the random seed should be
- minimal. The size of the random seed file is directly derived from the Linux
- kernel's entropy pool size, which defaults to 512 bytes. This means updating
- the random seed in the ESP should be doable safely with a single sector
- write (since hard-disk sectors typically happen to be 512 bytes long, too),
- which should be safe even with FAT file system drivers built into
+ minimal. Because the size of the random seed file is generally set to 32 bytes,
+ updating the random seed in the ESP should be doable safely with a single
+ sector write (since hard-disk sectors typically happen to be 512 bytes long,
+ too), which should be safe even with FAT file system drivers built into
low-quality EFI firmwares.
As a special restriction: in virtualized environments PID 1 will refrain
KEYBOARD_KEY_9e=email
KEYBOARD_KEY_9f=homepage
+evdev:name:AT Translated Set 2 keyboard:dmi:bvn*:bvr*:svnCompaq:pn*:pvr*:rvn*:rnN14KP6*
+ KEYBOARD_KEY_76=f21 # Fn+f2 toggle touchpad
+
evdev:input:b0003v049Fp0051*
evdev:input:b0003v049Fp008D*
KEYBOARD_KEY_0c0011=presentation
<listitem><para>If <command>status</command> is invoked (or no explicit command is given) and one of these
switches is specified, <command>hostnamectl</command> will print out just this selected hostname.</para>
- <para>If used with <command>set-hostname</command>, only the selected hostnames will be updated. When more
+ <para>If used with <command>hostname</command>, only the selected hostnames will be updated. When more
than one of these switches are specified, all the specified hostnames will be updated. </para></listitem>
</varlistentry>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemorySwapMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t MemoryZSwapMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryLimit = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s DevicePolicy = '...';
<!--property MemorySwapMax is not documented!-->
+ <!--property MemoryZSwapMax is not documented!-->
+
<!--property MemoryLimit is not documented!-->
<!--property DevicePolicy is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemorySwapMax"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="MemoryZSwapMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryLimit"/>
<variablelist class="dbus-property" generated="True" extra-ref="DevicePolicy"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemorySwapMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t MemoryZSwapMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryLimit = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s DevicePolicy = '...';
<!--property MemorySwapMax is not documented!-->
+ <!--property MemoryZSwapMax is not documented!-->
+
<!--property MemoryLimit is not documented!-->
<!--property DevicePolicy is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemorySwapMax"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="MemoryZSwapMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryLimit"/>
<variablelist class="dbus-property" generated="True" extra-ref="DevicePolicy"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemorySwapMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t MemoryZSwapMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryLimit = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s DevicePolicy = '...';
<!--property MemorySwapMax is not documented!-->
+ <!--property MemoryZSwapMax is not documented!-->
+
<!--property MemoryLimit is not documented!-->
<!--property DevicePolicy is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemorySwapMax"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="MemoryZSwapMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryLimit"/>
<variablelist class="dbus-property" generated="True" extra-ref="DevicePolicy"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemorySwapMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t MemoryZSwapMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryLimit = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s DevicePolicy = '...';
<!--property MemorySwapMax is not documented!-->
+ <!--property MemoryZSwapMax is not documented!-->
+
<!--property MemoryLimit is not documented!-->
<!--property DevicePolicy is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemorySwapMax"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="MemoryZSwapMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryLimit"/>
<variablelist class="dbus-property" generated="True" extra-ref="DevicePolicy"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemorySwapMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t MemoryZSwapMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryLimit = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s DevicePolicy = '...';
<!--property MemorySwapMax is not documented!-->
+ <!--property MemoryZSwapMax is not documented!-->
+
<!--property MemoryLimit is not documented!-->
<!--property DevicePolicy is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemorySwapMax"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="MemoryZSwapMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryLimit"/>
<variablelist class="dbus-property" generated="True" extra-ref="DevicePolicy"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemorySwapMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t MemoryZSwapMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryLimit = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s DevicePolicy = '...';
<!--property MemorySwapMax is not documented!-->
+ <!--property MemoryZSwapMax is not documented!-->
+
<!--property MemoryLimit is not documented!-->
<!--property DevicePolicy is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemorySwapMax"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="MemoryZSwapMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryLimit"/>
<variablelist class="dbus-property" generated="True" extra-ref="DevicePolicy"/>
below. Defaults to <literal>%t</literal>. To disable split artifact generation for a partition, set
<varname>SplitName=</varname> to <literal>-</literal>.</para></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>Minimize=</varname></term>
+
+ <listitem><para>Takes a boolean. Disabled by default. If enabled, the partition is created at least
+ as big as required for the minimal file system of the type specified by <varname>Format=</varname>,
+ taking into account the sources configured with <varname>CopyFiles=</varname>. Note that unless the
+ filesystem is a read-only filesystem, <command>systemd-repart</command> will have to populate the
+ filesystem twice, so enabling this option might slow down repart when populating large partitions.
+ </para></listitem>
+ </varlistentry>
</variablelist>
</refsect1>
to view this data. </para></listitem>
</varlistentry>
- <varlistentry>
- <term><varname>LoaderRandomSeed</varname></term>
-
- <listitem><para>A binary random seed <command>systemd-boot</command> may optionally pass to the
- OS. This is a volatile EFI variable that is hashed at boot from the combination of a random seed
- stored in the ESP (in <filename>/loader/random-seed</filename>) and a "system token" persistently
- stored in the EFI variable <varname>LoaderSystemToken</varname> (see below). During early OS boot the
- system manager reads this variable and passes it to the OS kernel's random pool, crediting the full
- entropy it contains. This is an efficient way to ensure the system starts up with a fully initialized
- kernel random pool — as early as the initrd phase. <command>systemd-boot</command> reads
- the random seed from the ESP, combines it with the "system token", and both derives a new random seed
- to update in-place the seed stored in the ESP, and the random seed to pass to the OS from it via
- SHA256 hashing in counter mode. This ensures that different physical systems that boot the same
- "golden" OS image — i.e. containing the same random seed file in the ESP — will still pass a
- different random seed to the OS. It is made sure the random seed stored in the ESP is fully
- overwritten before the OS is booted, to ensure different random seed data is used between subsequent
- boots.</para>
-
- <para>See <ulink url="https://systemd.io/RANDOM_SEEDS">Random Seeds</ulink> for
- further information.</para></listitem>
- </varlistentry>
-
<varlistentry>
<term><varname>LoaderSystemToken</varname></term>
<filename>/etc/kernel/tries</filename> when a boot loader entry is first created.</para>
</refsect1>
+ <refsect1>
+ <title>Using systemd-boot in virtual machines.</title>
+
+ <para>When using qemu with OVMF (UEFI Firmware for virtual machines) the <option>-kernel</option> switch
+ works not only for linux kernels, but for any EFI binary, including sd-boot and unified linux
+ kernels. Example command line for loading sd-boot on x64:</para>
+
+ <para>
+ <command>qemu-system-x86_64 <replaceable>[ ... ]</replaceable>
+ -kernel /usr/lib/systemd/boot/efi/systemd-bootx64.efi</command>
+ </para>
+
+ <para>systemd-boot will detect that it was started directly instead of being loaded from ESP and will
+ search for the ESP in that case, taking into account boot order information from the hypervisor (if
+ available).</para>
+ </refsect1>
+
<refsect1>
<title>See Also</title>
<para>
<title>Description</title>
<para><command>systemd-dissect</command> is a tool for introspecting and interacting with file system OS
- disk images, specifically Discoverable Disk Images (DDIs). It supports five different operations:</para>
+ disk images, specifically Discoverable Disk Images (DDIs). It supports four different operations:</para>
<orderedlist>
<listitem><para>Show general OS image information, including the image's
--add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \
--add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \
--add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \
- --add-section .pcrsig=tpm2-pcr-signature.json --change-section-vma .splash=0x80000 \
- --add-section .pcrpkey=tpm2-pcr-public.pem --change-section-vma .splash=0x90000 \
+ --add-section .pcrsig=tpm2-pcr-signature.json --change-section-vma .pcrsig=0x80000 \
+ --add-section .pcrpkey=tpm2-pcr-public.pem --change-section-vma .pcrpkey=0x90000 \
/usr/lib/systemd/boot/efi/linuxx64.efi.stub \
foo.efi</programlisting>
if <option>--split</option> is enabled.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--include-partitions=</option><arg rep="repeat">PARTITION</arg></term>
+ <term><option>--exclude-partitions=</option><arg rep="repeat">PARTITION</arg></term>
+
+ <listitem><para>These options specify which partition types <command>systemd-repart</command> should
+ operate on. If <option>--include-partitions=</option> is used, all partitions that aren't specified
+ are excluded. If <option>--exclude-partitions=</option> is used, all partitions that are specified
+ are excluded. Both options take a comma separated list of GPT partition type UUIDs or identifiers
+ (see <varname>Type=</varname> in
+ <citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>). All
+ partitions that are excluded using these options are still taken into account when calculating the
+ sizes and offsets of other partitions, but aren't actually written to the disk image. The net effect
+ of this option is that if you run <command>systemd-repart</command> again without these options, the
+ missing partitions will be added as if they had not been excluded the first time
+ <command>systemd-repart</command> was executed.</para></listitem>
+ </varlistentry>
+
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<varlistentry>
<term><varname>MemorySwapMax=<replaceable>bytes</replaceable></varname></term>
+ <term><varname>MemoryZSwapMax=<replaceable>bytes</replaceable></varname></term>
<listitem>
- <para>Specify the absolute limit on swap usage of the executed processes in this unit.</para>
+ <para>Specify the absolute limit on (z)swap usage of the executed processes in this unit.</para>
<para>Takes a swap size in bytes. If the value is suffixed with K, M, G or T, the specified swap size is
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the
- special value <literal>infinity</literal>, no swap limit is applied. This controls the
- <literal>memory.swap.max</literal> control group attribute. For details about this control group attribute,
+ special value <literal>infinity</literal>, no swap limit is applied. These settings control the
+ <literal>memory.(z)swap.max</literal> control group attributes. For details about these control group attributes,
see <ulink url="https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files">Memory Interface Files</ulink>.</para>
</listitem>
</varlistentry>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>'''],
+ ['rt_tgsigqueueinfo', '''#include <stdlib.h>
+ #include <unistd.h>
+ #include <signal.h>
+ #include <sys/wait.h>'''],
['mallinfo', '''#include <malloc.h>'''],
['mallinfo2', '''#include <malloc.h>'''],
['execveat', '''#include <unistd.h>'''],
foreach ident : ['crypt_set_metadata_size',
'crypt_activate_by_signed_key',
- 'crypt_token_max']
+ 'crypt_token_max',
+ 'crypt_reencrypt_init_by_passphrase',
+ 'crypt_reencrypt',
+ 'crypt_set_data_offset']
have_ident = have and cc.has_function(
ident,
prefix : '#include <libcryptsetup.h>',
tpm2 = dependency('tss2-esys tss2-rc tss2-mu',
required : want_tpm2 == 'true')
have = tpm2.found()
+ have_esys3 = tpm2.version().version_compare('>= 3.0.0')
else
have = false
+ have_esys3 = false
tpm2 = []
endif
conf.set10('HAVE_TPM2', have)
+conf.set10('HAVE_TSS2_ESYS3', have_esys3)
want_elfutils = get_option('elfutils')
if want_elfutils != 'false' and not skip_deps
install : true,
install_dir : rootlibexecdir)
-executable(
+public_programs += executable(
'systemd-ac-power',
'src/ac-power/ac-power.c',
include_directories : includes,
link_with : [libshared],
dependencies : [versiondep],
install_rpath : rootpkglibdir,
- install : true,
- install_dir : rootlibexecdir)
+ install : true)
public_programs += executable(
'systemd-detect-virt',
# version 5.6.0 to satisfy meson which makes bpf work on CentOS Stream 8 as well.
if [ "$(grep '^ID=' /etc/os-release)" = "ID=\"centos\"" ] && [ "$(grep '^VERSION=' /etc/os-release)" = "VERSION=\"8\"" ]; then
cp /usr/sbin/bpftool /usr/sbin/bpftool.real
- cat > /usr/sbin/bpftool <<EOF
+ cat >/usr/sbin/bpftool <<EOF
#!/bin/sh
if [ "\$1" = --version ]; then
echo 5.6.0
-e '/^IMAGE_VERSION=/!p' \
-e "\$aIMAGE_VERSION=$IMAGE_VERSION" <$OSRELEASEFILE >"/tmp/os-release.tmp"
- cat /tmp/os-release.tmp > "$DESTDIR"/usr/lib/os-release
+ cat /tmp/os-release.tmp >"$DESTDIR"/usr/lib/os-release
rm /tmp/os-release.tmp
fi
mkdir -p "$DESTDIR/etc/systemd/system.conf.d"
- cat > "$DESTDIR/etc/systemd/system.conf.d/10-asan.conf" <<EOF
+ cat >"$DESTDIR/etc/systemd/system.conf.d/10-asan.conf" <<EOF
[Manager]
ManagerEnvironment=ASAN_OPTIONS=$ASAN_OPTIONS\\
UBSAN_OPTIONS=$UBSAN_OPTIONS\\
# sanitizer failures appear directly on the user's console.
mkdir -p "$DESTDIR/etc/systemd/system/systemd-journald.service.d"
- cat > "$DESTDIR/etc/systemd/system/systemd-journald.service.d/10-stdout-tty.conf" <<EOF
+ cat >"$DESTDIR/etc/systemd/system/systemd-journald.service.d/10-stdout-tty.conf" <<EOF
[Service]
StandardOutput=tty
EOF
mkdir -p "$DESTDIR/etc/systemd/system/console-getty.service.d"
- cat > "$DESTDIR/etc/systemd/system/console-getty.service.d/10-no-vhangup.conf" <<EOF
+ cat >"$DESTDIR/etc/systemd/system/console-getty.service.d/10-no-vhangup.conf" <<EOF
[Service]
TTYVHangup=no
CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG
# Make sure services aren't enabled by default on Debian/Ubuntu.
mkdir -p "$DESTDIR/etc/systemd/system-preset"
-echo "disable *" > "$DESTDIR/etc/systemd/system-preset/99-mkosi.preset"
+echo "disable *" >"$DESTDIR/etc/systemd/system-preset/99-mkosi.preset"
if [ -d mkosi.kernel/ ]; then
cd "$SRCDIR/mkosi.kernel"
--enable MEMCG \
--enable MEMCG_SWAP \
--enable MEMCG_KMEM \
+ --enable IMA_ARCH_POLICY \
+ --enable DM_VERITY_VERIFY_ROOTHASH_SIG \
+ --enable DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING \
+ --enable INTEGRITY_MACHINE_KEYRING \
--enable NETFILTER_ADVANCED \
--enable NF_CONNTRACK_MARK
coreutils
diffutils
dnsmasq
+ dosfstools
+ e2fsprogs
findutils
gcc # For sanitizer libraries
gdb
kexec-tools
kmod
less
+ mtools
nano
nftables
openssl
util-linux
valgrind
wireguard-tools
+ xfsprogs
zsh
BuildPackages=
[Content]
Packages=
alsa-lib
+ btrfs-progs
compsize
dhcp
+ f2fs-tools
fuse2
gnutls
iproute
[Content]
Packages=
+ btrfs-progs
cryptsetup-bin
+ f2fs-tools
fdisk
fuse
gcc # Provides libasan/libubsan
[Content]
Packages=
alsa-lib
+ btrfs-progs
compsize
cryptsetup
dhcp-server
+ f2fs-tools
fuse
glib2
glibc-minimal-langpack
[Content]
Packages=
+ btrfs-progs
dbus-1
+ f2fs-tools
fuse
gcc # Provides libasan/libubsan
glibc-32bit
[Content]
Packages=
+ btrfs-progs
cryptsetup-bin
+ f2fs-tools
fdisk
fuse
gcc # Provides libasan/libubsan
# SPDX-License-Identifier: LGPL-2.1-or-later
if [ "$1" = "final" ]; then
- if command -v bootctl > /dev/null && [ -d "/efi" ]; then
+ if command -v bootctl >/dev/null && [ -d "/efi" ]; then
bootctl install
fi
- cat >> /root/.gdbinit <<EOF
+ cat >>/root/.gdbinit <<EOF
set debuginfod enabled off
set build-id-verbose 0
EOF
#
# Finnish translation of systemd.
# Jan Kuparinen <copper_fin@hotmail.com>, 2021, 2022.
+# Ricky Tigg <ricky.tigg@gmail.com>, 2022.
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-20 10:35+0200\n"
-"PO-Revision-Date: 2022-10-22 17:19+0000\n"
-"Last-Translator: Jan Kuparinen <copper_fin@hotmail.com>\n"
+"PO-Revision-Date: 2022-11-10 19:19+0000\n"
+"Last-Translator: Ricky Tigg <ricky.tigg@gmail.com>\n"
"Language-Team: Finnish <https://translate.fedoraproject.org/projects/systemd/"
"master/fi/>\n"
"Language: fi\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.14.1\n"
+"X-Generator: Weblate 4.14.2\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
#: src/hostname/org.freedesktop.hostname1.policy:20
msgid "Set hostname"
-msgstr "Määritä isäntänimi"
+msgstr "Määritä konenimi"
#: src/hostname/org.freedesktop.hostname1.policy:21
msgid "Authentication is required to set the local hostname."
if (r < 0)
return log_error_errno(r, "Failed to format calendar specification '%s': %m", p);
- table = table_new("name", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
assert_se(cell = table_get_cell(table, 0, 0));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
- r = table_set_align_percent(table, cell, 100);
- if (r < 0)
- return r;
-
assert_se(cell = table_get_cell(table, 0, 1));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
if (!streq(t, p)) {
r = table_add_many(table,
- TABLE_STRING, "Original form:",
+ TABLE_FIELD, "Original form",
TABLE_STRING, p);
if (r < 0)
return table_log_add_error(r);
}
r = table_add_many(table,
- TABLE_STRING, "Normalized form:",
+ TABLE_FIELD, "Normalized form",
TABLE_STRING, t);
if (r < 0)
return table_log_add_error(r);
if (r == -ENOENT) {
if (i == 0) {
r = table_add_many(table,
- TABLE_STRING, "Next elapse:",
+ TABLE_FIELD, "Next elapse",
TABLE_STRING, "never",
TABLE_SET_COLOR, ansi_highlight_yellow());
if (r < 0)
if (i == 0) {
r = table_add_many(table,
- TABLE_STRING, "Next elapse:",
+ TABLE_FIELD, "Next elapse",
TABLE_TIMESTAMP, next,
TABLE_SET_COLOR, ansi_highlight_blue());
if (r < 0)
else
k = 0;
- r = table_add_cell_stringf(table, NULL, "Iter. #%u:", i+1);
+ r = table_add_cell_stringf_full(table, NULL, TABLE_FIELD, "Iteration #%u", i+1);
if (r < 0)
return table_log_add_error(r);
if (!in_utc_timezone()) {
r = table_add_many(table,
- TABLE_STRING, "(in UTC):",
+ TABLE_FIELD, "(in UTC)",
TABLE_TIMESTAMP_UTC, next);
if (r < 0)
return table_log_add_error(r);
}
r = table_add_many(table,
- TABLE_STRING, "From now:",
+ TABLE_FIELD, "From now",
TABLE_TIMESTAMP_RELATIVE, next);
if (r < 0)
return table_log_add_error(r);
print_separator();
if (path_is_absolute(*arg)) {
- const char *dir;
-
NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) {
t = path_startswith(*arg, dir);
if (t)
}
static void filesystem_set_remove(Set *s, const FilesystemSet *set) {
- const char *filesystem;
-
NULSTR_FOREACH(filesystem, set->value) {
if (filesystem[0] == '@')
continue;
}
static void dump_filesystem_set(const FilesystemSet *set) {
- const char *filesystem;
int r;
if (!set)
if (strv_isempty(strv_skip(argv, 1))) {
_cleanup_set_free_ Set *kernel = NULL, *known = NULL;
- const char *fs;
int k;
NULSTR_FOREACH(fs, filesystem_sets[FILESYSTEM_SET_KNOWN].value)
if (r < 0)
return log_error_errno(r, "Parsing \"%s\" as ELF object failed: %m", abspath);
- t = table_new("", "");
+ t = table_new_vertical();
if (!t)
return log_oom();
- r = table_set_align_percent(t, TABLE_HEADER_CELL(0), 100);
- if (r < 0)
- return table_log_add_error(r);
-
r = table_add_many(
t,
- TABLE_STRING, "path:",
+ TABLE_FIELD, "path",
TABLE_STRING, abspath);
if (r < 0)
return table_log_add_error(r);
* metadata is parsed recursively in core files, so there might be
* multiple modules. */
if (STR_IN_SET(module_name, "elfType", "elfArchitecture")) {
- _cleanup_free_ char *suffixed = NULL;
-
- suffixed = strjoin(module_name, ":");
- if (!suffixed)
- return log_oom();
-
r = table_add_many(
t,
- TABLE_STRING, suffixed,
+ TABLE_FIELD, module_name,
TABLE_STRING, json_variant_string(module_json));
if (r < 0)
return table_log_add_error(r);
if (!streq(abspath, module_name)) {
r = table_add_many(
t,
- TABLE_STRING, "module name:",
+ TABLE_FIELD, "module name",
TABLE_STRING, module_name);
if (r < 0)
return table_log_add_error(r);
JSON_VARIANT_OBJECT_FOREACH(field_name, field, module_json)
if (json_variant_is_string(field)) {
- _cleanup_free_ char *suffixed = NULL;
-
- suffixed = strjoin(field_name, ":");
- if (!suffixed)
- return log_oom();
-
r = table_add_many(
t,
- TABLE_STRING, suffixed,
+ TABLE_FIELD, field_name,
TABLE_STRING, json_variant_string(field));
if (r < 0)
return table_log_add_error(r);
}
}
if (json_flags & JSON_FORMAT_OFF) {
- (void) table_set_header(t, true);
-
r = table_print(t, NULL);
if (r < 0)
return table_log_print_error(r);
}
static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilterSet *f, const char **ret_offending_syscall) {
- const char *syscall;
-
NULSTR_FOREACH(syscall, f->value) {
if (syscall[0] == '@') {
const SyscallFilterSet *g;
}
static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
- const char *syscall;
-
if (!set)
return;
}
static void dump_syscall_filter(const SyscallFilterSet *set) {
- const char *syscall;
-
printf("%s%s%s\n"
" # %s\n",
ansi_highlight(),
if (strv_isempty(strv_skip(argv, 1))) {
_cleanup_set_free_ Set *kernel = NULL, *known = NULL;
- const char *sys;
int k = 0; /* explicit initialization to appease gcc */
NULSTR_FOREACH(sys, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value)
return r;
}
- table = table_new("name", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
assert_se(cell = table_get_cell(table, 0, 0));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
- r = table_set_align_percent(table, cell, 100);
- if (r < 0)
- return r;
-
assert_se(cell = table_get_cell(table, 0, 1));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_add_many(table,
- TABLE_STRING, "Original:",
+ TABLE_FIELD, "Original",
TABLE_STRING, *input_timespan);
if (r < 0)
return table_log_add_error(r);
- r = table_add_cell_stringf(table, NULL, "%ss:", special_glyph(SPECIAL_GLYPH_MU));
+ r = table_add_cell_stringf_full(table, NULL, TABLE_FIELD, "%ss", special_glyph(SPECIAL_GLYPH_MU));
if (r < 0)
return table_log_add_error(r);
r = table_add_many(table,
TABLE_UINT64, output_usecs,
- TABLE_STRING, "Human:",
+ TABLE_FIELD, "Human",
TABLE_TIMESPAN, output_usecs,
TABLE_SET_COLOR, ansi_highlight());
if (r < 0)
return r;
}
- table = table_new("name", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
assert_se(cell = table_get_cell(table, 0, 0));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
- r = table_set_align_percent(table, cell, 100);
- if (r < 0)
- return r;
-
assert_se(cell = table_get_cell(table, 0, 1));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_add_many(table,
- TABLE_STRING, "Original form:",
+ TABLE_FIELD, "Original form",
TABLE_STRING, p,
- TABLE_STRING, "Normalized form:",
+ TABLE_FIELD, "Normalized form",
TABLE_TIMESTAMP, usec,
TABLE_SET_COLOR, ansi_highlight_blue());
if (r < 0)
if (!in_utc_timezone()) {
r = table_add_many(table,
- TABLE_STRING, "(in UTC):",
+ TABLE_FIELD, "(in UTC)",
TABLE_TIMESTAMP_UTC, usec);
if (r < 0)
return table_log_add_error(r);
}
- r = table_add_cell(table, NULL, TABLE_STRING, "UNIX seconds:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "UNIX seconds");
if (r < 0)
return table_log_add_error(r);
return r;
r = table_add_many(table,
- TABLE_STRING, "From now:",
+ TABLE_FIELD, "From now",
TABLE_TIMESTAMP_RELATIVE, usec);
if (r < 0)
return table_log_add_error(r);
strna(n1), path);
}
+static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) {
+ _cleanup_free_ char *n1 = NULL;
+
+ assert(fd >= 0);
+
+ if (!FLAGS_SET(flags, CHASE_WARN))
+ return -EREMCHG;
+
+ (void) fd_get_path(fd, &n1);
+
+ return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG),
+ "Detected symlink where not symlink is allowed at %s, refusing.",
+ strna(n1));
+}
+
int chase_symlinks_at(
int dir_fd,
const char *path,
if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
_cleanup_free_ char *destination = NULL;
+ if (flags & CHASE_PROHIBIT_SYMLINKS)
+ return log_prohibited_symlink(child, flags);
+
/* This is a symlink, in this case read the destination. But let's make sure we
* don't follow symlinks without bounds. */
if (--max_follow <= 0)
SYNTHETIC_ERRNO(ECHRNG),
"Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
absolute, root);
- }
- if (root) {
fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
* Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
* relative to the given directory fd instead of root. */
+ CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */
} ChaseSymlinksFlags;
bool unsafe_transition(const struct stat *a, const struct stat *b);
#include "hashmap.h"
#include "log.h"
#include "macro.h"
+#include "nulstr-util.h"
#include "path-util.h"
#include "set.h"
#include "sort-util.h"
#include "log.h"
#include "macro.h"
#include "mkdir.h"
+#include "nulstr-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "socket-util.h"
}
bool fs_in_group(const struct statfs *s, FilesystemGroups fs_group) {
- const char *fs;
int r;
NULSTR_FOREACH(fs, filesystem_sets[fs_group].value) {
int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
_cleanup_close_ int fd = -1;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
if (path) {
/* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */
fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
}
bool is_locale_utf8(void) {
- const char *set;
static int cached_answer = -1;
+ const char *set;
+ int r;
/* Note that we default to 'true' here, since today UTF8 is
* pretty much supported everywhere. */
if (cached_answer >= 0)
goto out;
+ r = getenv_bool_secure("SYSTEMD_UTF8");
+ if (r >= 0) {
+ cached_answer = r;
+ goto out;
+ } else if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m");
+
if (!setlocale(LC_ALL, "")) {
cached_answer = true;
goto out;
RateLimit ratelimit;
} LogRateLimit;
-#define log_ratelimit_internal(_level, _error, _format, _file, _line, _func, ...) \
+#define log_ratelimit_internal(_level, _error, _ratelimit, _format, _file, _line, _func, ...) \
({ \
int _log_ratelimit_error = (_error); \
int _log_ratelimit_level = (_level); \
static LogRateLimit _log_ratelimit = { \
- .ratelimit = { \
- .interval = 1 * USEC_PER_SEC, \
- .burst = 1, \
- }, \
+ .ratelimit = (_ratelimit), \
}; \
unsigned _num_dropped_errors = ratelimit_num_dropped(&_log_ratelimit.ratelimit); \
if (_log_ratelimit_error != _log_ratelimit.error || _log_ratelimit_level != _log_ratelimit.level) { \
_log_ratelimit.error = _log_ratelimit_error; \
_log_ratelimit.level = _log_ratelimit_level; \
} \
- if (ratelimit_below(&_log_ratelimit.ratelimit)) \
+ if (log_get_max_level() == LOG_DEBUG || ratelimit_below(&_log_ratelimit.ratelimit)) \
_log_ratelimit_error = _num_dropped_errors > 0 \
- ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", __VA_ARGS__, _num_dropped_errors) \
- : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, __VA_ARGS__); \
+ ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", ##__VA_ARGS__, _num_dropped_errors) \
+ : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, ##__VA_ARGS__); \
_log_ratelimit_error; \
})
-#define log_ratelimit_full_errno(level, error, format, ...) \
+#define log_ratelimit_full_errno(level, error, _ratelimit, format, ...) \
({ \
int _level = (level), _e = (error); \
_e = (log_get_max_level() >= LOG_PRI(_level)) \
- ? log_ratelimit_internal(_level, _e, format, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \
+ ? log_ratelimit_internal(_level, _e, _ratelimit, format, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__) \
: -ERRNO_VALUE(_e); \
_e < 0 ? _e : -ESTRPIPE; \
})
+
+#define log_ratelimit_full(level, _ratelimit, format, ...) \
+ log_ratelimit_full_errno(level, 0, _ratelimit, format, ##__VA_ARGS__)
+
+/* Normal logging */
+#define log_ratelimit_info(...) log_ratelimit_full(LOG_INFO, __VA_ARGS__)
+#define log_ratelimit_notice(...) log_ratelimit_full(LOG_NOTICE, __VA_ARGS__)
+#define log_ratelimit_warning(...) log_ratelimit_full(LOG_WARNING, __VA_ARGS__)
+#define log_ratelimit_error(...) log_ratelimit_full(LOG_ERR, __VA_ARGS__)
+#define log_ratelimit_emergency(...) log_ratelimit_full(log_emergency_level(), __VA_ARGS__)
+
+/* Logging triggered by an errno-like error */
+#define log_ratelimit_info_errno(error, ...) log_ratelimit_full_errno(LOG_INFO, error, __VA_ARGS__)
+#define log_ratelimit_notice_errno(error, ...) log_ratelimit_full_errno(LOG_NOTICE, error, __VA_ARGS__)
+#define log_ratelimit_warning_errno(error, ...) log_ratelimit_full_errno(LOG_WARNING, error, __VA_ARGS__)
+#define log_ratelimit_error_errno(error, ...) log_ratelimit_full_errno(LOG_ERR, error, __VA_ARGS__)
+#define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__)
/* ======================================================================= */
+#if !HAVE_RT_TGSIGQUEUEINFO
+static inline int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) {
+# if defined __NR_rt_tgsigqueueinfo && __NR_rt_tgsigqueueinfo >= 0
+ return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info);
+# else
+# error "__NR_rt_tgsigqueueinfo not defined"
+# endif
+}
+
+# define rt_tgsigqueueinfo missing_rt_tgsigqueueinfo
+#endif
+
+/* ======================================================================= */
+
#if !HAVE_EXECVEAT
static inline int missing_execveat(int dirfd, const char *pathname,
char *const argv[], char *const envp[],
#include "nulstr-util.h"
#include "string-util.h"
+#include "strv.h"
-const char* nulstr_get(const char *nulstr, const char *needle) {
- const char *i;
+char** strv_parse_nulstr(const char *s, size_t l) {
+ /* l is the length of the input data, which will be split at NULs into elements of the resulting
+ * strv. Hence, the number of items in the resulting strv will be equal to one plus the number of NUL
+ * bytes in the l bytes starting at s, unless s[l-1] is NUL, in which case the final empty string is
+ * not stored in the resulting strv, and length is equal to the number of NUL bytes.
+ *
+ * Note that contrary to a normal nulstr which cannot contain empty strings, because the input data
+ * is terminated by any two consequent NUL bytes, this parser accepts empty strings in s. */
+
+ _cleanup_strv_free_ char **v = NULL;
+ size_t c = 0, i = 0;
+
+ assert(s || l <= 0);
+
+ if (l <= 0)
+ return new0(char*, 1);
+
+ for (const char *p = s; p < s + l; p++)
+ if (*p == 0)
+ c++;
+
+ if (s[l-1] != 0)
+ c++;
+
+ v = new0(char*, c+1);
+ if (!v)
+ return NULL;
+
+ for (const char *p = s; p < s + l; ) {
+ const char *e;
+
+ e = memchr(p, 0, s + l - p);
+
+ v[i] = memdup_suffix0(p, e ? e - p : s + l - p);
+ if (!v[i])
+ return NULL;
+
+ i++;
+
+ if (!e)
+ break;
+
+ p = e + 1;
+ }
+
+ assert(i == c);
+
+ return TAKE_PTR(v);
+}
+
+char** strv_split_nulstr(const char *s) {
+ _cleanup_strv_free_ char **l = NULL;
+ /* This parses a nulstr, without specification of size, and stops at an empty string. This cannot
+ * parse nulstrs with embedded empty strings hence, as an empty string is an end marker. Use
+ * strv_parse_nulstr() above to parse a nulstr with embedded empty strings (which however requires a
+ * size to be specified) */
+
+ NULSTR_FOREACH(i, s)
+ if (strv_extend(&l, i) < 0)
+ return NULL;
+
+ return l ? TAKE_PTR(l) : strv_new(NULL);
+}
+
+int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) {
+ /* Builds a nulstr and returns it together with the size. An extra NUL byte will be appended (⚠️ but
+ * not included in the size! ⚠️). This is done so that the nulstr can be used both in
+ * strv_parse_nulstr() and in NULSTR_FOREACH()/strv_split_nulstr() contexts, i.e. with and without a
+ * size parameter. In the former case we can include empty strings, in the latter case we cannot (as
+ * that is the end marker).
+ *
+ * When NULSTR_FOREACH()/strv_split_nulstr() is used it is often assumed that the nulstr ends in two
+ * NUL bytes (which it will, if not empty). To ensure that this assumption *always* holds, we'll
+ * return a buffer with two NUL bytes in that case, but return a size of zero. */
+
+ _cleanup_free_ char *m = NULL;
+ size_t n = 0;
+
+ assert(ret);
+ assert(ret_size);
+
+ STRV_FOREACH(i, l) {
+ size_t z;
+
+ z = strlen(*i);
+
+ if (!GREEDY_REALLOC(m, n + z + 2))
+ return -ENOMEM;
+
+ memcpy(m + n, *i, z + 1);
+ n += z + 1;
+ }
+
+ if (!m) {
+ /* return a buffer with an extra NUL, so that the assumption that we always have two trailing NULs holds */
+ m = new0(char, 2);
+ if (!m)
+ return -ENOMEM;
+
+ n = 0;
+ } else
+ /* Make sure there is a second extra NUL at the end of resulting nulstr (not counted in return size) */
+ m[n] = '\0';
+
+ *ret = TAKE_PTR(m);
+ *ret_size = n;
+
+ return 0;
+}
+
+const char* nulstr_get(const char *nulstr, const char *needle) {
if (!nulstr)
return NULL;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <errno.h>
+#include <macro.h>
#include <stdbool.h>
#include <string.h>
#define NULSTR_FOREACH(i, l) \
- for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
+ for (typeof(*(l)) *(i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
#define NULSTR_FOREACH_PAIR(i, j, l) \
- for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i))
+ for (typeof(*(l)) *(i) = (l), *(j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i))
const char* nulstr_get(const char *nulstr, const char *needle);
static inline bool nulstr_contains(const char *nulstr, const char *needle) {
return nulstr_get(nulstr, needle);
}
+
+char** strv_parse_nulstr(const char *s, size_t l);
+char** strv_split_nulstr(const char *s);
+int strv_make_nulstr(char * const *l, char **p, size_t *n);
+
+static inline int strv_from_nulstr(char ***ret, const char *nulstr) {
+ char **t;
+
+ assert(ret);
+
+ t = strv_split_nulstr(nulstr);
+ if (!t)
+ return -ENOMEM;
+
+ *ret = t;
+ return 0;
+}
}
int find_portable_profile(const char *name, const char *unit, char **ret_path) {
- const char *p, *dot;
+ const char *dot;
assert(name);
assert(ret_path);
return path_make_relative(from, to, ret);
}
-int path_make_relative_cwd(const char *p, char **ret) {
- char *c;
- int r;
-
- assert(p);
- assert(ret);
-
- if (path_is_absolute(p)) {
- _cleanup_free_ char *cwd = NULL;
-
- r = safe_getcwd(&cwd);
- if (r < 0)
- return r;
-
- r = path_make_relative(cwd, p, &c);
- if (r < 0)
- return r;
- } else {
- c = strdup(p);
- if (!c)
- return -ENOMEM;
- }
-
- *ret = TAKE_PTR(c);
- return 0;
-}
-
char* path_startswith_strv(const char *p, char **set) {
STRV_FOREACH(s, set) {
char *t;
int path_make_absolute_cwd(const char *p, char **ret);
int path_make_relative(const char *from, const char *to, char **ret);
int path_make_relative_parent(const char *from_child, const char *to, char **ret);
-int path_make_relative_cwd(const char *from, char **ret);
char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;
static inline char* path_startswith(const char *path, const char *prefix) {
return path_startswith_full(path, prefix, true);
#include "missing_sched.h"
#include "missing_syscall.h"
#include "namespace-util.h"
+#include "nulstr-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
/* Some limits on the pool sizes when we deal with the kernel random pool */
#define RANDOM_POOL_SIZE_MIN 32U
#define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)
+#define RANDOM_EFI_SEED_SIZE 32U
size_t random_pool_size(void);
#include "missing_syscall.h"
#include "process-util.h"
#include "sigbus.h"
+#include "signal-util.h"
#define SIGBUS_QUEUE_MAX 64
if (si->si_code != BUS_ADRERR || !si->si_addr) {
assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0);
- rt_sigqueueinfo(getpid_cached(), SIGBUS, si);
+ propagate_signal(sn, si);
return;
}
#include "errno-util.h"
#include "macro.h"
+#include "missing_syscall.h"
#include "parse-util.h"
#include "signal-util.h"
#include "stdio-util.h"
return r; /* Returns the signal popped */
}
+
+void propagate_signal(int sig, siginfo_t *siginfo) {
+ pid_t p;
+
+ /* To be called from a signal handler. Will raise the same signal again, in our process + in our threads.
+ *
+ * Note that we use raw_getpid() instead of getpid_cached(). We might have forked with raw_clone()
+ * earlier (see PID 1), and hence let's go to the raw syscall here. In particular as this is not
+ * performance sensitive code.
+ *
+ * Note that we use kill() rather than raise() as fallback, for similar reasons. */
+
+ p = raw_getpid();
+
+ if (rt_tgsigqueueinfo(p, gettid(), sig, siginfo) < 0)
+ assert_se(kill(p, sig) >= 0);
+}
int pop_pending_signal_internal(int sig, ...);
#define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1)
+
+void propagate_signal(int sig, siginfo_t *siginfo);
#include "fileio.h"
#include "filesystems.h"
#include "fs-util.h"
+#include "hash-funcs.h"
#include "macro.h"
#include "missing_fs.h"
#include "missing_magic.h"
return 0;
}
+
+void inode_hash_func(const struct stat *q, struct siphash *state) {
+ siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
+ siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
+}
+
+int inode_compare_func(const struct stat *a, const struct stat *b) {
+ int r;
+
+ r = CMP(a->st_dev, b->st_dev);
+ if (r != 0)
+ return r;
+
+ return CMP(a->st_ino, b->st_ino);
+}
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
#include "macro.h"
#include "missing_stat.h"
+#include "siphash24.h"
int is_symlink(const char *path);
int is_dir_full(int atfd, const char *fname, bool follow);
struct new_statx nsx; \
} var
#endif
+
+void inode_hash_func(const struct stat *q, struct siphash *state);
+int inode_compare_func(const struct stat *a, const struct stat *b);
+extern const struct hash_ops inode_hash_ops;
return l;
}
-char** strv_parse_nulstr(const char *s, size_t l) {
- /* l is the length of the input data, which will be split at NULs into
- * elements of the resulting strv. Hence, the number of items in the resulting strv
- * will be equal to one plus the number of NUL bytes in the l bytes starting at s,
- * unless s[l-1] is NUL, in which case the final empty string is not stored in
- * the resulting strv, and length is equal to the number of NUL bytes.
- *
- * Note that contrary to a normal nulstr which cannot contain empty strings, because
- * the input data is terminated by any two consequent NUL bytes, this parser accepts
- * empty strings in s.
- */
-
- size_t c = 0, i = 0;
- char **v;
-
- assert(s || l <= 0);
-
- if (l <= 0)
- return new0(char*, 1);
-
- for (const char *p = s; p < s + l; p++)
- if (*p == 0)
- c++;
-
- if (s[l-1] != 0)
- c++;
-
- v = new0(char*, c+1);
- if (!v)
- return NULL;
-
- for (const char *p = s; p < s + l; ) {
- const char *e;
-
- e = memchr(p, 0, s + l - p);
-
- v[i] = strndup(p, e ? e - p : s + l - p);
- if (!v[i]) {
- strv_free(v);
- return NULL;
- }
-
- i++;
-
- if (!e)
- break;
-
- p = e + 1;
- }
-
- assert(i == c);
-
- return v;
-}
-
-char** strv_split_nulstr(const char *s) {
- const char *i;
- char **r = NULL;
-
- NULSTR_FOREACH(i, s)
- if (strv_extend(&r, i) < 0) {
- strv_free(r);
- return NULL;
- }
-
- if (!r)
- return strv_new(NULL);
-
- return r;
-}
-
-int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) {
- /* A valid nulstr with two NULs at the end will be created, but
- * q will be the length without the two trailing NULs. Thus the output
- * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
- * and can also be parsed by strv_parse_nulstr as long as the length
- * is provided separately.
- */
-
- _cleanup_free_ char *m = NULL;
- size_t n = 0;
-
- assert(ret);
- assert(ret_size);
-
- STRV_FOREACH(i, l) {
- size_t z;
-
- z = strlen(*i);
-
- if (!GREEDY_REALLOC(m, n + z + 2))
- return -ENOMEM;
-
- memcpy(m + n, *i, z + 1);
- n += z + 1;
- }
-
- if (!m) {
- m = new0(char, 1);
- if (!m)
- return -ENOMEM;
- n = 1;
- } else
- /* make sure there is a second extra NUL at the end of resulting nulstr */
- m[n] = '\0';
-
- assert(n > 0);
- *ret = m;
- *ret_size = n - 1;
-
- m = NULL;
-
- return 0;
-}
-
bool strv_overlap(char * const *a, char * const *b) {
STRV_FOREACH(i, a)
if (strv_contains(b, *i))
return strv_join_full(l, separator, NULL, false);
}
-char** strv_parse_nulstr(const char *s, size_t l);
-char** strv_split_nulstr(const char *s);
-int strv_make_nulstr(char * const *l, char **p, size_t *n);
-
-static inline int strv_from_nulstr(char ***a, const char *nulstr) {
- char **t;
-
- t = strv_split_nulstr(nulstr);
- if (!t)
- return -ENOMEM;
- *a = t;
- return 0;
-}
-
bool strv_overlap(char * const *a, char * const *b) _pure_;
#define _STRV_FOREACH_BACKWARDS(s, l, h, i) \
#include "tmpfile-util.h"
#include "umask-util.h"
-int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_temp_path) {
+static int fopen_temporary_internal(int dir_fd, const char *path, FILE **ret_file) {
_cleanup_fclose_ FILE *f = NULL;
- _cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -1;
int r;
- if (path) {
- r = tempfn_random(path, NULL, &t);
- if (r < 0)
- return r;
- } else {
- const char *d;
-
- r = tmp_dir(&d);
- if (r < 0)
- return r;
-
- r = tempfn_random_child(d, NULL, &t);
- if (r < 0)
- return r;
- }
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
- fd = openat(dir_fd, t, O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL, 0600);
+ fd = openat(dir_fd, path, O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL, 0600);
if (fd < 0)
return -errno;
r = take_fdopen_unlocked(&fd, "w", &f);
if (r < 0) {
- (void) unlinkat(dir_fd, t, 0);
+ (void) unlinkat(dir_fd, path, 0);
return r;
}
if (ret_file)
*ret_file = TAKE_PTR(f);
- if (ret_temp_path)
- *ret_temp_path = TAKE_PTR(t);
+ return 0;
+}
+
+int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ r = tempfn_random(path, NULL, &t);
+ if (r < 0)
+ return r;
+
+ r = fopen_temporary_internal(dir_fd, t, ret_file);
+ if (r < 0)
+ return r;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(t);
+
+ return 0;
+}
+
+int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
+ if (!path) {
+ r = tmp_dir(&path);
+ if (r < 0)
+ return r;
+ }
+
+ r = tempfn_random_child(path, NULL, &t);
+ if (r < 0)
+ return r;
+
+ r = fopen_temporary_internal(dir_fd, t, ret_file);
+ if (r < 0)
+ return r;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(t);
return 0;
}
static inline int fopen_temporary(const char *path, FILE **ret_file, char **ret_path) {
return fopen_temporary_at(AT_FDCWD, path, ret_file, ret_path);
}
+
+int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
+static inline int fopen_temporary_child(const char *path, FILE **ret_file, char **ret_path) {
+ return fopen_temporary_child_at(AT_FDCWD, path, ret_file, ret_path);
+}
+
int mkostemp_safe(char *pattern);
int fmkostemp_safe(char *pattern, const char *mode, FILE**_f);
assert(previous);
assert(is_first);
- r = chase_symlinks_and_opendir(path, esp_path, CHASE_PREFIX_ROOT, &p, &d);
+ r = chase_symlinks_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
assert(n_printed);
r = efi_get_boot_option(id, &title, &partition, &path, &active);
+ if (r == -ENOENT) {
+ log_debug_errno(r, "Boot option 0x%04X referenced but missing, ignoring: %m", id);
+ return 0;
+ }
if (r < 0)
- return log_error_errno(r, "Failed to read boot option %u: %m", id);
+ return log_error_errno(r, "Failed to read boot option 0x%04X: %m", id);
/* print only configured entries with partition information */
if (!path || sd_id128_is_null(partition)) {
- log_debug("Ignoring boot entry %u without partition information.", id);
+ log_debug("Ignoring boot entry 0x%04X without partition information.", id);
return 0;
}
if (!p)
return log_oom();
- r = chase_symlinks(p, root, CHASE_PREFIX_ROOT, &source_path, NULL);
+ r = chase_symlinks(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
/* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
- r = chase_symlinks(p, NULL, CHASE_PREFIX_ROOT, &source_path, NULL);
+ r = chase_symlinks(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
if (r < 0)
return log_error_errno(r,
"Failed to resolve path %s%s%s: %m",
if (!q)
return log_oom();
- r = chase_symlinks(q, esp_path, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &dest_path, NULL);
+ r = chase_symlinks(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path);
v = strjoina("/EFI/BOOT/BOOT", e);
ascii_strupper(strrchr(v, '/') + 1);
- r = chase_symlinks(v, esp_path, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &default_dest_path, NULL);
+ r = chase_symlinks(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path);
_cleanup_free_ char *path = NULL;
int r;
- r = chase_symlinks_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT, &path, &d);
+ r = chase_symlinks_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
/* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
- r = chase_symlinks_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT, &path, &d);
+ r = chase_symlinks_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
if (r < 0)
return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR);
return 0;
}
- r = chase_symlinks_and_access(path, esp_path, CHASE_PREFIX_ROOT, F_OK, NULL, NULL);
+ r = chase_symlinks_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL, NULL);
if (r == -ENOENT)
return 0;
if (r < 0)
_cleanup_free_ char *p = NULL;
int r, c = 0;
- r = chase_symlinks_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT, &p, &d);
+ r = chase_symlinks_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
printf("\n");
printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal());
- have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)), F_OK) >= 0;
- printf(" Passed to OS: %s\n", yes_no(have));
have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0;
printf(" System Token: %s\n", have ? "set" : "not set");
static int install_random_seed(const char *esp) {
_cleanup_(unlink_and_freep) char *tmp = NULL;
- _cleanup_free_ void *buffer = NULL;
+ uint8_t buffer[RANDOM_EFI_SEED_SIZE];
_cleanup_free_ char *path = NULL;
_cleanup_close_ int fd = -1;
- size_t sz, token_size;
+ size_t token_size;
ssize_t n;
int r;
if (!path)
return log_oom();
- sz = random_pool_size();
-
- buffer = malloc(sz);
- if (!buffer)
- return log_oom();
-
- r = crypto_random_bytes(buffer, sz);
+ r = crypto_random_bytes(buffer, sizeof(buffer));
if (r < 0)
return log_error_errno(r, "Failed to acquire random seed: %m");
return log_error_errno(fd, "Failed to open random seed file for writing: %m");
}
- n = write(fd, buffer, sz);
+ n = write(fd, buffer, sizeof(buffer));
if (n < 0)
return log_error_errno(errno, "Failed to write random seed file: %m");
- if ((size_t) n != sz)
+ if ((size_t) n != sizeof(buffer))
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file.");
if (rename(tmp, path) < 0)
tmp = mfree(tmp);
- log_info("Random seed file %s successfully written (%zu bytes).", path, sz);
+ log_info("Random seed file %s successfully written (%zu bytes).", path, sizeof(buffer));
if (!arg_touch_variables)
return 0;
if (r != -ENOENT)
return log_error_errno(r, "Failed to test system token validity: %m");
} else {
- if (token_size >= sz) {
+ if (token_size >= sizeof(buffer)) {
/* Let's avoid writes if we can, and initialize this only once. */
log_debug("System token already written, not updating.");
return 0;
}
- log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sz);
+ log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sizeof(buffer));
}
- r = crypto_random_bytes(buffer, sz);
+ r = crypto_random_bytes(buffer, sizeof(buffer));
if (r < 0)
return log_error_errno(r, "Failed to acquire random seed: %m");
* and possibly get identification information or too much insight into the kernel's entropy pool
* state. */
RUN_WITH_UMASK(0077) {
- r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sz);
+ r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sizeof(buffer));
if (r < 0) {
if (!arg_graceful)
return log_error_errno(r, "Failed to write 'LoaderSystemToken' EFI variable: %m");
else
log_warning_errno(r, "Unable to write 'LoaderSystemToken' EFI variable, ignoring: %m");
} else
- log_info("Successfully initialized system token in EFI variable with %zu bytes.", sz);
+ log_info("Successfully initialized system token in EFI variable with %zu bytes.", sizeof(buffer));
}
return 0;
#include "linux.h"
#include "measure.h"
#include "pe.h"
+#include "vmm.h"
#include "random-seed.h"
#include "secure-boot.h"
#include "shim.h"
config_default_entry_select(config);
}
+static EFI_STATUS discover_root_dir(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI_FILE **ret_dir) {
+ if (is_direct_boot(loaded_image->DeviceHandle))
+ return vmm_open(&loaded_image->DeviceHandle, ret_dir);
+ else
+ return open_volume(loaded_image->DeviceHandle, ret_dir);
+}
+
EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
_cleanup_(file_closep) EFI_FILE *root_dir = NULL;
err = device_path_to_str(loaded_image->FilePath, &loaded_image_path);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error getting loaded image path: %m");
+ return log_error_status_stall(err, L"Error getting loaded image path: %r", err);
export_variables(loaded_image, loaded_image_path, init_usec);
- err = open_volume(loaded_image->DeviceHandle, &root_dir);
+ err = discover_root_dir(loaded_image, &root_dir);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Unable to open root directory: %r", err);
}
EFI_STATUS reconnect_all_drivers(void) {
- _cleanup_free_ EFI_HANDLE *handles = NULL;
- UINTN n_handles = 0;
- EFI_STATUS err;
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+ size_t n_handles = 0;
+ EFI_STATUS err;
- /* Reconnects all handles, so that any loaded drivers can take effect. */
+ /* Reconnects all handles, so that any loaded drivers can take effect. */
- err = BS->LocateHandleBuffer(AllHandles, NULL, NULL, &n_handles, &handles);
- if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to get list of handles: %r", err);
+ err = BS->LocateHandleBuffer(AllHandles, NULL, NULL, &n_handles, &handles);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Failed to get list of handles: %r", err);
- for (UINTN i = 0; i < n_handles; i++) {
- err = BS->ConnectController(handles[i], NULL, NULL, true);
- if (err == EFI_NOT_FOUND) /* No drivers for this handle */
- continue;
- if (err != EFI_SUCCESS)
- log_error_status_stall(err, L"Failed to reconnect handle %" PRIuN L", ignoring: %r", i, err);
- }
+ for (size_t i = 0; i < n_handles; i++)
+ /* 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);
- return EFI_SUCCESS;
+ return EFI_SUCCESS;
}
EFI_STATUS load_drivers(
memcpy(dest, src, n);
return (uint8_t *) dest + n;
}
+
+static inline void explicit_bzero_safe(void *bytes, size_t len) {
+ if (!bytes || len == 0)
+ return;
+ memset(bytes, 0, len);
+ __asm__ __volatile__("": :"r"(bytes) :"memory");
+}
#else
/* For unit testing. */
int efi_memcmp(const void *p1, const void *p2, size_t n);
'boot.c',
'drivers.c',
'random-seed.c',
+ 'vmm.c',
'shim.c',
'xbootldr.c',
)
#define EFI_RNG_GUID &(const EFI_GUID) EFI_RNG_PROTOCOL_GUID
+struct linux_efi_random_seed {
+ uint32_t size;
+ uint8_t seed[];
+};
+
+#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \
+ { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } }
+
/* SHA256 gives us 256/8=32 bytes */
#define HASH_VALUE_SIZE 32
-static EFI_STATUS acquire_rng(UINTN size, void **ret) {
- _cleanup_free_ void *data = NULL;
+/* Linux's RNG is 256 bits, so let's provide this much */
+#define DESIRED_SEED_SIZE 32
+
+/* Some basic domain separation in case somebody uses this data elsewhere */
+#define HASH_LABEL "systemd-boot random seed label v1"
+
+static EFI_STATUS acquire_rng(void *ret, UINTN size) {
EFI_RNG_PROTOCOL *rng;
EFI_STATUS err;
if (!rng)
return EFI_UNSUPPORTED;
- data = xmalloc(size);
-
- err = rng->GetRNG(rng, NULL, size, data);
+ err = rng->GetRNG(rng, NULL, size, ret);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to acquire RNG data: %r", err);
-
- *ret = TAKE_PTR(data);
- return EFI_SUCCESS;
-}
-
-static void hash_once(
- const void *old_seed,
- const void *rng,
- UINTN size,
- const void *system_token,
- UINTN system_token_size,
- uint64_t uefi_monotonic_counter,
- UINTN counter,
- uint8_t ret[static HASH_VALUE_SIZE]) {
-
- /* This hashes together:
- *
- * 1. The contents of the old seed file
- * 2. Some random data acquired from the UEFI RNG (optional)
- * 3. Some 'system token' the installer installed as EFI variable (optional)
- * 4. The UEFI "monotonic counter" that increases with each boot
- * 5. A supplied counter value
- *
- * And writes the result to the specified buffer.
- */
-
- struct sha256_ctx hash;
-
- assert(old_seed);
- assert(system_token_size == 0 || system_token);
-
- sha256_init_ctx(&hash);
- sha256_process_bytes(old_seed, size, &hash);
- if (rng)
- sha256_process_bytes(rng, size, &hash);
- if (system_token_size > 0)
- sha256_process_bytes(system_token, system_token_size, &hash);
- sha256_process_bytes(&uefi_monotonic_counter, sizeof(uefi_monotonic_counter), &hash);
- sha256_process_bytes(&counter, sizeof(counter), &hash);
- sha256_finish_ctx(&hash, ret);
-}
-
-static EFI_STATUS hash_many(
- const void *old_seed,
- const void *rng,
- UINTN size,
- const void *system_token,
- UINTN system_token_size,
- uint64_t uefi_monotonic_counter,
- UINTN counter_start,
- UINTN n,
- void **ret) {
-
- _cleanup_free_ void *output = NULL;
-
- assert(old_seed);
- assert(system_token_size == 0 || system_token);
- assert(ret);
-
- /* Hashes the specified parameters in counter mode, generating n hash values, with the counter in the
- * range counter_start…counter_start+n-1. */
-
- output = xmalloc_multiply(HASH_VALUE_SIZE, n);
-
- for (UINTN i = 0; i < n; i++)
- hash_once(old_seed, rng, size,
- system_token, system_token_size,
- uefi_monotonic_counter,
- counter_start + i,
- (uint8_t*) output + (i * HASH_VALUE_SIZE));
-
- *ret = TAKE_PTR(output);
- return EFI_SUCCESS;
-}
-
-static EFI_STATUS mangle_random_seed(
- const void *old_seed,
- const void *rng,
- UINTN size,
- const void *system_token,
- UINTN system_token_size,
- uint64_t uefi_monotonic_counter,
- void **ret_new_seed,
- void **ret_for_kernel) {
-
- _cleanup_free_ void *new_seed = NULL, *for_kernel = NULL;
- EFI_STATUS err;
- UINTN n;
-
- assert(old_seed);
- assert(system_token_size == 0 || system_token);
- assert(ret_new_seed);
- assert(ret_for_kernel);
-
- /* This takes the old seed file contents, an (optional) random number acquired from the UEFI RNG, an
- * (optional) system 'token' installed once by the OS installer in an EFI variable, and hashes them
- * together in counter mode, generating a new seed (to replace the file on disk) and the seed for the
- * kernel. To keep things simple, the new seed and kernel data have the same size as the old seed and
- * RNG data. */
-
- n = (size + HASH_VALUE_SIZE - 1) / HASH_VALUE_SIZE;
-
- /* Begin hashing in counter mode at counter 0 for the new seed for the disk */
- err = hash_many(old_seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, 0, n, &new_seed);
- if (err != EFI_SUCCESS)
- return err;
-
- /* Continue counting at 'n' for the seed for the kernel */
- err = hash_many(old_seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, n, n, &for_kernel);
- if (err != EFI_SUCCESS)
- return err;
-
- *ret_new_seed = TAKE_PTR(new_seed);
- *ret_for_kernel = TAKE_PTR(for_kernel);
-
return EFI_SUCCESS;
}
}
EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
- _cleanup_free_ void *seed = NULL, *new_seed = NULL, *rng = NULL, *for_kernel = NULL, *system_token = NULL;
+ _cleanup_erase_ uint8_t random_bytes[DESIRED_SEED_SIZE], hash_key[HASH_VALUE_SIZE];
+ _cleanup_free_ struct linux_efi_random_seed *new_seed_table = NULL;
+ struct linux_efi_random_seed *previous_seed_table = NULL;
+ _cleanup_free_ void *seed = NULL, *system_token = NULL;
_cleanup_(file_closep) EFI_FILE *handle = NULL;
- UINTN size, rsize, wsize, system_token_size = 0;
_cleanup_free_ EFI_FILE_INFO *info = NULL;
+ _cleanup_erase_ struct sha256_ctx hash;
uint64_t uefi_monotonic_counter = 0;
+ size_t size, rsize, wsize;
+ bool seeded_by_efi = false;
EFI_STATUS err;
+ EFI_TIME now;
assert(root_dir);
+ assert_cc(DESIRED_SEED_SIZE == HASH_VALUE_SIZE);
validate_sha256();
if (mode == RANDOM_SEED_OFF)
return EFI_NOT_FOUND;
- /* Let's better be safe than sorry, and for now disable this logic in SecureBoot mode, so that we
- * don't credit a random seed that is not authenticated. */
- if (secure_boot_enabled())
- return EFI_NOT_FOUND;
+ /* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */
+ sha256_init_ctx(&hash);
+
+ /* Some basic domain separation in case somebody uses this data elsewhere */
+ sha256_process_bytes(HASH_LABEL, sizeof(HASH_LABEL) - 1, &hash);
+
+ for (size_t i = 0; i < ST->NumberOfTableEntries; ++i)
+ if (memcmp(&(const EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID,
+ &ST->ConfigurationTable[i].VendorGuid, sizeof(EFI_GUID)) == 0) {
+ previous_seed_table = ST->ConfigurationTable[i].VendorTable;
+ break;
+ }
+ if (!previous_seed_table) {
+ size = 0;
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ } else {
+ size = previous_seed_table->size;
+ seeded_by_efi = size >= DESIRED_SEED_SIZE;
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(previous_seed_table->seed, size, &hash);
+
+ /* Zero and free the previous seed table only at the end after we've managed to install a new
+ * one, so that in case this function fails or aborts, Linux still receives whatever the
+ * previous bootloader chain set. So, the next line of this block is not an explicit_bzero()
+ * call. */
+ }
+
+ /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
+ * idea to use it because it helps us for cases where users mistakenly include a random seed in
+ * golden master images that are replicated many times. */
+ err = acquire_rng(random_bytes, sizeof(random_bytes));
+ if (err != EFI_SUCCESS) {
+ size = 0;
+ /* If we can't get any randomness from EFI itself, then we'll only be relying on what's in
+ * ESP. But ESP is mutable, so if secure boot is enabled, we probably shouldn't trust that
+ * alone, in which case we bail out early. */
+ if (!seeded_by_efi && secure_boot_enabled())
+ return EFI_NOT_FOUND;
+ } else {
+ seeded_by_efi = true;
+ size = sizeof(random_bytes);
+ }
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(random_bytes, size, &hash);
/* Get some system specific seed that the installer might have placed in an EFI variable. We include
* it in our hash. This is protection against golden master image sloppiness, and it remains on the
* system, even when disk images are duplicated or swapped out. */
- err = acquire_system_token(&system_token, &system_token_size);
- if (mode != RANDOM_SEED_ALWAYS && err != EFI_SUCCESS)
+ size = 0;
+ err = acquire_system_token(&system_token, &size);
+ if (mode != RANDOM_SEED_ALWAYS && (err != EFI_SUCCESS || size < DESIRED_SEED_SIZE) && !seeded_by_efi)
return err;
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ if (system_token) {
+ sha256_process_bytes(system_token, size, &hash);
+ explicit_bzero_safe(system_token, size);
+ }
err = root_dir->Open(
root_dir,
err = get_file_info_harder(handle, &info, NULL);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to get file info for random seed: %r");
+ return log_error_status_stall(err, L"Failed to get file info for random seed: %r", err);
size = info->FileSize;
if (size < RANDOM_MAX_SIZE_MIN)
return log_error_status_stall(EFI_INVALID_PARAMETER, L"Random seed file is too large.");
seed = xmalloc(size);
-
rsize = size;
err = handle->Read(handle, &rsize, seed);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to read random seed file: %r", err);
- if (rsize != size)
+ if (rsize != size) {
+ explicit_bzero_safe(seed, rsize);
return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short read on random seed file.");
+ }
+
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(seed, size, &hash);
+ explicit_bzero_safe(seed, size);
err = handle->SetPosition(handle, 0);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
- /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
- * idea to use it because it helps us for cases where users mistakenly include a random seed in
- * golden master images that are replicated many times. */
- (void) acquire_rng(size, &rng); /* It's fine if this fails */
-
/* Let's also include the UEFI monotonic counter (which is supposedly increasing on every single
* boot) in the hash, so that even if the changes to the ESP for some reason should not be
* persistent, the random seed we generate will still be different on every single boot. */
err = BS->GetNextMonotonicCount(&uefi_monotonic_counter);
- if (err != EFI_SUCCESS)
+ if (err != EFI_SUCCESS && !seeded_by_efi)
return log_error_status_stall(err, L"Failed to acquire UEFI monotonic counter: %r", err);
+ size = sizeof(uefi_monotonic_counter);
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(&uefi_monotonic_counter, size, &hash);
- /* Calculate new random seed for the disk and what to pass to the kernel */
- err = mangle_random_seed(seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, &new_seed, &for_kernel);
- if (err != EFI_SUCCESS)
- return err;
+ err = RT->GetTime(&now, NULL);
+ size = err == EFI_SUCCESS ? sizeof(now) : 0; /* Known to be flaky, so don't bark on error. */
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(&now, size, &hash);
+
+ /* hash_key = HASH(hash) */
+ sha256_finish_ctx(&hash, hash_key);
+ /* hash = hash_key || 0 */
+ sha256_init_ctx(&hash);
+ sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
+ sha256_process_bytes(&(const uint8_t){ 0 }, sizeof(uint8_t), &hash);
+ /* random_bytes = HASH(hash) */
+ sha256_finish_ctx(&hash, random_bytes);
+
+ size = sizeof(random_bytes);
+ /* If the file size is too large, zero out the remaining bytes on disk. */
+ if (size < info->FileSize) {
+ err = handle->SetPosition(handle, size);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Failed to seek to offset of random seed file: %r", err);
+ wsize = info->FileSize - size;
+ err = handle->Write(handle, &wsize, seed /* All zeros now */);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
+ if (wsize != info->FileSize - size)
+ return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
+ err = handle->Flush(handle);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
+ err = handle->SetPosition(handle, 0);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
+
+ /* We could truncate the file here with something like:
+ *
+ * info->FileSize = size;
+ * err = handle->SetInfo(handle, &GenericFileInfo, info->Size, info);
+ * if (err != EFI_SUCCESS)
+ * return log_error_status_stall(err, L"Failed to truncate random seed file: %r", err);
+ *
+ * But this is considered slightly risky, because EFI filesystem drivers are a little bit
+ * flimsy. So instead we rely on userspace eventually truncating this when it writes a new
+ * seed. For now the best we do is zero it. */
+ }
/* Update the random seed on disk before we use it */
wsize = size;
- err = handle->Write(handle, &wsize, new_seed);
+ err = handle->Write(handle, &wsize, random_bytes);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
if (wsize != size)
return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
-
err = handle->Flush(handle);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
- /* We are good to go */
- err = efivar_set_raw(LOADER_GUID, L"LoaderRandomSeed", for_kernel, size, 0);
+ err = BS->AllocatePool(EfiACPIReclaimMemory,
+ offsetof(struct linux_efi_random_seed, seed) + DESIRED_SEED_SIZE,
+ (void **) &new_seed_table);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Failed to allocate EFI table for random seed: %r", err);
+ new_seed_table->size = DESIRED_SEED_SIZE;
+
+ /* hash = hash_key || 1 */
+ sha256_init_ctx(&hash);
+ sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
+ sha256_process_bytes(&(const uint8_t){ 1 }, sizeof(uint8_t), &hash);
+ /* new_seed_table->seed = HASH(hash) */
+ sha256_finish_ctx(&hash, new_seed_table->seed);
+
+ err = BS->InstallConfigurationTable(&(EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID, new_seed_table);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to write random seed to EFI variable: %r", err);
+ return log_error_status_stall(err, L"Failed to install EFI table for random seed: %r", err);
+ TAKE_PTR(new_seed_table);
+
+ if (previous_seed_table) {
+ /* Now that we've succeeded in installing the new table, we can safely nuke the old one. */
+ explicit_bzero_safe(previous_seed_table->seed, previous_seed_table->size);
+ explicit_bzero_safe(previous_seed_table, sizeof(*previous_seed_table));
+ free(previous_seed_table);
+ }
return EFI_SUCCESS;
}
#define UINTN_MAX (~(UINTN)0)
#define INTN_MAX ((INTN)(UINTN_MAX>>1))
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif
+#if __has_attribute(__error__)
+__attribute__((noreturn)) extern void __assert_cl_failure__(void) __attribute__((__error__("compile-time assertion failed")));
+#else
+__attribute__((noreturn)) extern void __assert_cl_failure__(void);
+#endif
+/* assert_cl generates a later-stage compile-time assertion when constant folding occurs. */
+#define assert_cl(condition) ({ if (!(condition)) __assert_cl_failure__(); })
+
/* gnu-efi format specifiers for integers are fixed to either 64bit with 'l' and 32bit without a size prefix.
* We rely on %u/%d/%x to format regular ints, so ensure the size is what we expect. At the same time, we also
* need specifiers for (U)INTN which are native (pointer) sized. */
#define _cleanup_free_ _cleanup_(freep)
+static __always_inline void erase_obj(void *p) {
+#ifdef __OPTIMIZE__
+ size_t l;
+ assert_cl(p);
+ l = __builtin_object_size(p, 0);
+ assert_cl(l != (size_t) -1);
+ explicit_bzero_safe(p, l);
+#else
+#warning "Object will not be erased with -O0; do not release to production."
+#endif
+}
+
+#define _cleanup_erase_ _cleanup_(erase_obj)
+
_malloc_ _alloc_(1) _returns_nonnull_ _warn_unused_result_
static inline void *xmalloc(size_t size) {
void *p;
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <efi.h>
+#include <efilib.h>
+#include <stdbool.h>
+
+#include "drivers.h"
+#include "efi-string.h"
+#include "string-util-fundamental.h"
+#include "util.h"
+
+#define QEMU_KERNEL_LOADER_FS_MEDIA_GUID \
+ { 0x1428f772, 0xb64a, 0x441e, {0xb8, 0xc3, 0x9e, 0xbd, 0xd7, 0xf8, 0x93, 0xc7 }}
+
+#define VMM_BOOT_ORDER_GUID \
+ { 0x668f4529, 0x63d0, 0x4bb5, {0xb6, 0x5d, 0x6f, 0xbb, 0x9d, 0x36, 0xa4, 0x4a }}
+
+/* detect direct boot */
+bool is_direct_boot(EFI_HANDLE device) {
+ EFI_STATUS err;
+ VENDOR_DEVICE_PATH *dp;
+
+ err = BS->HandleProtocol(device, &DevicePathProtocol, (void **) &dp);
+ if (err != EFI_SUCCESS)
+ return false;
+
+ /* 'qemu -kernel systemd-bootx64.efi' */
+ if (dp->Header.Type == MEDIA_DEVICE_PATH &&
+ dp->Header.SubType == MEDIA_VENDOR_DP &&
+ memcmp(&dp->Guid, &(EFI_GUID)QEMU_KERNEL_LOADER_FS_MEDIA_GUID, sizeof(EFI_GUID)) == 0)
+ return true;
+
+ /* loaded from firmware volume (sd-boot added to ovmf) */
+ if (dp->Header.Type == MEDIA_DEVICE_PATH &&
+ dp->Header.SubType == MEDIA_PIWG_FW_VOL_DP)
+ return true;
+
+ return false;
+}
+
+static bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start) {
+ if (!start)
+ return true;
+ if (!dp)
+ return false;
+ for (;;) {
+ if (IsDevicePathEnd(start))
+ return true;
+ if (IsDevicePathEnd(dp))
+ return false;
+ size_t l1 = DevicePathNodeLength(start);
+ size_t l2 = DevicePathNodeLength(dp);
+ if (l1 != l2)
+ return false;
+ if (memcmp(dp, start, l1) != 0)
+ return false;
+ start = NextDevicePathNode(start);
+ dp = NextDevicePathNode(dp);
+ }
+}
+
+/*
+ * Try find ESP when not loaded from ESP
+ *
+ * Inspect all filesystems known to the firmware, try find the ESP. In case VMMBootOrderNNNN variables are
+ * present they are used to inspect the filesystems in the specified order. When nothing was found or the
+ * variables are not present the function will do one final search pass over all filesystems.
+ *
+ * Recent OVMF builds store the qemu boot order (as specified using the bootindex property on the qemu
+ * command line) in VMMBootOrderNNNN. The variables contain a device path.
+ *
+ * Example qemu command line:
+ * qemu -virtio-scsi-pci,addr=14.0 -device scsi-cd,scsi-id=4,bootindex=1
+ *
+ * Resulting variable:
+ * VMMBootOrder0000 = PciRoot(0x0)/Pci(0x14,0x0)/Scsi(0x4,0x0)
+ */
+EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) {
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+ size_t n_handles;
+ EFI_STATUS err, dp_err;
+
+ assert(ret_vmm_dev);
+ assert(ret_vmm_dir);
+
+ /* find all file system handles */
+ err = BS->LocateHandleBuffer(ByProtocol, &FileSystemProtocol, NULL, &n_handles, &handles);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ for (size_t order = 0;; order++) {
+ _cleanup_free_ EFI_DEVICE_PATH *dp = NULL;
+ char16_t order_str[STRLEN("VMMBootOrder") + 4 + 1];
+
+ SPrint(order_str, sizeof(order_str), u"VMMBootOrder%04x", order);
+ dp_err = efivar_get_raw(&(EFI_GUID)VMM_BOOT_ORDER_GUID, order_str, (char**)&dp, NULL);
+
+ for (size_t i = 0; i < n_handles; i++) {
+ _cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL;
+ EFI_DEVICE_PATH *fs;
+
+ err = BS->HandleProtocol(handles[i], &DevicePathProtocol, (void **) &fs);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* check against VMMBootOrderNNNN (if set) */
+ if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp))
+ continue;
+
+ err = open_volume(handles[i], &root_dir);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ /* simple ESP check */
+ err = root_dir->Open(root_dir, &efi_dir, (char16_t*) u"\\EFI",
+ EFI_FILE_MODE_READ,
+ EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ *ret_vmm_dev = handles[i];
+ *ret_vmm_dir = TAKE_PTR(root_dir);
+ return EFI_SUCCESS;
+ }
+
+ if (dp_err != EFI_SUCCESS)
+ return EFI_NOT_FOUND;
+ }
+ assert_not_reached();
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <efi.h>
+#include <efilib.h>
+
+bool is_direct_boot(EFI_HANDLE device);
+EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir);
}
_cleanup_free_ void *sig = malloc(ss);
- if (!ss) {
+ if (!sig) {
r = log_oom();
goto finish;
}
#include "build.h"
#include "efivars.h"
+#include "env-util.h"
#include "main-func.h"
#include "openssl-util.h"
#include "parse-util.h"
length = strlen(word);
+ int b = getenv_bool("SYSTEMD_PCRPHASE_STUB_VERIFY");
+ if (b < 0 && b != -ENXIO)
+ log_warning_errno(b, "Unable to parse $SYSTEMD_PCRPHASE_STUB_VERIFY value, ignoring.");
+
/* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string);
if (r == -ENOENT) {
- log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE);
- return EXIT_SUCCESS;
- }
- if (r < 0)
+ if (b != 0) {
+ log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE);
+ return EXIT_SUCCESS;
+ } else
+ log_notice("Kernel stub did not measure kernel image into PCR %u, but told to measure anyway, hence proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE);
+ } else if (r < 0)
return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m");
-
- /* Let's validate that the stub announced PCR 11 as we expected. */
- r = safe_atou(pcr_string, &pcr_nr);
- if (r < 0)
- return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string);
- if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE)
- return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
+ else {
+ /* Let's validate that the stub announced PCR 11 as we expected. */
+ r = safe_atou(pcr_string, &pcr_nr);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string);
+ if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) {
+ if (b != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
+ else
+ log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
+ } else
+ log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE);
+ }
r = dlopen_tpm2();
if (r < 0)
"/run/systemd/inaccessible/blk\0" "rwm\0";
int r = 0, k;
- const char *node, *acc;
NULSTR_FOREACH_PAIR(node, acc, auto_devices) {
k = bpf_devices_allow_list_device(prog, path, node, acc);
if (r >= 0 && k < 0)
if (name[0] == '@') {
const FilesystemSet *set;
- const char *i;
set = filesystem_set_find(name);
if (!set) {
.memory_high = CGROUP_LIMIT_MAX,
.memory_max = CGROUP_LIMIT_MAX,
.memory_swap_max = CGROUP_LIMIT_MAX,
+ .memory_zswap_max = CGROUP_LIMIT_MAX,
.memory_limit = CGROUP_LIMIT_MAX,
} else if (streq(property_name, "MemorySwapMax")) {
unit_value = c->memory_swap_max;
file = "memory.swap.max";
+ } else if (streq(property_name, "MemoryZSwapMax")) {
+ unit_value = c->memory_zswap_max;
+ file = "memory.zswap.max";
} else
return -EINVAL;
/* memory.swap.max is special in that it relies on CONFIG_MEMCG_SWAP (and the default swapaccount=1).
* In the absence of reliably being able to detect whether memcg swap support is available or not,
- * only complain if the error is not ENOENT. */
+ * only complain if the error is not ENOENT. This is similarly the case for memory.zswap.max relying
+ * on CONFIG_ZSWAP. */
if (r > 0 || IN_SET(r, -ENODATA, -EOWNERDEAD) ||
- (r == -ENOENT && streq(property_name, "MemorySwapMax")))
+ (r == -ENOENT && STR_IN_SET(property_name, "MemorySwapMax", "MemoryZSwapMax")))
buf[0] = 0;
else if (r < 0) {
errno = -r;
"%sMemoryHigh: %" PRIu64 "%s\n"
"%sMemoryMax: %" PRIu64 "%s\n"
"%sMemorySwapMax: %" PRIu64 "%s\n"
+ "%sMemoryZSwapMax: %" PRIu64 "%s\n"
"%sMemoryLimit: %" PRIu64 "\n"
"%sTasksMax: %" PRIu64 "\n"
"%sDevicePolicy: %s\n"
prefix, c->memory_high, format_cgroup_memory_limit_comparison(cdc, sizeof(cdc), u, "MemoryHigh"),
prefix, c->memory_max, format_cgroup_memory_limit_comparison(cdd, sizeof(cdd), u, "MemoryMax"),
prefix, c->memory_swap_max, format_cgroup_memory_limit_comparison(cde, sizeof(cde), u, "MemorySwapMax"),
+ prefix, c->memory_zswap_max, format_cgroup_memory_limit_comparison(cde, sizeof(cde), u, "MemoryZSwapMax"),
prefix, c->memory_limit,
prefix, tasks_max_resolve(&c->tasks_max),
prefix, cgroup_device_policy_to_string(c->device_policy),
return unit_get_ancestor_memory_min(u) > 0 || unit_get_ancestor_memory_low(u) > 0 ||
c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX ||
- c->memory_swap_max != CGROUP_LIMIT_MAX;
+ c->memory_swap_max != CGROUP_LIMIT_MAX || c->memory_zswap_max != CGROUP_LIMIT_MAX;
}
static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) {
if ((apply_mask & CGROUP_MASK_MEMORY) && !is_local_root) {
if (cg_all_unified() > 0) {
- uint64_t max, swap_max = CGROUP_LIMIT_MAX;
+ uint64_t max, swap_max = CGROUP_LIMIT_MAX, zswap_max = CGROUP_LIMIT_MAX;
if (unit_has_unified_memory_config(u)) {
max = c->memory_max;
swap_max = c->memory_swap_max;
+ zswap_max = c->memory_zswap_max;
} else {
max = c->memory_limit;
cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high);
cgroup_apply_unified_memory_limit(u, "memory.max", max);
cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max);
+ cgroup_apply_unified_memory_limit(u, "memory.zswap.max", zswap_max);
(void) set_attribute_and_warn(u, "memory", "memory.oom.group", one_zero(c->memory_oom_group));
uint64_t memory_high;
uint64_t memory_max;
uint64_t memory_swap_max;
+ uint64_t memory_zswap_max;
bool default_memory_min_set:1;
bool default_memory_low_set:1;
if (getpid_cached() != 1)
/* Pass this on immediately, if this is not PID 1 */
- (void) raise(sig);
+ propagate_signal(sig, siginfo);
else if (!arg_dump_core)
log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig));
else {
(void) chdir("/");
/* Raise the signal again */
- pid = raw_getpid();
- (void) kill(pid, sig); /* raise() would kill the parent */
-
+ propagate_signal(sig, siginfo);
assert_not_reached();
_exit(EXIT_EXCEPTION);
} else {
SD_BUS_PROPERTY("MemoryHigh", "t", NULL, offsetof(CGroupContext, memory_high), 0),
SD_BUS_PROPERTY("MemoryMax", "t", NULL, offsetof(CGroupContext, memory_max), 0),
SD_BUS_PROPERTY("MemorySwapMax", "t", NULL, offsetof(CGroupContext, memory_swap_max), 0),
+ SD_BUS_PROPERTY("MemoryZSwapMax", "t", NULL, offsetof(CGroupContext, memory_zswap_max), 0),
SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0),
SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0),
SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0),
BUS_DEFINE_SET_CGROUP_LIMIT(memory, CGROUP_MASK_MEMORY, physical_memory_scale, 1);
BUS_DEFINE_SET_CGROUP_LIMIT(memory_protection, CGROUP_MASK_MEMORY, physical_memory_scale, 0);
BUS_DEFINE_SET_CGROUP_LIMIT(swap, CGROUP_MASK_MEMORY, physical_memory_scale, 0);
+BUS_DEFINE_SET_CGROUP_LIMIT(zswap, CGROUP_MASK_MEMORY, physical_memory_scale, 0);
REENABLE_WARNING;
static int bus_cgroup_set_cpu_weight(
if (streq(name, "MemorySwapMax"))
return bus_cgroup_set_swap(u, name, &c->memory_swap_max, message, flags, error);
+ if (streq(name, "MemoryZSwapMax"))
+ return bus_cgroup_set_zswap(u, name, &c->memory_zswap_max, message, flags, error);
+
if (streq(name, "MemoryMax"))
return bus_cgroup_set_memory(u, name, &c->memory_max, message, flags, error);
if (streq(name, "MemorySwapMaxScale"))
return bus_cgroup_set_swap_scale(u, name, &c->memory_swap_max, message, flags, error);
+ if (streq(name, "MemoryZSwapMaxScale"))
+ return bus_cgroup_set_zswap_scale(u, name, &c->memory_zswap_max, message, flags, error);
+
if (streq(name, "MemoryMaxScale"))
return bus_cgroup_set_memory_scale(u, name, &c->memory_max, message, flags, error);
#include "random-util.h"
#include "strv.h"
-/* If a random seed was passed by the boot loader in the LoaderRandomSeed EFI variable, let's credit it to
- * the kernel's random pool, but only once per boot. If this is run very early during initialization we can
- * instantly boot up with a filled random pool.
- *
- * This makes no judgement on the entropy passed, it's the job of the boot loader to only pass us a seed that
- * is suitably validated. */
-
-static void lock_down_efi_variables(void) {
+void lock_down_efi_variables(void) {
+ _cleanup_close_ int fd = -1;
int r;
+ fd = open(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Unable to open LoaderSystemToken EFI variable, ignoring: %m");
+ return;
+ }
+
/* Paranoia: let's restrict access modes of these a bit, so that unprivileged users can't use them to
* identify the system or gain too much insight into what we might have credited to the entropy
* pool. */
- FOREACH_STRING(path,
- EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)),
- EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken))) {
-
- r = chattr_path(path, 0, FS_IMMUTABLE_FL, NULL);
- if (r == -ENOENT)
- continue;
- if (r < 0)
- log_warning_errno(r, "Failed to drop FS_IMMUTABLE_FL from %s, ignoring: %m", path);
-
- if (chmod(path, 0600) < 0)
- log_warning_errno(errno, "Failed to reduce access mode of %s, ignoring: %m", path);
- }
-}
-
-int efi_take_random_seed(void) {
- _cleanup_free_ void *value = NULL;
- size_t size;
- int r;
-
- /* Paranoia comes first. */
- lock_down_efi_variables();
-
- if (access("/run/systemd/efi-random-seed-taken", F_OK) < 0) {
- if (errno != ENOENT) {
- log_warning_errno(errno, "Failed to determine whether we already used the random seed token, not using it.");
- return 0;
- }
-
- /* ENOENT means we haven't used it yet. */
- } else {
- log_debug("EFI random seed already used, not using again.");
- return 0;
- }
-
- r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderRandomSeed), NULL, &value, &size);
- if (r == -EOPNOTSUPP) {
- log_debug_errno(r, "System lacks EFI support, not initializing random seed from EFI variable.");
- return 0;
- }
- if (r == -ENOENT) {
- log_debug_errno(r, "Boot loader did not pass LoaderRandomSeed EFI variable, not crediting any entropy.");
- return 0;
- }
+ r = chattr_fd(fd, 0, FS_IMMUTABLE_FL, NULL);
if (r < 0)
- return log_warning_errno(r, "Failed to read LoaderRandomSeed EFI variable, ignoring: %m");
-
- if (size == 0)
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Random seed passed from boot loader has zero size? Ignoring.");
-
- /* Before we use the seed, let's mark it as used, so that we never credit it twice. Also, it's a nice
- * way to let users known that we successfully acquired entropy from the boot loader. */
- r = touch("/run/systemd/efi-random-seed-taken");
- if (r < 0)
- return log_warning_errno(r, "Unable to mark EFI random seed as used, not using it: %m");
-
- r = random_write_entropy(-1, value, size, true);
- if (r < 0)
- return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m");
-
- log_info("Successfully credited entropy passed from boot loader.");
- return 1;
+ log_warning_errno(r, "Failed to drop FS_IMMUTABLE_FL from LoaderSystemToken EFI variable, ignoring: %m");
+ if (fchmod(fd, 0600) < 0)
+ log_warning_errno(errno, "Failed to reduce access mode of LoaderSystemToken EFI variable, ignoring: %m");
}
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
-int efi_take_random_seed(void);
+void lock_down_efi_variables(void);
#include "alloc-util.h"
#include "bus-util.h"
#include "capability-util.h"
+#include "efi-api.h"
#include "fileio.h"
#include "kmod-setup.h"
#include "macro.h"
} kmod_table[] = {
/* This one we need to load explicitly, since auto-loading on use doesn't work
* before udev created the ghost device nodes, and we need it earlier than that. */
- { "autofs4", "/sys/class/misc/autofs", true, false, NULL },
+ { "autofs4", "/sys/class/misc/autofs", true, false, NULL },
/* This one we need to load explicitly, since auto-loading of IPv6 is not done when
* we try to configure ::1 on the loopback device. */
- { "ipv6", "/sys/module/ipv6", false, true, NULL },
+ { "ipv6", "/sys/module/ipv6", false, true, NULL },
/* This should never be a module */
- { "unix", "/proc/net/unix", true, true, NULL },
+ { "unix", "/proc/net/unix", true, true, NULL },
#if HAVE_LIBIPTC
/* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */
- { "ip_tables", "/proc/net/ip_tables_names", false, false, NULL },
+ { "ip_tables", "/proc/net/ip_tables_names", false, false, NULL },
#endif
/* virtio_rng would be loaded by udev later, but real entropy might be needed very early */
- { "virtio_rng", NULL, false, false, has_virtio_rng },
+ { "virtio_rng", NULL, false, false, has_virtio_rng },
/* qemu_fw_cfg would be loaded by udev later, but we want to import credentials from it super early */
- { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu },
+ { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu },
/* dmi-sysfs is needed to import credentials from it super early */
- { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL },
+ { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL },
+
+#if HAVE_TPM2
+ /* Make sure the tpm subsystem is available which ConditionSecurity=tpm2 depends on. */
+ { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 },
+#endif
};
_cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
unsigned i;
{{type}}.MemoryHigh, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context)
{{type}}.MemoryMax, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context)
{{type}}.MemorySwapMax, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context)
+{{type}}.MemoryZSwapMax, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context)
{{type}}.MemoryLimit, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context)
{{type}}.DeviceAllow, config_parse_device_allow, 0, offsetof({{type}}, cgroup_context)
{{type}}.DevicePolicy, config_parse_device_policy, 0, offsetof({{type}}, cgroup_context.device_policy)
bytes = physical_memory_scale(r, 10000U);
if (bytes >= UINT64_MAX ||
- (bytes <= 0 && !STR_IN_SET(lvalue, "MemorySwapMax", "MemoryLow", "MemoryMin", "DefaultMemoryLow", "DefaultMemoryMin"))) {
+ (bytes <= 0 && !STR_IN_SET(lvalue, "MemorySwapMax", "MemoryZSwapMax", "MemoryLow", "MemoryMin", "DefaultMemoryLow", "DefaultMemoryMin"))) {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Memory limit '%s' out of range, ignoring.", rvalue);
return 0;
}
c->memory_max = bytes;
else if (streq(lvalue, "MemorySwapMax"))
c->memory_swap_max = bytes;
+ else if (streq(lvalue, "MemoryZSwapMax"))
+ c->memory_zswap_max = bytes;
else if (streq(lvalue, "MemoryLimit")) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unit uses MemoryLimit=; please use MemoryMax= instead. Support for MemoryLimit= will be removed soon.");
};
const char *prev = NULL;
- const char *i;
assert(f);
goto finish;
}
- /* The efivarfs is now mounted, let's read the random seed off it */
- (void) efi_take_random_seed();
+ /* The efivarfs is now mounted, let's lock down the system token. */
+ lock_down_efi_variables();
/* Cache command-line options passed from EFI variables */
if (!skip_setup)
"/dev/tty\0";
char temporary_mount[] = "/tmp/namespace-dev-XXXXXX";
- const char *d, *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL;
+ const char *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL;
bool can_mknod = true;
int r;
"ID_MODEL\0";
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
- const char *i, *name;
+ const char *name;
struct stat st;
assert(path);
static int should_skip_path(const char *prefix, const char *suffix) {
#if HAVE_SPLIT_USR
_cleanup_free_ char *target = NULL, *dirname = NULL;
- const char *p;
dirname = path_join(prefix, suffix);
if (!dirname)
}
static int process_suffix(const char *suffix, const char *onlyprefix) {
- const char *p;
char *f, *key;
OrderedHashmap *top, *bottom, *drops, *h;
int r = 0, k, n_found = 0;
}
static int process_suffixes(const char *onlyprefix) {
- const char *n;
int n_found = 0, r;
NULSTR_FOREACH(n, suffixes) {
}
static int process_suffix_chop(const char *arg) {
- const char *p;
-
assert(arg);
if (!path_is_absolute(arg))
pager_open(arg_pager_flags);
if (arg_json_format_flags & JSON_FORMAT_OFF)
- printf(" Name: %s\n", bn);
+ printf(" Name: %s%s%s\n", ansi_highlight(), bn, ansi_normal());
if (ioctl(d->fd, BLKGETSIZE64, &size) < 0)
log_debug_errno(errno, "Failed to query size of loopback device: %m");
else if (arg_json_format_flags & JSON_FORMAT_OFF) {
_cleanup_strv_free_ char **sysext_scopes = NULL;
+ if (!sd_id128_is_null(m->image_uuid))
+ printf("Image UUID: %s\n", SD_ID128_TO_UUID_STRING(m->image_uuid));
+
if (m->hostname)
printf(" Hostname: %s\n", m->hostname);
r = json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("name", JSON_BUILD_STRING(bn)),
+ JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->image_uuid), "imageUuid", JSON_BUILD_UUID(m->image_uuid)),
JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)),
JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)),
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
if (errno != ENOENT)
return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target);
- r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS);
+ r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL);
} else
- r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS);
+ r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL);
if (r < 0)
return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
size_t sw_alloc = MAX(h->sw_alloc, 1u);
buf2 = malloc(sw_alloc);
- if (!buf) {
+ if (!buf2) {
log_oom();
return 0;
}
pid_t mount_pid;
int exit_status;
- r = fopen_temporary(NULL, &f, &p);
+ r = fopen_temporary_child(NULL, &f, &p);
if (r < 0)
return log_error_errno(r, "Failed to create temporary credentials file: %m");
size_t *ret_volume_key_size) {
_cleanup_free_ char *xattr_buf = NULL;
- const char *xa;
int r;
assert(setup);
_cleanup_free_ char *xattr_buf = NULL;
size_t volume_key_size = 0;
uint32_t slot = 0;
- const char *xa;
int r;
assert(h);
assert(root_fd >= 0);
- r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE);
+ r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE, NULL);
if (r == -ENOENT) {
log_info("Skeleton directory %s missing, ignoring.", skel);
return 0;
assert(i);
- table = table_new("key", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
assert_se(cell = table_get_cell(table, 0, 0));
(void) table_set_ellipsize_percent(table, cell, 100);
- (void) table_set_align_percent(table, cell, 100);
-
- table_set_header(table, false);
table_set_ersatz_string(table, TABLE_ERSATZ_UNSET);
r = table_add_many(table,
- TABLE_STRING, "Static hostname:",
+ TABLE_FIELD, "Static hostname",
TABLE_STRING, i->static_hostname);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->pretty_hostname) &&
!streq_ptr(i->pretty_hostname, i->static_hostname)) {
r = table_add_many(table,
- TABLE_STRING, "Pretty hostname:",
+ TABLE_FIELD, "Pretty hostname",
TABLE_STRING, i->pretty_hostname);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->hostname) &&
!streq_ptr(i->hostname, i->static_hostname)) {
r = table_add_many(table,
- TABLE_STRING, "Transient hostname:",
+ TABLE_FIELD, "Transient hostname",
TABLE_STRING, i->hostname);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->icon_name)) {
r = table_add_many(table,
- TABLE_STRING, "Icon name:",
+ TABLE_FIELD, "Icon name",
TABLE_STRING, i->icon_name);
if (r < 0)
return table_log_add_error(r);
v = strjoina(i->chassis, " ", v);
r = table_add_many(table,
- TABLE_STRING, "Chassis:",
+ TABLE_FIELD, "Chassis",
TABLE_STRING, v ?: i->chassis);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->deployment)) {
r = table_add_many(table,
- TABLE_STRING, "Deployment:",
+ TABLE_FIELD, "Deployment",
TABLE_STRING, i->deployment);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->location)) {
r = table_add_many(table,
- TABLE_STRING, "Location:",
+ TABLE_FIELD, "Location",
TABLE_STRING, i->location);
if (r < 0)
return table_log_add_error(r);
r = sd_id128_get_machine(&mid);
if (r >= 0) {
r = table_add_many(table,
- TABLE_STRING, "Machine ID:",
+ TABLE_FIELD, "Machine ID",
TABLE_ID128, mid);
if (r < 0)
return table_log_add_error(r);
r = sd_id128_get_boot(&bid);
if (r >= 0) {
r = table_add_many(table,
- TABLE_STRING, "Boot ID:",
+ TABLE_FIELD, "Boot ID",
TABLE_ID128, bid);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->virtualization)) {
r = table_add_many(table,
- TABLE_STRING, "Virtualization:",
+ TABLE_FIELD, "Virtualization",
TABLE_STRING, i->virtualization);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->os_pretty_name)) {
r = table_add_many(table,
- TABLE_STRING, "Operating System:",
+ TABLE_FIELD, "Operating System",
TABLE_STRING, i->os_pretty_name,
TABLE_SET_URL, i->home_url);
if (r < 0)
if (!isempty(i->os_cpe_name)) {
r = table_add_many(table,
- TABLE_STRING, "CPE OS Name:",
+ TABLE_FIELD, "CPE OS Name",
TABLE_STRING, i->os_cpe_name);
if (r < 0)
return table_log_add_error(r);
v = strjoina(i->kernel_name, " ", i->kernel_release);
r = table_add_many(table,
- TABLE_STRING, "Kernel:",
+ TABLE_FIELD, "Kernel",
TABLE_STRING, v);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->architecture)) {
r = table_add_many(table,
- TABLE_STRING, "Architecture:",
+ TABLE_FIELD, "Architecture",
TABLE_STRING, i->architecture);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->hardware_vendor)) {
r = table_add_many(table,
- TABLE_STRING, "Hardware Vendor:",
+ TABLE_FIELD, "Hardware Vendor",
TABLE_STRING, i->hardware_vendor);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->hardware_model)) {
r = table_add_many(table,
- TABLE_STRING, "Hardware Model:",
+ TABLE_FIELD, "Hardware Model",
TABLE_STRING, i->hardware_model);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->firmware_version)) {
r = table_add_many(table,
- TABLE_STRING, "Firmware Version:",
+ TABLE_FIELD, "Firmware Version",
TABLE_STRING, i->firmware_version);
if (r < 0)
return table_log_add_error(r);
if (have_uuid)
id = gpt_partition_type_uuid_to_string(uuid) ?: "XYZ";
else {
- r = gpt_partition_type_uuid_from_string(*p, &uuid);
+ GptPartitionType type;
+
+ r = gpt_partition_type_from_string(*p, &type);
if (r < 0)
return log_error_errno(r, "Unknown identifier \"%s\".", *p);
+ uuid = type.uuid;
id = *p;
}
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
BTRFS_SNAPSHOT_RECURSIVE);
else
- r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS);
+ r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL);
if (r < 0)
return log_error_errno(r, "Failed to create local image: %m");
int mhd_respondf(struct MHD_Connection *connection,
int error,
- unsigned code,
+ enum MHD_RequestTerminationCode code,
const char *format, ...) _printf_(4,5);
int mhd_respond(struct MHD_Connection *connection,
- unsigned code,
+ enum MHD_RequestTerminationCode code,
const char *message);
int mhd_respond_oom(struct MHD_Connection *connection);
Set **units) {
_cleanup_set_free_free_ Set *found = NULL;
- const char *field;
int r;
found = set_new(&string_hash_ops);
}
if (!NLMSG_OK(nl, buffer_size)) {
- log_error("Audit netlink message truncated.");
+ log_ratelimit_error(JOURNALD_LOG_RATELIMIT, "Audit netlink message truncated.");
return;
}
r = client_context_acquire(s, ucred.pid, &ucred, NULL, 0, NULL, &s->my_context);
if (r < 0)
- log_warning_errno(r, "Failed to acquire our own context, ignoring: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to acquire our own context, ignoring: %m");
}
if (!s->namespace && !s->pid1_context) {
r = client_context_acquire(s, 1, NULL, NULL, 0, NULL, &s->pid1_context);
if (r < 0)
- log_warning_errno(r, "Failed to acquire PID1's context, ignoring: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to acquire PID1's context, ignoring: %m");
}
}
if (ERRNO_IS_TRANSIENT(errno) || errno == EPIPE)
return 0;
- return log_error_errno(errno, "Failed to read from /dev/kmsg: %m");
+ return log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT, "Failed to read from /dev/kmsg: %m");
}
dev_kmsg_record(s, buffer, l);
assert(fd == s->dev_kmsg_fd);
if (revents & EPOLLERR)
- log_warning("/dev/kmsg buffer overrun, some messages lost.");
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT,
+ "/dev/kmsg buffer overrun, some messages lost.");
if (!(revents & EPOLLIN))
log_error("Got invalid event from epoll for /dev/kmsg: %"PRIx32, revents);
if (ucred && pid_is_valid(ucred->pid)) {
r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context);
if (r < 0)
- log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m",
+ ucred->pid);
}
do {
r = fd_get_path(fd, &k);
if (r < 0) {
- log_error_errno(r, "readlink(/proc/self/fd/%i) failed: %m", fd);
+ log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT,
+ "readlink(/proc/self/fd/%i) failed: %m", fd);
return;
}
e = PATH_STARTSWITH_SET(k, "/dev/shm/", "/tmp/", "/var/tmp/");
if (!e) {
- log_error("Received file outside of allowed directories. Refusing.");
+ log_ratelimit_error(JOURNALD_LOG_RATELIMIT,
+ "Received file outside of allowed directories. Refusing.");
return;
}
if (!filename_is_valid(e)) {
- log_error("Received file in subdirectory of allowed directories. Refusing.");
+ log_ratelimit_error(JOURNALD_LOG_RATELIMIT,
+ "Received file in subdirectory of allowed directories. Refusing.");
return;
}
}
if (fstat(fd, &st) < 0) {
- log_error_errno(errno, "Failed to stat passed file, ignoring: %m");
+ log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to stat passed file, ignoring: %m");
return;
}
if (!S_ISREG(st.st_mode)) {
- log_error("File passed is not regular. Ignoring.");
+ log_ratelimit_error(JOURNALD_LOG_RATELIMIT,
+ "File passed is not regular. Ignoring.");
return;
}
/* When !sealed, set a lower memory limit. We have to read the file,
* effectively doubling memory use. */
if (st.st_size > ENTRY_SIZE_MAX / (sealed ? 1 : 2)) {
- log_error("File passed too large (%"PRIu64" bytes). Ignoring.", (uint64_t) st.st_size);
+ log_ratelimit_error(JOURNALD_LOG_RATELIMIT,
+ "File passed too large (%"PRIu64" bytes). Ignoring.",
+ (uint64_t) st.st_size);
return;
}
ps = PAGE_ALIGN(st.st_size);
p = mmap(NULL, ps, PROT_READ, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) {
- log_error_errno(errno, "Failed to map memfd, ignoring: %m");
+ log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to map memfd, ignoring: %m");
return;
}
ssize_t n;
if (fstatvfs(fd, &vfs) < 0) {
- log_error_errno(errno, "Failed to stat file system of passed file, not processing it: %m");
+ log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to stat file system of passed file, not processing it: %m");
return;
}
* https://github.com/systemd/systemd/issues/1822
*/
if (vfs.f_flag & ST_MANDLOCK) {
- log_error("Received file descriptor from file system with mandatory locking enabled, not processing it.");
+ log_ratelimit_error(JOURNALD_LOG_RATELIMIT,
+ "Received file descriptor from file system with mandatory locking enabled, not processing it.");
return;
}
* and so is SMB. */
r = fd_nonblock(fd, true);
if (r < 0) {
- log_error_errno(r, "Failed to make fd non-blocking, not processing it: %m");
+ log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to make fd non-blocking, not processing it: %m");
return;
}
n = pread(fd, p, st.st_size, 0);
if (n < 0)
- log_error_errno(errno, "Failed to read file, ignoring: %m");
+ log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to read file, ignoring: %m");
else if (n > 0)
server_process_native_message(s, p, n, ucred, tv, label, label_len);
}
#define IDLE_TIMEOUT_USEC (30*USEC_PER_SEC)
+#define FAILED_TO_WRITE_ENTRY_RATELIMIT ((RateLimit) { .interval = 1 * USEC_PER_SEC, .burst = 1 })
+
static int determine_path_usage(
Server *s,
const char *path,
d = opendir(path);
if (!d)
- return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR,
- errno, "Failed to open %s: %m", path);
+ return log_ratelimit_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR,
+ errno, JOURNALD_LOG_RATELIMIT, "Failed to open %s: %m", path);
if (fstatvfs(dirfd(d), &ss) < 0)
- return log_error_errno(errno, "Failed to fstatvfs(%s): %m", path);
+ return log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to fstatvfs(%s): %m", path);
*ret_free = ss.f_bsize * ss.f_bavail;
*ret_used = 0;
r = fd_add_uid_acl_permission(f->file->fd, uid, ACL_READ);
if (r < 0)
- log_warning_errno(r, "Failed to set ACL on %s, ignoring: %m", f->file->path);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to set ACL on %s, ignoring: %m", f->file->path);
#endif
}
patch_min_use(&s->system_storage);
} else {
if (!IN_SET(r, -ENOENT, -EROFS))
- log_warning_errno(r, "Failed to open system journal: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to open system journal: %m");
r = 0;
}
r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_storage.metrics, &s->runtime_journal);
if (r < 0) {
if (r != -ENOENT)
- log_warning_errno(r, "Failed to open runtime journal: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to open runtime journal: %m");
r = 0;
}
r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_storage.metrics, &s->runtime_journal);
if (r < 0)
- return log_error_errno(r, "Failed to open runtime journal: %m");
+ return log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to open runtime journal: %m");
}
if (s->runtime_journal) {
r = managed_journal_file_rotate(f, s->mmap, file_flags, s->compress.threshold_bytes, s->deferred_closes);
if (r < 0) {
if (*f)
- return log_error_errno(r, "Failed to rotate %s: %m", (*f)->file->path);
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to rotate %s: %m", (*f)->file->path);
else
- return log_error_errno(r, "Failed to create new %s journal: %m", name);
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to create new %s journal: %m", name);
}
server_add_acls(*f, uid);
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open %s: %m", s->system_storage.path);
+ return log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to open %s: %m", s->system_storage.path);
}
for (;;) {
de = readdir_no_dot(d);
if (!de) {
if (errno != 0)
- log_warning_errno(errno, "Failed to enumerate %s, ignoring: %m", s->system_storage.path);
+ log_ratelimit_warning_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to enumerate %s, ignoring: %m",
+ s->system_storage.path);
break;
}
fd = openat(dirfd(d), de->d_name, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
if (fd < 0) {
- log_full_errno(IN_SET(errno, ELOOP, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
- "Failed to open journal file '%s' for rotation: %m", full);
+ log_ratelimit_full_errno(IN_SET(errno, ELOOP, ENOENT) ? LOG_DEBUG : LOG_WARNING,
+ errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to open journal file '%s' for rotation: %m", full);
continue;
}
NULL,
&f);
if (r < 0) {
- log_warning_errno(r, "Failed to read journal file %s for rotation, trying to move it out of the way: %m", full);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to read journal file %s for rotation, trying to move it out of the way: %m",
+ full);
r = journal_file_dispose(dirfd(d), de->d_name);
if (r < 0)
- log_warning_errno(r, "Failed to move %s out of the way, ignoring: %m", full);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to move %s out of the way, ignoring: %m",
+ full);
else
log_debug("Successfully moved %s out of the way.", full);
if (s->system_journal) {
r = managed_journal_file_set_offline(s->system_journal, false);
if (r < 0)
- log_warning_errno(r, "Failed to sync system journal, ignoring: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to sync system journal, ignoring: %m");
}
ORDERED_HASHMAP_FOREACH(f, s->user_journals) {
r = managed_journal_file_set_offline(f, false);
if (r < 0)
- log_warning_errno(r, "Failed to sync user journal, ignoring: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to sync user journal, ignoring: %m");
}
if (s->sync_event_source) {
r = sd_event_source_set_enabled(s->sync_event_source, SD_EVENT_OFF);
if (r < 0)
- log_error_errno(r, "Failed to disable sync timer source: %m");
+ log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to disable sync timer source: %m");
}
s->sync_scheduled = false;
storage->metrics.n_max_files, s->max_retention_usec,
&s->oldest_file_usec, verbose);
if (r < 0 && r != -ENOENT)
- log_warning_errno(r, "Failed to vacuum %s, ignoring: %m", storage->path);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to vacuum %s, ignoring: %m", storage->path);
cache_space_invalidate(&storage->space);
}
return true;
case -EIO: /* I/O error of some kind (mmap) */
- log_warning("%s: IO error, rotating.", f->path);
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT, "%s: IO error, rotating.", f->path);
return true;
case -EHOSTDOWN: /* Other machine */
- log_info("%s: Journal file from other machine, rotating.", f->path);
+ log_ratelimit_info(JOURNALD_LOG_RATELIMIT, "%s: Journal file from other machine, rotating.", f->path);
return true;
case -EBUSY: /* Unclean shutdown */
- log_info("%s: Unclean shutdown, rotating.", f->path);
+ log_ratelimit_info(JOURNALD_LOG_RATELIMIT, "%s: Unclean shutdown, rotating.", f->path);
return true;
case -EPROTONOSUPPORT: /* Unsupported feature */
- log_info("%s: Unsupported feature, rotating.", f->path);
+ log_ratelimit_info(JOURNALD_LOG_RATELIMIT, "%s: Unsupported feature, rotating.", f->path);
return true;
case -EBADMSG: /* Corrupted */
case -ENODATA: /* Truncated */
case -ESHUTDOWN: /* Already archived */
- log_warning("%s: Journal file corrupted, rotating.", f->path);
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT, "%s: Journal file corrupted, rotating.", f->path);
return true;
case -EIDRM: /* Journal file has been deleted */
- log_warning("%s: Journal file has been deleted, rotating.", f->path);
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT, "%s: Journal file has been deleted, rotating.", f->path);
return true;
case -ETXTBSY: /* Journal file is from the future */
- log_warning("%s: Journal file is from the future, rotating.", f->path);
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT, "%s: Journal file is from the future, rotating.", f->path);
return true;
case -EAFNOSUPPORT:
- log_warning("%s: underlying file system does not support memory mapping or another required file system feature.", f->path);
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT,
+ "%s: underlying file system does not support memory mapping or another required file system feature.",
+ f->path);
return false;
default:
* to ensure that the entries in the journal files are strictly ordered by time, in order to ensure
* bisection works correctly. */
- log_info("Time jumped backwards, rotating.");
+ log_ratelimit_info(JOURNALD_LOG_RATELIMIT, "Time jumped backwards, rotating.");
rotate = true;
} else {
return;
if (journal_file_rotate_suggested(f->file, s->max_file_usec, LOG_INFO)) {
- log_info("%s: Journal header limits reached or header out-of-date, rotating.", f->file->path);
+ log_ratelimit_info(JOURNALD_LOG_RATELIMIT,
+ "%s: Journal header limits reached or header out-of-date, rotating.",
+ f->file->path);
rotate = true;
}
}
}
if (vacuumed || !shall_try_append_again(f->file, r)) {
- log_ratelimit_full_errno(LOG_ERR, r, "Failed to write entry (%zu items, %zu bytes), ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n));
+ log_ratelimit_error_errno(r, FAILED_TO_WRITE_ENTRY_RATELIMIT,
+ "Failed to write entry (%zu items, %zu bytes), ignoring: %m",
+ n, IOVEC_TOTAL_SIZE(iovec, n));
return;
}
if (r == -E2BIG)
log_debug("Journal file %s is full, rotating to a new file", f->file->path);
else
- log_ratelimit_full_errno(LOG_INFO, r, "Failed to write entry to %s (%zu items, %zu bytes), rotating before retrying: %m", f->file->path, n, IOVEC_TOTAL_SIZE(iovec, n));
+ log_ratelimit_info_errno(r, FAILED_TO_WRITE_ENTRY_RATELIMIT,
+ "Failed to write entry to %s (%zu items, %zu bytes), rotating before retrying: %m",
+ f->file->path, n, IOVEC_TOTAL_SIZE(iovec, n));
server_rotate(s);
server_vacuum(s, false);
if (!f)
return;
- log_debug("Retrying write.");
+ log_debug_errno(r, "Retrying write.");
r = journal_file_append_entry(f->file, &ts, NULL, iovec, n, &s->seqnum, NULL, NULL);
if (r < 0)
- log_ratelimit_full_errno(LOG_ERR, r, "Failed to write entry to %s (%zu items, %zu bytes) despite vacuuming, ignoring: %m", f->file->path, n, IOVEC_TOTAL_SIZE(iovec, n));
+ log_ratelimit_error_errno(r, FAILED_TO_WRITE_ENTRY_RATELIMIT,
+ "Failed to write entry to %s (%zu items, %zu bytes) despite vacuuming, ignoring: %m",
+ f->file->path, n, IOVEC_TOTAL_SIZE(iovec, n));
else
server_schedule_sync(s, priority);
}
r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY);
if (r < 0)
- return log_error_errno(r, "Failed to read runtime journal: %m");
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to read runtime journal: %m");
sd_journal_set_data_threshold(j, 0);
r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
if (r < 0) {
- log_error_errno(r, "Can't read entry: %m");
+ log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT, "Can't read entry: %m");
goto finish;
}
continue;
if (!shall_try_append_again(s->system_journal->file, r)) {
- log_error_errno(r, "Can't write entry: %m");
+ log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT, "Can't write entry: %m");
goto finish;
}
- log_info("Rotating system journal.");
+ log_ratelimit_info(JOURNALD_LOG_RATELIMIT, "Rotating system journal.");
server_rotate(s);
server_vacuum(s, false);
if (!s->system_journal) {
- log_notice("Didn't flush runtime journal since rotation of system journal wasn't successful.");
+ log_ratelimit_notice(JOURNALD_LOG_RATELIMIT,
+ "Didn't flush runtime journal since rotation of system journal wasn't successful.");
r = -EIO;
goto finish;
}
log_debug("Retrying write.");
r = journal_file_copy_entry(f, s->system_journal->file, o, f->current_offset);
if (r < 0) {
- log_error_errno(r, "Can't write entry: %m");
+ log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT, "Can't write entry: %m");
goto finish;
}
}
fn = strjoina(s->runtime_directory, "/flushed");
k = touch(fn);
if (k < 0)
- log_warning_errno(k, "Failed to touch %s, ignoring: %m", fn);
+ log_ratelimit_warning_errno(k, JOURNALD_LOG_RATELIMIT,
+ "Failed to touch %s, ignoring: %m", fn);
server_refresh_idle_timer(s);
return r;
fn = strjoina(s->runtime_directory, "/flushed");
if (unlink(fn) < 0 && errno != ENOENT)
- log_warning_errno(errno, "Failed to unlink %s, ignoring: %m", fn);
+ log_ratelimit_warning_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to unlink %s, ignoring: %m", fn);
server_refresh_idle_timer(s);
return 0;
if (ERRNO_IS_TRANSIENT(n))
return 0;
if (n == -EXFULL) {
- log_warning("Got message with truncated control data (too many fds sent?), ignoring.");
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT,
+ "Got message with truncated control data (too many fds sent?), ignoring.");
return 0;
}
- return log_error_errno(n, "recvmsg() failed: %m");
+ return log_ratelimit_error_errno(n, JOURNALD_LOG_RATELIMIT, "recvmsg() failed: %m");
}
CMSG_FOREACH(cmsg, &msghdr)
if (n > 0 && n_fds == 0)
server_process_syslog_message(s, s->buffer, n, ucred, tv, label, label_len);
else if (n_fds > 0)
- log_warning("Got file descriptors via syslog socket. Ignoring.");
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT,
+ "Got file descriptors via syslog socket. Ignoring.");
} else if (fd == s->native_fd) {
if (n > 0 && n_fds == 0)
else if (n == 0 && n_fds == 1)
server_process_native_file(s, fds[0], ucred, tv, label, label_len);
else if (n_fds > 0)
- log_warning("Got too many file descriptors via native socket. Ignoring.");
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT,
+ "Got too many file descriptors via native socket. Ignoring.");
} else {
assert(fd == s->audit_fd);
if (n > 0 && n_fds == 0)
server_process_audit_message(s, s->buffer, n, ucred, &sa, msghdr.msg_namelen);
else if (n_fds > 0)
- log_warning("Got file descriptors via audit socket. Ignoring.");
+ log_ratelimit_warning(JOURNALD_LOG_RATELIMIT,
+ "Got file descriptors via audit socket. Ignoring.");
}
close_many(fds, n_fds);
fn = strjoina(s->runtime_directory, "/rotated");
r = write_timestamp_file_atomic(fn, now(CLOCK_MONOTONIC));
if (r < 0)
- log_warning_errno(r, "Failed to write %s, ignoring: %m", fn);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to write %s, ignoring: %m", fn);
}
static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
fn = strjoina(s->runtime_directory, "/synced");
r = write_timestamp_file_atomic(fn, now(CLOCK_MONOTONIC));
if (r < 0)
- log_warning_errno(r, "Failed to write %s, ignoring: %m", fn);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to write %s, ignoring: %m", fn);
return;
}
#include "time-util.h"
#include "varlink.h"
+#define JOURNALD_LOG_RATELIMIT ((RateLimit) { .interval = 60 * USEC_PER_SEC, .burst = 3 })
+
typedef enum Storage {
STORAGE_AUTO,
STORAGE_VOLATILE,
r = fstat(s->fd, &st);
if (r < 0)
- return log_warning_errno(errno, "Failed to stat connected stream: %m");
+ return log_ratelimit_warning_errno(errno, JOURNALD_LOG_RATELIMIT,
+ "Failed to stat connected stream: %m");
/* We use device and inode numbers as identifier for the stream */
r = asprintf(&s->state_file, "%s/streams/%lu:%lu", s->server->runtime_directory, (unsigned long) st.st_dev, (unsigned long) st.st_ino);
if (s->server->notify_event_source) {
r = sd_event_source_set_enabled(s->server->notify_event_source, SD_EVENT_ON);
if (r < 0)
- log_warning_errno(r, "Failed to enable notify event source: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT, "Failed to enable notify event source: %m");
}
}
fail:
(void) unlink(s->state_file);
- return log_error_errno(r, "Failed to save stream data %s: %m", s->state_file);
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to save stream data %s: %m", s->state_file);
}
static int stdout_stream_log(
else if (pid_is_valid(s->ucred.pid)) {
r = client_context_acquire(s->server, s->ucred.pid, &s->ucred, s->label, strlen_ptr(s->label), s->unit_id, &s->context);
if (r < 0)
- log_warning_errno(r, "Failed to acquire client context, ignoring: %m");
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to acquire client context, ignoring: %m");
}
priority = s->priority;
/* line breaks by NUL, line max length or EOF are not permissible during the negotiation part of the protocol */
if (line_break != LINE_BREAK_NEWLINE && s->state != STDOUT_STREAM_RUNNING)
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "Control protocol line not properly terminated.");
+ return log_ratelimit_warning_errno(SYNTHETIC_ERRNO(EINVAL), JOURNALD_LOG_RATELIMIT,
+ "Control protocol line not properly terminated.");
switch (s->state) {
priority = syslog_parse_priority_and_facility(p);
if (priority < 0)
- return log_warning_errno(priority, "Failed to parse log priority line: %m");
+ return log_ratelimit_warning_errno(priority, JOURNALD_LOG_RATELIMIT,
+ "Failed to parse log priority line: %m");
s->priority = priority;
s->state = STDOUT_STREAM_LEVEL_PREFIX;
case STDOUT_STREAM_LEVEL_PREFIX:
r = parse_boolean(p);
if (r < 0)
- return log_warning_errno(r, "Failed to parse level prefix line: %m");
+ return log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to parse level prefix line: %m");
s->level_prefix = r;
s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG;
case STDOUT_STREAM_FORWARD_TO_SYSLOG:
r = parse_boolean(p);
if (r < 0)
- return log_warning_errno(r, "Failed to parse forward to syslog line: %m");
+ return log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to parse forward to syslog line: %m");
s->forward_to_syslog = r;
s->state = STDOUT_STREAM_FORWARD_TO_KMSG;
case STDOUT_STREAM_FORWARD_TO_KMSG:
r = parse_boolean(p);
if (r < 0)
- return log_warning_errno(r, "Failed to parse copy to kmsg line: %m");
+ return log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to parse copy to kmsg line: %m");
s->forward_to_kmsg = r;
s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE;
case STDOUT_STREAM_FORWARD_TO_CONSOLE:
r = parse_boolean(p);
if (r < 0)
- return log_warning_errno(r, "Failed to parse copy to console line.");
+ return log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to parse copy to console line.");
s->forward_to_console = r;
s->state = STDOUT_STREAM_RUNNING;
if (ERRNO_IS_TRANSIENT(errno))
return 0;
- log_warning_errno(errno, "Failed to read from stream: %m");
+ log_ratelimit_warning_errno(errno, JOURNALD_LOG_RATELIMIT, "Failed to read from stream: %m");
goto terminate;
}
cmsg_close_all(&msghdr);
r = sd_id128_randomize(&id);
if (r < 0)
- return log_error_errno(r, "Failed to generate stream ID: %m");
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT, "Failed to generate stream ID: %m");
stream = new(StdoutStream, 1);
if (!stream)
r = getpeercred(fd, &stream->ucred);
if (r < 0)
- return log_error_errno(r, "Failed to determine peer credentials: %m");
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT, "Failed to determine peer credentials: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
if (mac_selinux_use()) {
r = getpeersec(fd, &stream->label);
if (r < 0 && r != -EOPNOTSUPP)
- (void) log_warning_errno(r, "Failed to determine peer security context: %m");
+ (void) log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT, "Failed to determine peer security context: %m");
}
(void) shutdown(fd, SHUT_WR);
r = sd_event_add_io(s->event, &stream->event_source, fd, EPOLLIN, stdout_stream_process, stream);
if (r < 0)
- return log_error_errno(r, "Failed to add stream to event loop: %m");
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT, "Failed to add stream to event loop: %m");
r = sd_event_source_set_priority(stream->event_source, SD_EVENT_PRIORITY_NORMAL+5);
if (r < 0)
- return log_error_errno(r, "Failed to adjust stdout event source priority: %m");
+ return log_ratelimit_error_errno(r, JOURNALD_LOG_RATELIMIT, "Failed to adjust stdout event source priority: %m");
stream->fd = fd;
if (ERRNO_IS_ACCEPT_AGAIN(errno))
return 0;
- return log_error_errno(errno, "Failed to accept stdout connection: %m");
+ return log_ratelimit_error_errno(errno, JOURNALD_LOG_RATELIMIT, "Failed to accept stdout connection: %m");
}
if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
if (ucred && pid_is_valid(ucred->pid)) {
r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context);
if (r < 0)
- log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid);
+ log_ratelimit_warning_errno(r, JOURNALD_LOG_RATELIMIT,
+ "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m",
+ ucred->pid);
}
/* We are creating a copy of the message because we want to forward the original message
*/
assert_ret(sd_journal_open_directory(&j, t, 0));
assert_ret(sd_journal_seek_head(j));
- assert_ret(sd_journal_previous(j) == 0);
assert_ret(sd_journal_next(j));
test_check_numbers_down(j, 4);
sd_journal_close(j);
*/
assert_ret(sd_journal_open_directory(&j, t, 0));
assert_ret(sd_journal_seek_tail(j));
- assert_ret(sd_journal_next(j) == 0);
assert_ret(sd_journal_previous(j));
test_check_numbers_up(j, 4);
sd_journal_close(j);
*/
assert_ret(sd_journal_open_directory(&j, t, 0));
assert_ret(sd_journal_seek_tail(j));
- assert_ret(sd_journal_next(j) == 0);
assert_ret(r = sd_journal_previous_skip(j, 4));
assert_se(r == 4);
test_check_numbers_down(j, 4);
*/
assert_ret(sd_journal_open_directory(&j, t, 0));
assert_ret(sd_journal_seek_head(j));
- assert_ret(sd_journal_previous(j) == 0);
assert_ret(r = sd_journal_next_skip(j, 4));
assert_se(r == 4);
test_check_numbers_up(j, 4);
[ -n "$MACHINE_ID" ] && \
log_verbose "machine-id $MACHINE_ID acquired from /etc/machine-info"
fi
-if [ -z "$MACHINE_ID" ] && [ -f /etc/machine-id ]; then
+if [ -z "$MACHINE_ID" ] && [ -s /etc/machine-id ]; then
read -r MACHINE_ID </etc/machine-id
+ [ "$MACHINE_ID" = "uninitialized" ] && unset MACHINE_ID
[ -n "$MACHINE_ID" ] && \
log_verbose "machine-id $MACHINE_ID acquired from /etc/machine-id"
fi
#include "fileio.h"
#include "format-util.h"
#include "hexdecoct.h"
+#include "nulstr-util.h"
#include "parse-util.h"
#include "process-util.h"
#include "string-util.h"
s);
if (r < 0)
- return r;
+ goto finish;
/* Make the slot of the match call floating now. We need the reference, but we don't
* want that this match pins the bus object, hence we first create it non-floating, but
return -ENOMEM;
size_t i = 0;
- char *p;
NULSTR_FOREACH(p, buf_nulstr)
buf_strv[i++] = p;
assert(i == num);
_cleanup_(sd_device_unrefp) sd_device *device = NULL, *from_nulstr = NULL;
_cleanup_free_ char *nulstr_copy = NULL;
- const char *devlink, *nulstr;
+ const char *nulstr;
size_t len;
assert_se(sd_device_new_from_syspath(&device, "/sys/class/net/lo") >= 0);
if (!hwdb->f)
return log_debug_errno(errno, "Failed to open %s: %m", path);
} else {
- NULSTR_FOREACH(path, hwdb_bin_paths) {
- log_debug("Trying to open \"%s\"...", path);
- hwdb->f = fopen(path, "re");
- if (hwdb->f)
+ NULSTR_FOREACH(p, hwdb_bin_paths) {
+ log_debug("Trying to open \"%s\"...", p);
+ hwdb->f = fopen(p, "re");
+ if (hwdb->f) {
+ path = p;
break;
+ }
if (errno != ENOENT)
- return log_debug_errno(errno, "Failed to open %s: %m", path);
+ return log_debug_errno(errno, "Failed to open %s: %m", p);
}
if (!hwdb->f)
return 1;
}
+/* Ideally this would be a function parameter but initializers for static fields have to be compile
+ * time constants so we hardcode the interval instead. */
+#define LOG_RATELIMIT ((RateLimit) { .interval = 60 * USEC_PER_SEC, .burst = 3 })
+
bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec, int log_level) {
assert(f);
assert(f->header);
if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
- log_full(log_level,
- "Data hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items, %llu file size, %"PRIu64" bytes per hash table item), suggesting rotation.",
- f->path,
- 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
- le64toh(f->header->n_data),
- le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
- (unsigned long long) f->last_stat.st_size,
- f->last_stat.st_size / le64toh(f->header->n_data));
+ log_ratelimit_full(
+ log_level, LOG_RATELIMIT,
+ "Data hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items, %llu file size, %"PRIu64" bytes per hash table item), suggesting rotation.",
+ f->path,
+ 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
+ le64toh(f->header->n_data),
+ le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
+ (unsigned long long) f->last_stat.st_size,
+ f->last_stat.st_size / le64toh(f->header->n_data));
return true;
}
if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
- log_full(log_level,
- "Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.",
- f->path,
- 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
- le64toh(f->header->n_fields),
- le64toh(f->header->field_hash_table_size) / sizeof(HashItem));
+ log_ratelimit_full(
+ log_level, LOG_RATELIMIT,
+ "Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.",
+ f->path,
+ 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
+ le64toh(f->header->n_fields),
+ le64toh(f->header->field_hash_table_size) / sizeof(HashItem));
return true;
}
* longest chain is longer than some threshold, let's suggest rotation. */
if (JOURNAL_HEADER_CONTAINS(f->header, data_hash_chain_depth) &&
le64toh(f->header->data_hash_chain_depth) > HASH_CHAIN_DEPTH_MAX) {
- log_full(log_level,
- "Data hash table of %s has deepest hash chain of length %" PRIu64 ", suggesting rotation.",
- f->path, le64toh(f->header->data_hash_chain_depth));
+ log_ratelimit_full(
+ log_level, LOG_RATELIMIT,
+ "Data hash table of %s has deepest hash chain of length %" PRIu64 ", suggesting rotation.",
+ f->path, le64toh(f->header->data_hash_chain_depth));
return true;
}
if (JOURNAL_HEADER_CONTAINS(f->header, field_hash_chain_depth) &&
le64toh(f->header->field_hash_chain_depth) > HASH_CHAIN_DEPTH_MAX) {
- log_full(log_level,
- "Field hash table of %s has deepest hash chain of length at %" PRIu64 ", suggesting rotation.",
- f->path, le64toh(f->header->field_hash_chain_depth));
+ log_ratelimit_full(
+ log_level, LOG_RATELIMIT,
+ "Field hash table of %s has deepest hash chain of length at %" PRIu64 ", suggesting rotation.",
+ f->path, le64toh(f->header->field_hash_chain_depth));
return true;
}
JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
le64toh(f->header->n_data) > 0 &&
le64toh(f->header->n_fields) == 0) {
- log_full(log_level,
- "Data objects of %s are not indexed by field objects, suggesting rotation.",
- f->path);
+ log_ratelimit_full(
+ log_level, LOG_RATELIMIT,
+ "Data objects of %s are not indexed by field objects, suggesting rotation.",
+ f->path);
return true;
}
t = now(CLOCK_REALTIME);
if (h > 0 && t > h + max_file_usec) {
- log_full(log_level,
- "Oldest entry in %s is older than the configured file retention duration (%s), suggesting rotation.",
- f->path, FORMAT_TIMESPAN(max_file_usec, USEC_PER_SEC));
+ log_ratelimit_full(
+ log_level, LOG_RATELIMIT,
+ "Oldest entry in %s is older than the configured file retention duration (%s), suggesting rotation.",
+ f->path, FORMAT_TIMESPAN(max_file_usec, USEC_PER_SEC));
return true;
}
}
/* FIXME: missing: find by monotonic */
if (j->current_location.type == LOCATION_HEAD)
- return direction == DIRECTION_DOWN ? journal_file_next_entry_for_data(f, d, DIRECTION_DOWN, ret, offset) : 0;
+ return journal_file_next_entry_for_data(f, d, DIRECTION_DOWN, ret, offset);
if (j->current_location.type == LOCATION_TAIL)
- return direction == DIRECTION_UP ? journal_file_next_entry_for_data(f, d, DIRECTION_UP, ret, offset) : 0;
+ return journal_file_next_entry_for_data(f, d, DIRECTION_UP, ret, offset);
if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
return journal_file_move_to_entry_by_seqnum_for_data(f, d, j->current_location.seqnum, direction, ret, offset);
if (j->current_location.monotonic_set) {
/* No matches is simple */
if (j->current_location.type == LOCATION_HEAD)
- return direction == DIRECTION_DOWN ? journal_file_next_entry(f, 0, DIRECTION_DOWN, ret, offset) : 0;
+ return journal_file_next_entry(f, 0, DIRECTION_DOWN, ret, offset);
if (j->current_location.type == LOCATION_TAIL)
- return direction == DIRECTION_UP ? journal_file_next_entry(f, 0, DIRECTION_UP, ret, offset) : 0;
+ return journal_file_next_entry(f, 0, DIRECTION_UP, ret, offset);
if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset);
if (j->current_location.monotonic_set) {
static const char search_paths[] =
"/run/log/journal\0"
"/var/log/journal\0";
- const char *p;
assert(j);
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
+#include "nulstr-util.h"
#include "path-lookup.h"
#include "path-util.h"
#include "string-util.h"
return log_error_errno(r, "Failed to build locale settings from kernel command line: %m");
}
- table = table_new("key", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
assert_se(cell = table_get_cell(table, 0, 0));
(void) table_set_ellipsize_percent(table, cell, 100);
- (void) table_set_align_percent(table, cell, 100);
-
- table_set_header(table, false);
table_set_ersatz_string(table, TABLE_ERSATZ_UNSET);
if (!strv_isempty(kernel_locale)) {
log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.");
r = table_add_many(table,
- TABLE_STRING, "Command Line:",
+ TABLE_FIELD, "Command Line",
TABLE_SET_COLOR, ansi_highlight_yellow(),
TABLE_STRV, kernel_locale,
TABLE_SET_COLOR, ansi_highlight_yellow());
}
r = table_add_many(table,
- TABLE_STRING, "System Locale:",
+ TABLE_FIELD, "System Locale",
TABLE_STRV, i->locale,
- TABLE_STRING, "VC Keymap:",
+ TABLE_FIELD, "VC Keymap",
TABLE_STRING, i->vconsole_keymap);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->vconsole_keymap_toggle)) {
r = table_add_many(table,
- TABLE_STRING, "VC Toggle Keymap:",
+ TABLE_FIELD, "VC Toggle Keymap",
TABLE_STRING, i->vconsole_keymap_toggle);
if (r < 0)
return table_log_add_error(r);
}
r = table_add_many(table,
- TABLE_STRING, "X11 Layout:",
+ TABLE_FIELD, "X11 Layout",
TABLE_STRING, i->x11_layout);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->x11_model)) {
r = table_add_many(table,
- TABLE_STRING, "X11 Model:",
+ TABLE_FIELD, "X11 Model",
TABLE_STRING, i->x11_model);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->x11_variant)) {
r = table_add_many(table,
- TABLE_STRING, "X11 Variant:",
+ TABLE_FIELD, "X11 Variant",
TABLE_STRING, i->x11_variant);
if (r < 0)
return table_log_add_error(r);
if (!isempty(i->x11_options)) {
r = table_add_many(table,
- TABLE_STRING, "X11 Options:",
+ TABLE_FIELD, "X11 Options",
TABLE_STRING, i->x11_options);
if (r < 0)
return table_log_add_error(r);
}
int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
- const char *dir;
_cleanup_free_ char *n = NULL;
if (x11_variant)
* 0 or to the actual UID shift depending on the direction we copy. If no UID shift is set we'll copy
* the UID/GIDs as they are. */
if (copy_from)
- r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, uid_shift == 0 ? UID_INVALID : 0, uid_shift == 0 ? GID_INVALID : 0, copy_flags);
+ r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, uid_shift == 0 ? UID_INVALID : 0, uid_shift == 0 ? GID_INVALID : 0, copy_flags, NULL);
else
- r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, uid_shift == 0 ? UID_INVALID : uid_shift, uid_shift == 0 ? GID_INVALID : uid_shift, copy_flags);
+ r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, uid_shift == 0 ? UID_INVALID : uid_shift, uid_shift == 0 ? GID_INVALID : uid_shift, copy_flags, NULL);
hostfd = safe_close(hostfd);
containerfd = safe_close(containerfd);
static int call_get_os_release(sd_bus *bus, const char *method, const char *name, const char *query, ...) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- const char *k, *v, *iter, **query_res = NULL;
+ const char *k, *v, **query_res = NULL;
size_t count = 0, awaited_args = 0;
va_list ap;
int r;
{ 0, "sched_getparam" },
{ 0, "sched_getscheduler" },
{ 0, "sched_rr_get_interval" },
+ { 0, "sched_rr_get_interval_time64" },
{ 0, "sched_yield" },
{ 0, "seccomp" },
{ 0, "sendfile" },
"tty\0"
"net/tun\0";
- const char *d;
int r = 0;
assert(dest);
#include "mkfs-util.h"
#include "mount-util.h"
#include "mountpoint-util.h"
+#include "nulstr-util.h"
#include "openssl-util.h"
#include "parse-argument.h"
#include "parse-helpers.h"
#include "utf8.h"
/* If not configured otherwise use a minimal partition size of 10M */
-#define DEFAULT_MIN_SIZE (10*1024*1024)
+#define DEFAULT_MIN_SIZE (10ULL*1024ULL*1024ULL)
/* Hard lower limit for new partition sizes */
-#define HARD_MIN_SIZE 4096
+#define HARD_MIN_SIZE 4096ULL
/* We know up front we're never going to put more than this in a verity sig partition. */
-#define VERITY_SIG_SIZE (HARD_MIN_SIZE * 4)
+#define VERITY_SIG_SIZE (HARD_MIN_SIZE*4ULL)
/* libfdisk takes off slightly more than 1M of the disk size when creating a GPT disk label */
-#define GPT_METADATA_SIZE (1044*1024)
+#define GPT_METADATA_SIZE (1044ULL*1024ULL)
/* LUKS2 takes off 16M of the partition size with its metadata by default */
-#define LUKS2_METADATA_SIZE (16*1024*1024)
+#define LUKS2_METADATA_SIZE (16ULL*1024ULL*1024ULL)
+
+/* To do LUKS2 offline encryption, we need to keep some extra free space at the end of the partition. */
+#define LUKS2_METADATA_KEEP_FREE (LUKS2_METADATA_SIZE*2ULL)
+
+/* LUKS2 volume key size. */
+#define VOLUME_KEY_SIZE (512ULL/8ULL)
/* Note: When growing and placing new partitions we always align to 4K sector size. It's how newer hard disks
* are designed, and if everything is aligned to that performance is best. And for older hard disks with 512B
EMPTY_CREATE, /* create disk as loopback file, create a partition table always */
} arg_empty = EMPTY_REFUSE;
+typedef enum {
+ FILTER_PARTITIONS_NONE,
+ FILTER_PARTITIONS_EXCLUDE,
+ FILTER_PARTITIONS_INCLUDE,
+ _FILTER_PARTITIONS_MAX,
+ _FILTER_PARTITIONS_INVALID = -EINVAL,
+} FilterPartitionsType;
+
static bool arg_dry_run = true;
static const char *arg_node = NULL;
static char *arg_root = NULL;
static char *arg_tpm2_public_key = NULL;
static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
static bool arg_split = false;
+static sd_id128_t *arg_filter_partitions = NULL;
+static size_t arg_filter_partitions_size = 0;
+static FilterPartitionsType arg_filter_partitions_type = FILTER_PARTITIONS_NONE;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep);
typedef struct Partition Partition;
typedef struct FreeArea FreeArea;
char *definition_path;
char **drop_in_files;
- sd_id128_t type_uuid;
+ GptPartitionType type;
sd_id128_t current_uuid, new_uuid;
bool new_uuid_is_set;
char *current_label, *new_label;
+ sd_id128_t fs_uuid;
bool dropped;
bool factory_reset;
char *copy_blocks_path;
bool copy_blocks_auto;
+ const char *copy_blocks_root;
int copy_blocks_fd;
uint64_t copy_blocks_size;
EncryptMode encrypt;
VerityMode verity;
char *verity_match_key;
+ bool minimize;
uint64_t gpt_flags;
int no_auto;
[VERITY_SIG] = "signature",
};
-#if HAVE_LIBCRYPTSETUP
-DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
-#else
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
-#endif
-
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode);
static uint64_t round_down_size(uint64_t v, uint64_t p) {
/* Reset several parameters set through definition file to make the partition foreign. */
- p->new_label = mfree(p->new_label);
p->definition_path = mfree(p->definition_path);
p->drop_in_files = strv_free(p->drop_in_files);
p->copy_blocks_path = mfree(p->copy_blocks_path);
p->copy_blocks_fd = safe_close(p->copy_blocks_fd);
+ p->copy_blocks_root = NULL;
p->format = mfree(p->format);
p->copy_files = strv_free(p->copy_files);
p->make_directories = strv_free(p->make_directories);
p->verity_match_key = mfree(p->verity_match_key);
- p->new_uuid = SD_ID128_NULL;
- p->new_uuid_is_set = false;
p->priority = 0;
p->weight = 1000;
p->padding_weight = 0;
p->verity = VERITY_OFF;
}
+static bool partition_skip(const Partition *p) {
+ assert(p);
+
+ if (arg_filter_partitions_type == FILTER_PARTITIONS_NONE)
+ return false;
+
+ for (size_t i = 0; i < arg_filter_partitions_size; i++)
+ if (sd_id128_equal(p->type.uuid, arg_filter_partitions[i]))
+ return arg_filter_partitions_type == FILTER_PARTITIONS_EXCLUDE;
+
+ return arg_filter_partitions_type == FILTER_PARTITIONS_INCLUDE;
+}
+
static Partition* partition_unlink_and_free(Context *context, Partition *p) {
if (!p)
return NULL;
uint64_t d = 0;
if (p->encrypt != ENCRYPT_OFF)
- d += round_up_size(LUKS2_METADATA_SIZE, context->grain_size);
+ d += round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size);
if (p->copy_blocks_size != UINT64_MAX)
d += round_up_size(p->copy_blocks_size, context->grain_size);
return 0;
}
-static void context_place_partitions(Context *context) {
+static uint64_t find_first_unused_partno(Context *context) {
uint64_t partno = 0;
assert(context);
- /* Determine next partition number to assign */
- LIST_FOREACH(partitions, p, context->partitions) {
- if (!PARTITION_EXISTS(p))
- continue;
+ for (bool changed = true; changed;) {
+ changed = false;
- assert(p->partno != UINT64_MAX);
- if (p->partno >= partno)
- partno = p->partno + 1;
+ LIST_FOREACH(partitions, p, context->partitions) {
+ if (p->partno != UINT64_MAX && p->partno == partno) {
+ partno++;
+ changed = true;
+ break;
+ }
+ }
}
+ return partno;
+}
+
+static void context_place_partitions(Context *context) {
+
+ assert(context);
+
for (size_t i = 0; i < context->n_free_areas; i++) {
FreeArea *a = context->free_areas[i];
_unused_ uint64_t left;
continue;
p->offset = start;
- p->partno = partno++;
+ p->partno = find_first_unused_partno(context);
assert(left >= p->new_size);
start += p->new_size;
void *data,
void *userdata) {
- sd_id128_t *type_uuid = ASSERT_PTR(data);
+ GptPartitionType *type = ASSERT_PTR(data);
int r;
assert(rvalue);
- r = gpt_partition_type_uuid_from_string(rvalue, type_uuid);
+ r = gpt_partition_type_from_string(rvalue, type);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse partition type: %s", rvalue);
if (streq(rvalue, "auto")) {
partition->copy_blocks_path = mfree(partition->copy_blocks_path);
partition->copy_blocks_auto = true;
+ partition->copy_blocks_root = arg_root;
return 0;
}
free_and_replace(partition->copy_blocks_path, d);
partition->copy_blocks_auto = false;
+ partition->copy_blocks_root = arg_root;
return 0;
}
static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
ConfigTableItem table[] = {
- { "Partition", "Type", config_parse_type, 0, &p->type_uuid },
+ { "Partition", "Type", config_parse_type, 0, &p->type },
{ "Partition", "Label", config_parse_label, 0, &p->new_label },
{ "Partition", "UUID", config_parse_uuid, 0, p },
{ "Partition", "Priority", config_parse_int32, 0, &p->priority },
{ "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto },
{ "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs },
{ "Partition", "SplitName", config_parse_string, 0, &p->split_name_format },
+ { "Partition", "Minimize", config_parse_bool, 0, &p->minimize },
{}
};
int r;
r = path_extract_filename(path, &filename);
if (r < 0)
- return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);;
+ return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
dropin_dirname = strjoina(filename, ".d");
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"PaddingMinBytes= larger than PaddingMaxBytes=, refusing.");
- if (sd_id128_is_null(p->type_uuid))
+ if (sd_id128_is_null(p->type.uuid))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Type= not defined, refusing.");
return log_oom();
}
+ if (p->minimize && !p->format)
+ return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+ "Minimize= can only be enabled if Format= is set");
+
+ if ((!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)) && !mkfs_supports_root_option(p->format) && geteuid() != 0)
+ return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM),
+ "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=",
+ p->format);
+
+ if (p->format && fstype_is_ro(p->format) && strv_isempty(p->copy_files) && strv_isempty(p->make_directories))
+ return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+ "Cannot format %s filesystem without source files, refusing", p->format);
+
if (p->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) {
r = dlopen_cryptsetup();
if (r < 0)
verity_mode_to_string(p->verity));
/* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */
- if ((gpt_partition_type_is_root_verity(p->type_uuid) ||
- gpt_partition_type_is_usr_verity(p->type_uuid)) &&
+ if ((p->type.designator == PARTITION_ROOT_VERITY ||
+ p->type.designator == PARTITION_USR_VERITY) &&
p->read_only < 0)
p->read_only = true;
/* Default to "growfs" on, unless read-only */
- if (gpt_partition_type_knows_growfs(p->type_uuid) &&
+ if (gpt_partition_type_knows_growfs(p->type) &&
p->read_only <= 0)
p->growfs = true;
LIST_FOREACH(partitions, pp, context->partitions) {
last = pp;
- if (!sd_id128_equal(pp->type_uuid, ptid))
+ if (!sd_id128_equal(pp->type.uuid, ptid))
continue;
if (!pp->current_partition) {
return log_oom();
np->current_uuid = id;
- np->type_uuid = ptid;
+ np->type = gpt_partition_type_from_uuid(ptid);
np->current_size = sz;
np->offset = start;
np->partno = partno;
if (p->current_label)
return p->current_label;
- return gpt_partition_type_uuid_to_string(p->type_uuid);
+ return gpt_partition_type_uuid_to_string(p->type.uuid);
}
static int context_dump_partitions(Context *context, const char *node) {
r = table_add_many(
t,
- TABLE_STRING, gpt_partition_type_uuid_to_string_harder(p->type_uuid, uuid_buffer),
+ TABLE_STRING, gpt_partition_type_uuid_to_string_harder(p->type.uuid, uuid_buffer),
TABLE_STRING, empty_to_null(label) ?: "-", TABLE_SET_COLOR, empty_to_null(label) ? NULL : ansi_grey(),
TABLE_UUID, p->new_uuid_is_set ? p->new_uuid : p->current_uuid,
TABLE_STRING, p->definition_path ? basename(p->definition_path) : "-", TABLE_SET_COLOR, p->definition_path ? NULL : ansi_grey(),
else if (!sd_id128_is_null(p->current_uuid))
id = p->current_uuid;
else
- id = p->type_uuid;
+ id = p->type.uuid;
buf = strdup(SD_ID128_TO_UUID_STRING(id));
if (!p->allocated_to_area)
continue;
+ if (partition_skip(p))
+ continue;
+
r = context_wipe_partition(context, p);
if (r < 0)
return r;
return 0;
}
-static int partition_encrypt(
+typedef struct {
+ LoopDevice *loop;
+ int fd;
+ char *path;
+ int whole_fd;
+} PartitionTarget;
+
+static int partition_target_fd(PartitionTarget *t) {
+ assert(t);
+ assert(t->loop || t->fd >= 0 || t->whole_fd >= 0);
+ return t->loop ? t->loop->fd : t->fd >= 0 ? t->fd : t->whole_fd;
+}
+
+static const char* partition_target_path(PartitionTarget *t) {
+ assert(t);
+ assert(t->loop || t->path);
+ return t->loop ? t->loop->node : t->path;
+}
+
+static PartitionTarget *partition_target_free(PartitionTarget *t) {
+ if (!t)
+ return NULL;
+
+ loop_device_unref(t->loop);
+ safe_close(t->fd);
+ unlink_and_free(t->path);
+
+ return mfree(t);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(PartitionTarget*, partition_target_free);
+
+static int partition_target_prepare(
Context *context,
Partition *p,
- const char *node,
- struct crypt_device **ret_cd,
- char **ret_volume,
- int *ret_fd) {
-#if HAVE_LIBCRYPTSETUP
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_(erase_and_freep) void *volume_key = NULL;
- _cleanup_free_ char *dm_name = NULL, *vol = NULL;
- size_t volume_key_size = 256 / 8;
- sd_id128_t uuid;
+ uint64_t size,
+ bool need_path,
+ PartitionTarget **ret) {
+
+ _cleanup_(partition_target_freep) PartitionTarget *t = NULL;
+ struct stat st;
+ int whole_fd;
int r;
assert(context);
assert(p);
- assert(p->encrypt != ENCRYPT_OFF);
+ assert(ret);
- log_debug("Encryption mode for partition %" PRIu64 ": %s", p->partno, encrypt_mode_to_string(p->encrypt));
+ assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
- r = dlopen_cryptsetup();
- if (r < 0)
- return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m");
+ if (fstat(whole_fd, &st) < 0)
+ return -errno;
+
+ /* If we're operating on a block device, we definitely need privileges to access block devices so we
+ * can just use loop devices as our target. Otherwise, we're operating on a regular file, in that
+ * case, let's write to regular files and copy those into the final image so we can run without root
+ * privileges. On filesystems with reflinking support, we can take advantage of this and just reflink
+ * the result into the image.
+ */
- if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0)
+ t = new0(PartitionTarget, 1);
+ if (!t)
return log_oom();
- if (ret_volume) {
- vol = path_join("/dev/mapper/", dm_name);
- if (!vol)
+ if (S_ISBLK(st.st_mode) || (p->format && !mkfs_supports_root_option(p->format))) {
+ _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+
+ /* Loopback block devices are not only useful to turn regular files into block devices, but
+ * also to cut out sections of block devices into new block devices. */
+
+ r = loop_device_make(whole_fd, O_RDWR, p->offset, size, 0, 0, LOCK_EX, &d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
+
+ *t = (PartitionTarget) {
+ .loop = TAKE_PTR(d),
+ .fd = -1,
+ };
+ } else if (need_path) {
+ _cleanup_(unlink_and_freep) char *temp = NULL;
+ _cleanup_close_ int fd = -1;
+ const char *vt;
+
+ r = var_tmp_dir(&vt);
+ if (r < 0)
+ return log_error_errno(r, "Could not determine temporary directory: %m");
+
+ temp = path_join(vt, "repart-XXXXXX");
+ if (!temp)
return log_oom();
+
+ fd = mkostemp_safe(temp);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to create temporary file: %m");
+
+ if (ftruncate(fd, size) < 0)
+ return log_error_errno(errno, "Failed to truncate temporary file to %s: %m",
+ FORMAT_BYTES(size));
+
+ *t = (PartitionTarget) {
+ .fd = TAKE_FD(fd),
+ .path = TAKE_PTR(temp),
+ };
+ } else {
+ if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1)
+ return log_error_errno(errno, "Failed to seek to partition offset: %m");
+
+ *t = (PartitionTarget) {
+ .fd = -1,
+ .whole_fd = whole_fd,
+ };
+ }
+
+ *ret = TAKE_PTR(t);
+
+ return 0;
+}
+
+static int partition_target_grow(PartitionTarget *t, uint64_t size) {
+ int r;
+
+ assert(t);
+
+ if (t->loop) {
+ r = loop_device_refresh_size(t->loop, UINT64_MAX, size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to refresh loopback device size: %m");
+ } else if (t->fd >= 0) {
+ if (ftruncate(t->fd, size) < 0)
+ return log_error_errno(errno, "Failed to grow '%s' to %s by truncation: %m",
+ t->path, FORMAT_BYTES(size));
+ }
+
+ return 0;
+}
+
+static int partition_target_sync(Context *context, Partition *p, PartitionTarget *t) {
+ int whole_fd, r;
+
+ assert(context);
+ assert(p);
+ assert(t);
+
+ assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+
+ if (t->loop) {
+ r = loop_device_sync(t->loop);
+ if (r < 0)
+ return log_error_errno(r, "Failed to sync loopback device: %m");
+ } else if (t->fd >= 0) {
+ if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1)
+ return log_error_errno(errno, "Failed to seek to partition offset: %m");
+
+ r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy bytes to partition: %m");
+ } else {
+ if (fsync(t->whole_fd) < 0)
+ return log_error_errno(errno, "Failed to sync changes: %m");
}
+ return 0;
+}
+
+static int partition_encrypt(Context *context, Partition *p, const char *node) {
+#if HAVE_LIBCRYPTSETUP && HAVE_CRYPT_SET_DATA_OFFSET && HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE && HAVE_CRYPT_REENCRYPT
+ struct crypt_params_luks2 luks_params = {
+ .label = strempty(p->new_label),
+ .sector_size = context->sector_size,
+ .data_device = node,
+ };
+ struct crypt_params_reencrypt reencrypt_params = {
+ .mode = CRYPT_REENCRYPT_ENCRYPT,
+ .direction = CRYPT_REENCRYPT_BACKWARD,
+ .resilience = "datashift",
+ .data_shift = LUKS2_METADATA_SIZE / 512,
+ .luks2 = &luks_params,
+ .flags = CRYPT_REENCRYPT_INITIALIZE_ONLY|CRYPT_REENCRYPT_MOVE_FIRST_SEGMENT,
+ };
+ _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL;
+ _cleanup_fclose_ FILE *h = NULL;
+ _cleanup_free_ char *hp = NULL;
+ const char *passphrase = NULL;
+ size_t passphrase_size = 0;
+ sd_id128_t uuid;
+ const char *vt;
+ int r;
+
+ assert(context);
+ assert(p);
+ assert(p->encrypt != ENCRYPT_OFF);
+
+ r = dlopen_cryptsetup();
+ if (r < 0)
+ return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m");
+
r = derive_uuid(p->new_uuid, "luks-uuid", &uuid);
if (r < 0)
return r;
log_info("Encrypting future partition %" PRIu64 "...", p->partno);
- volume_key = malloc(volume_key_size);
- if (!volume_key)
- return log_oom();
+ r = var_tmp_dir(&vt);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine temporary files directory: %m");
- r = crypto_random_bytes(volume_key, volume_key_size);
+ r = fopen_temporary_child(vt, &h, &hp);
if (r < 0)
- return log_error_errno(r, "Failed to generate volume key: %m");
+ return log_error_errno(r, "Failed to create temporary LUKS header file: %m");
- r = sym_crypt_init(&cd, node);
+ /* Weird cryptsetup requirement which requires the header file to be the size of at least one sector. */
+ r = posix_fallocate(fileno(h), 0, context->sector_size);
if (r < 0)
- return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
+ return log_error_errno(r, "Failed to grow temporary LUKS header file: %m");
+
+ r = sym_crypt_init(&cd, hp);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", hp);
cryptsetup_enable_logging(cd);
+ /* Disable kernel keyring usage by libcryptsetup as a workaround for
+ * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/273. This makes sure that we can do
+ * offline encryption even when repart is running in a container. */
+ r = sym_crypt_volume_key_keyring(cd, false);
+ if (r < 0)
+ return log_error_errno(r, "Failed to disable kernel keyring: %m");
+
+ r = sym_crypt_metadata_locking(cd, false);
+ if (r < 0)
+ return log_error_errno(r, "Failed to disable metadata locking: %m");
+
+ r = sym_crypt_set_data_offset(cd, LUKS2_METADATA_SIZE / 512);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set data offset: %m");
+
r = sym_crypt_format(cd,
CRYPT_LUKS2,
"aes",
"xts-plain64",
SD_ID128_TO_UUID_STRING(uuid),
- volume_key,
- volume_key_size,
- &(struct crypt_params_luks2) {
- .label = strempty(p->new_label),
- .sector_size = context->sector_size,
- });
+ NULL,
+ VOLUME_KEY_SIZE,
+ &luks_params);
if (r < 0)
return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
r = sym_crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
- volume_key,
- volume_key_size,
+ NULL,
+ VOLUME_KEY_SIZE,
strempty(arg_key),
arg_key_size);
if (r < 0)
return log_error_errno(r, "Failed to add LUKS2 key: %m");
+
+ passphrase = strempty(arg_key);
+ passphrase_size = arg_key_size;
}
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) {
#if HAVE_TPM2
- _cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *pubkey = NULL;
keyslot = sym_crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
- volume_key,
- volume_key_size,
+ NULL,
+ VOLUME_KEY_SIZE,
base64_encoded,
strlen(base64_encoded));
if (keyslot < 0)
- return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
+ return log_error_errno(keyslot, "Failed to add new TPM2 key: %m");
r = tpm2_make_luks2_json(
keyslot,
r = cryptsetup_add_token_json(cd, v);
if (r < 0)
return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m");
+
+ passphrase = base64_encoded;
+ passphrase_size = strlen(base64_encoded);
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Support for TPM2 enrollment not enabled.");
#endif
}
- r = sym_crypt_activate_by_volume_key(
+ r = sym_crypt_reencrypt_init_by_passphrase(
cd,
- dm_name,
- volume_key,
- volume_key_size,
- arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0);
+ NULL,
+ passphrase,
+ passphrase_size,
+ CRYPT_ANY_SLOT,
+ 0,
+ sym_crypt_get_cipher(cd),
+ sym_crypt_get_cipher_mode(cd),
+ &reencrypt_params);
if (r < 0)
- return log_error_errno(r, "Failed to activate LUKS superblock: %m");
-
- log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno);
-
- if (ret_fd) {
- _cleanup_close_ int dev_fd = -1;
-
- dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY);
- if (dev_fd < 0)
- return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol);
-
- *ret_fd = TAKE_FD(dev_fd);
- }
+ return log_error_errno(r, "Failed to prepare for reencryption: %m");
- if (ret_cd)
- *ret_cd = TAKE_PTR(cd);
- if (ret_volume)
- *ret_volume = TAKE_PTR(vol);
+ /* crypt_reencrypt_init_by_passphrase() doesn't actually put the LUKS header at the front, we have
+ * to do that ourselves. */
- return 0;
-#else
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot encrypt: %m");
-#endif
-}
+ sym_crypt_free(cd);
+ cd = NULL;
-static int deactivate_luks(struct crypt_device *cd, const char *node) {
-#if HAVE_LIBCRYPTSETUP
- int r;
+ r = sym_crypt_init(&cd, node);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node);
- if (!cd)
- return 0;
+ r = sym_crypt_header_restore(cd, CRYPT_LUKS2, hp);
+ if (r < 0)
+ return log_error_errno(r, "Failed to place new LUKS header at head of %s: %m", node);
- assert(node);
+ reencrypt_params.flags &= ~CRYPT_REENCRYPT_INITIALIZE_ONLY;
- /* udev or so might access out block device in the background while we are done. Let's hence force
- * detach the volume. We sync'ed before, hence this should be safe. */
+ r = sym_crypt_reencrypt_init_by_passphrase(
+ cd,
+ NULL,
+ passphrase,
+ passphrase_size,
+ CRYPT_ANY_SLOT,
+ 0,
+ NULL,
+ NULL,
+ &reencrypt_params);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load reencryption context: %m");
- r = sym_crypt_deactivate_by_name(cd, basename(node), CRYPT_DEACTIVATE_FORCE);
+ r = sym_crypt_reencrypt(cd, NULL);
if (r < 0)
- return log_error_errno(r, "Failed to deactivate LUKS device: %m");
+ return log_error_errno(r, "Failed to encrypt %s: %m", node);
+
+ log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno);
- return 1;
-#else
return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "libcryptsetup is not supported or is missing required symbols, cannot encrypt: %m");
#endif
}
-static int context_copy_blocks(Context *context) {
- int whole_fd = -1, r;
-
- assert(context);
-
- /* Copy in file systems on the block level */
-
- LIST_FOREACH(partitions, p, context->partitions) {
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
- _cleanup_free_ char *encrypted = NULL;
- _cleanup_close_ int encrypted_dev_fd = -1;
- int target_fd;
+static int partition_format_verity_hash(
+ Context *context,
+ Partition *p,
+ const char *data_node) {
- if (p->copy_blocks_fd < 0)
- continue;
+#if HAVE_LIBCRYPTSETUP
+ Partition *dp;
+ _cleanup_(partition_target_freep) PartitionTarget *t = NULL;
+ _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
+ _cleanup_free_ uint8_t *rh = NULL;
+ size_t rhs;
+ int r;
- if (p->dropped)
- continue;
+ assert(context);
+ assert(p);
+ assert(data_node);
- if (PARTITION_EXISTS(p)) /* Never copy over existing partitions */
- continue;
+ if (p->dropped)
+ return 0;
- assert(p->new_size != UINT64_MAX);
- assert(p->copy_blocks_size != UINT64_MAX);
- assert(p->new_size >= p->copy_blocks_size);
+ if (PARTITION_EXISTS(p)) /* Never format existing partitions */
+ return 0;
- if (whole_fd < 0)
- assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+ if (p->verity != VERITY_HASH)
+ return 0;
- if (p->encrypt != ENCRYPT_OFF) {
- r = loop_device_make(whole_fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d);
- if (r < 0)
- return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
+ if (partition_skip(p))
+ return 0;
- r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd);
- if (r < 0)
- return log_error_errno(r, "Failed to encrypt device: %m");
+ assert_se(dp = p->siblings[VERITY_DATA]);
+ assert(!dp->dropped);
- if (flock(encrypted_dev_fd, LOCK_EX) < 0)
- return log_error_errno(errno, "Failed to lock LUKS device: %m");
+ r = dlopen_cryptsetup();
+ if (r < 0)
+ return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m");
- target_fd = encrypted_dev_fd;
- } else {
- if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1)
- return log_error_errno(errno, "Failed to seek to partition offset: %m");
+ r = partition_target_prepare(context, p, p->new_size, /*need_path=*/ true, &t);
+ if (r < 0)
+ return r;
- target_fd = whole_fd;
- }
+ r = sym_crypt_init(&cd, partition_target_path(t));
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
- log_info("Copying in '%s' (%s) on block level into future partition %" PRIu64 ".",
- p->copy_blocks_path, FORMAT_BYTES(p->copy_blocks_size), p->partno);
+ r = sym_crypt_format(
+ cd, CRYPT_VERITY, NULL, NULL, NULL, NULL, 0,
+ &(struct crypt_params_verity){
+ .data_device = data_node,
+ .flags = CRYPT_VERITY_CREATE_HASH,
+ .hash_name = "sha256",
+ .hash_type = 1,
+ .data_block_size = context->sector_size,
+ .hash_block_size = context->sector_size,
+ .salt_size = 32,
+ });
+ if (r < 0)
+ return log_error_errno(r, "Failed to setup verity hash data: %m");
- r = copy_bytes_full(p->copy_blocks_fd, target_fd, p->copy_blocks_size, 0, NULL, NULL, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path);
+ r = partition_target_sync(context, p, t);
+ if (r < 0)
+ return r;
- if (fsync(target_fd) < 0)
- return log_error_errno(errno, "Failed to synchronize copied data blocks: %m");
+ r = sym_crypt_get_volume_key_size(cd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine verity root hash size: %m");
+ rhs = (size_t) r;
- if (p->encrypt != ENCRYPT_OFF) {
- encrypted_dev_fd = safe_close(encrypted_dev_fd);
+ rh = malloc(rhs);
+ if (!rh)
+ return log_oom();
- r = deactivate_luks(cd, encrypted);
- if (r < 0)
- return r;
+ r = sym_crypt_volume_key_get(cd, CRYPT_ANY_SLOT, (char *) rh, &rhs, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get verity root hash: %m");
- sym_crypt_free(cd);
- cd = NULL;
+ assert(rhs >= sizeof(sd_id128_t) * 2);
- r = loop_device_sync(d);
- if (r < 0)
- return log_error_errno(r, "Failed to sync loopback device: %m");
- }
+ if (!dp->new_uuid_is_set) {
+ memcpy_safe(dp->new_uuid.bytes, rh, sizeof(sd_id128_t));
+ dp->new_uuid_is_set = true;
+ }
- log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path);
+ if (!p->new_uuid_is_set) {
+ memcpy_safe(p->new_uuid.bytes, rh + rhs - sizeof(sd_id128_t), sizeof(sd_id128_t));
+ p->new_uuid_is_set = true;
}
+ p->roothash = TAKE_PTR(rh);
+ p->roothash_size = rhs;
+
return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes: %m");
+#endif
}
-static int do_copy_files(Partition *p, const char *root) {
- int r;
-
+static int sign_verity_roothash(
+ const uint8_t *roothash,
+ size_t roothash_size,
+ uint8_t **ret_signature,
+ size_t *ret_signature_size) {
+
+#if HAVE_OPENSSL
+ _cleanup_(BIO_freep) BIO *rb = NULL;
+ _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
+ _cleanup_free_ char *hex = NULL;
+ _cleanup_free_ uint8_t *sig = NULL;
+ int sigsz;
+
+ assert(roothash);
+ assert(roothash_size > 0);
+ assert(ret_signature);
+ assert(ret_signature_size);
+
+ hex = hexmem(roothash, roothash_size);
+ if (!hex)
+ return log_oom();
+
+ rb = BIO_new_mem_buf(hex, -1);
+ if (!rb)
+ return log_oom();
+
+ p7 = PKCS7_sign(arg_certificate, arg_private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY);
+ if (!p7)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ sigsz = i2d_PKCS7(p7, &sig);
+ if (sigsz < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ *ret_signature = TAKE_PTR(sig);
+ *ret_signature_size = sigsz;
+
+ return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot setup verity signature: %m");
+#endif
+}
+
+static int partition_format_verity_sig(Context *context, Partition *p) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ uint8_t *sig = NULL;
+ _cleanup_free_ char *text = NULL;
+ Partition *hp;
+ uint8_t fp[X509_FINGERPRINT_SIZE];
+ size_t sigsz = 0, padsz; /* avoid false maybe-uninitialized warning */
+ int whole_fd, r;
+
+ assert(p->verity == VERITY_SIG);
+
+ if (p->dropped)
+ return 0;
+
+ if (PARTITION_EXISTS(p))
+ return 0;
+
+ if (partition_skip(p))
+ return 0;
+
+ assert_se(hp = p->siblings[VERITY_HASH]);
+ assert(!hp->dropped);
+
+ assert(arg_certificate);
+
+ assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+
+ r = sign_verity_roothash(hp->roothash, hp->roothash_size, &sig, &sigsz);
+ if (r < 0)
+ return r;
+
+ r = x509_fingerprint(arg_certificate, fp);
+ if (r < 0)
+ return log_error_errno(r, "Unable to calculate X509 certificate fingerprint: %m");
+
+ r = json_build(&v,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("rootHash", JSON_BUILD_HEX(hp->roothash, hp->roothash_size)),
+ JSON_BUILD_PAIR(
+ "certificateFingerprint",
+ JSON_BUILD_HEX(fp, sizeof(fp))
+ ),
+ JSON_BUILD_PAIR("signature", JSON_BUILD_BASE64(sig, sigsz))
+ )
+ );
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON object: %m");
+
+ r = json_variant_format(v, 0, &text);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format JSON object: %m");
+
+ padsz = round_up_size(strlen(text), 4096);
+ assert_se(padsz <= p->new_size);
+
+ r = strgrowpad0(&text, padsz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to pad string to %s", FORMAT_BYTES(padsz));
+
+ if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1)
+ return log_error_errno(errno, "Failed to seek to partition offset: %m");
+
+ r = loop_write(whole_fd, text, padsz, /*do_poll=*/ false);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write verity signature to partition: %m");
+
+ if (fsync(whole_fd) < 0)
+ return log_error_errno(errno, "Failed to synchronize verity signature JSON: %m");
+
+ return 0;
+}
+
+static int context_copy_blocks(Context *context) {
+ int r;
+
+ assert(context);
+
+ /* Copy in file systems on the block level */
+
+ LIST_FOREACH(partitions, p, context->partitions) {
+ _cleanup_(partition_target_freep) PartitionTarget *t = NULL;
+
+ if (p->copy_blocks_fd < 0)
+ continue;
+
+ if (p->dropped)
+ continue;
+
+ if (PARTITION_EXISTS(p)) /* Never copy over existing partitions */
+ continue;
+
+ if (partition_skip(p))
+ continue;
+
+ assert(p->new_size != UINT64_MAX);
+ assert(p->copy_blocks_size != UINT64_MAX);
+ assert(p->new_size >= p->copy_blocks_size + (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0));
+
+ r = partition_target_prepare(context, p, p->new_size,
+ /*need_path=*/ p->encrypt != ENCRYPT_OFF || p->siblings[VERITY_HASH],
+ &t);
+ if (r < 0)
+ return r;
+
+ log_info("Copying in '%s' (%s) on block level into future partition %" PRIu64 ".",
+ p->copy_blocks_path, FORMAT_BYTES(p->copy_blocks_size), p->partno);
+
+ r = copy_bytes(p->copy_blocks_fd, partition_target_fd(t), p->copy_blocks_size, COPY_REFLINK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path);
+
+ if (p->encrypt != ENCRYPT_OFF) {
+ r = partition_encrypt(context, p, partition_target_path(t));
+ if (r < 0)
+ return r;
+ }
+
+ r = partition_target_sync(context, p, t);
+ if (r < 0)
+ return r;
+
+ log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path);
+
+ if (p->siblings[VERITY_HASH]) {
+ r = partition_format_verity_hash(context, p->siblings[VERITY_HASH],
+ partition_target_path(t));
+ if (r < 0)
+ return r;
+ }
+
+ if (p->siblings[VERITY_SIG]) {
+ r = partition_format_verity_sig(context, p->siblings[VERITY_SIG]);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int do_copy_files(Partition *p, const char *root, const Set *denylist) {
+
+ int r;
+
assert(p);
assert(root);
r = copy_tree_at(
sfd, ".",
pfd, fn,
- UID_INVALID, GID_INVALID,
- COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS);
+ getuid(), getgid(),
+ COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS,
+ denylist);
} else
r = copy_tree_at(
sfd, ".",
tfd, ".",
- UID_INVALID, GID_INVALID,
- COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS);
+ getuid(), getgid(),
+ COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS,
+ denylist);
if (r < 0)
- return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", *source, strempty(arg_root), *target);
+ return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m",
+ strempty(arg_root), *source, strempty(root), *target);
} else {
_cleanup_free_ char *dn = NULL, *fn = NULL;
if (tfd < 0)
return log_error_errno(errno, "Failed to create target file '%s': %m", *target);
- r = copy_bytes(sfd, tfd, UINT64_MAX, COPY_REFLINK|COPY_SIGINT);
+ r = copy_bytes(sfd, tfd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_SIGINT);
if (r < 0)
return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", *source, strempty(arg_root), *target);
STRV_FOREACH(d, p->make_directories) {
- r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755);
+ r = mkdir_p_root(root, *d, getuid(), getgid(), 0755);
if (r < 0)
return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d);
}
return 0;
}
-static int partition_populate_directory(Partition *p, char **ret_root, char **ret_tmp_root) {
+static int partition_populate_directory(Partition *p, const Set *denylist, char **ret) {
_cleanup_(rm_rf_physical_and_freep) char *root = NULL;
int r;
- assert(ret_root);
- assert(ret_tmp_root);
-
- /* When generating read-only filesystems, we need the source tree to be available when we generate
- * the read-only filesystem. Because we might have multiple source trees, we build a temporary source
- * tree beforehand where we merge all our inputs. We then use this merged source tree to create the
- * read-only filesystem. */
-
- if (!fstype_is_ro(p->format)) {
- *ret_root = NULL;
- *ret_tmp_root = NULL;
- return 0;
- }
-
- /* If we only have a single directory that's meant to become the root directory of the filesystem,
- * we can shortcut this function and just use that directory as the root directory instead. If we
- * allocate a temporary directory, it's stored in "ret_tmp_root" to indicate it should be removed.
- * Otherwise, we return the directory to use in "root" to indicate it should not be removed. */
-
- if (strv_length(p->copy_files) == 2 && strv_length(p->make_directories) == 0 && streq(p->copy_files[1], "/")) {
- _cleanup_free_ char *s = NULL;
-
- r = chase_symlinks(p->copy_files[0], arg_root, CHASE_PREFIX_ROOT, &s, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to resolve source '%s%s': %m", strempty(arg_root), p->copy_files[0]);
+ assert(ret);
- *ret_root = TAKE_PTR(s);
- *ret_tmp_root = NULL;
+ if ((strv_isempty(p->copy_files) && strv_isempty(p->make_directories))) {
+ *ret = NULL;
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to create temporary directory: %m");
- r = do_copy_files(p, root);
+ if (chmod(root, 0755) < 0)
+ return log_error_errno(errno, "Failed to change mode of temporary directory: %m");
+
+ /* Make sure everything is owned by the user running repart so that make_filesystem() can map the
+ * user running repart to "root" in a user namespace to have the files owned by root in the final
+ * image. */
+
+ r = do_copy_files(p, root, denylist);
if (r < 0)
return r;
if (r < 0)
return r;
- *ret_root = NULL;
- *ret_tmp_root = TAKE_PTR(root);
+ *ret = TAKE_PTR(root);
return 0;
}
-static int partition_populate_filesystem(Partition *p, const char *node) {
+static int partition_populate_filesystem(Partition *p, const char *node, const Set *denylist) {
+ _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+ struct stat st;
int r;
assert(p);
assert(node);
- if (fstype_is_ro(p->format))
- return 0;
-
if (strv_isempty(p->copy_files) && strv_isempty(p->make_directories))
return 0;
- log_info("Populating partition %" PRIu64 " with files.", p->partno);
+ if (stat(node, &st) < 0)
+ return log_error_errno(errno, "Failed to stat %s: %m", node);
+
+ if (!S_ISBLK(st.st_mode)) {
+ r = loop_device_make_by_path(node, O_RDWR, 0, LOCK_EX, &d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make loopback device of %s: %m", node);
+
+ node = d->node;
+ }
+
+ log_info("Populating %s filesystem with files.", p->format);
/* We copy in a child process, since we have to mount the fs for that, and we don't want that fs to
* appear in the host namespace. Hence we fork a child that has its own file system namespace and
if (mount_nofollow_verbose(LOG_ERR, node, fs, p->format, MS_NOATIME|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
_exit(EXIT_FAILURE);
- if (do_copy_files(p, fs) < 0)
+ if (do_copy_files(p, fs, denylist) < 0)
_exit(EXIT_FAILURE);
if (do_make_directories(p, fs) < 0)
_exit(EXIT_SUCCESS);
}
- log_info("Successfully populated partition %" PRIu64 " with files.", p->partno);
+ log_info("Successfully populated %s filesystem with files.", p->format);
return 0;
}
-static int context_mkfs(Context *context) {
- int fd = -1, r;
+static int make_copy_files_denylist(Context *context, Set **ret) {
+ _cleanup_set_free_ Set *denylist = NULL;
+ int r;
assert(context);
-
- /* Make a file system */
+ assert(ret);
LIST_FOREACH(partitions, p, context->partitions) {
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
- _cleanup_(rm_rf_physical_and_freep) char *tmp_root = NULL;
- _cleanup_free_ char *encrypted = NULL, *root = NULL;
- _cleanup_close_ int encrypted_dev_fd = -1;
- const char *fsdev;
- sd_id128_t fs_uuid;
-
- if (p->dropped)
+ const char *sources = gpt_partition_type_mountpoint_nulstr(p->type);
+ if (!sources)
continue;
- if (PARTITION_EXISTS(p)) /* Never format existing partitions */
- continue;
-
- if (!p->format)
- continue;
-
- assert(p->offset != UINT64_MAX);
- assert(p->new_size != UINT64_MAX);
-
- if (fd < 0)
- assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
-
- /* Loopback block devices are not only useful to turn regular files into block devices, but
- * also to cut out sections of block devices into new block devices. */
-
- r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d);
- if (r < 0)
- return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
+ NULSTR_FOREACH(s, sources) {
+ _cleanup_free_ char *d = NULL;
+ struct stat st;
- if (p->encrypt != ENCRYPT_OFF) {
- r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd);
+ r = chase_symlinks_and_stat(s, arg_root, CHASE_PREFIX_ROOT, NULL, &st, NULL);
+ if (r == -ENOENT)
+ continue;
if (r < 0)
- return log_error_errno(r, "Failed to encrypt device: %m");
-
- if (flock(encrypted_dev_fd, LOCK_EX) < 0)
- return log_error_errno(errno, "Failed to lock LUKS device: %m");
-
- fsdev = encrypted;
- } else
- fsdev = d->node;
-
- log_info("Formatting future partition %" PRIu64 ".", p->partno);
-
- /* Calculate the UUID for the file system as HMAC-SHA256 of the string "file-system-uuid",
- * keyed off the partition UUID. */
- r = derive_uuid(p->new_uuid, "file-system-uuid", &fs_uuid);
- if (r < 0)
- return r;
-
- /* Ideally, we populate filesystems using our own code after creating the filesystem to
- * ensure consistent handling of chattrs, xattrs and other similar things. However, when
- * using read-only filesystems such as squashfs, we can't populate after creating the
- * filesystem because it's read-only, so instead we create a temporary root to use as the
- * source tree when generating the read-only filesystem. */
- r = partition_populate_directory(p, &root, &tmp_root);
- if (r < 0)
- return r;
-
- r = make_filesystem(fsdev, p->format, strempty(p->new_label), root ?: tmp_root, fs_uuid, arg_discard);
- if (r < 0) {
- encrypted_dev_fd = safe_close(encrypted_dev_fd);
- (void) deactivate_luks(cd, encrypted);
- return r;
- }
-
- log_info("Successfully formatted future partition %" PRIu64 ".", p->partno);
-
- /* The file system is now created, no need to delay udev further */
- if (p->encrypt != ENCRYPT_OFF)
- if (flock(encrypted_dev_fd, LOCK_UN) < 0)
- return log_error_errno(errno, "Failed to unlock LUKS device: %m");
-
- /* Now, we can populate all the other filesystems that aren't read-only. */
- r = partition_populate_filesystem(p, fsdev);
- if (r < 0) {
- encrypted_dev_fd = safe_close(encrypted_dev_fd);
- (void) deactivate_luks(cd, encrypted);
- return r;
- }
+ return log_error_errno(r, "Failed to stat source file '%s%s': %m",
+ strempty(arg_root), s);
- /* Note that we always sync explicitly here, since mkfs.fat doesn't do that on its own, and
- * if we don't sync before detaching a block device the in-flight sectors possibly won't hit
- * the disk. */
-
- if (p->encrypt != ENCRYPT_OFF) {
- if (fsync(encrypted_dev_fd) < 0)
- return log_error_errno(errno, "Failed to synchronize LUKS volume: %m");
- encrypted_dev_fd = safe_close(encrypted_dev_fd);
+ if (set_contains(denylist, &st))
+ continue;
- r = deactivate_luks(cd, encrypted);
- if (r < 0)
- return r;
+ d = memdup(&st, sizeof(st));
+ if (!d)
+ return log_oom();
+ if (set_ensure_put(&denylist, &inode_hash_ops, d) < 0)
+ return log_oom();
- sym_crypt_free(cd);
- cd = NULL;
+ TAKE_PTR(d);
}
-
- r = loop_device_sync(d);
- if (r < 0)
- return log_error_errno(r, "Failed to sync loopback device: %m");
}
+ *ret = TAKE_PTR(denylist);
return 0;
}
-static int do_verity_format(
- LoopDevice *data_device,
- LoopDevice *hash_device,
- uint64_t sector_size,
- uint8_t **ret_roothash,
- size_t *ret_roothash_size) {
-
-#if HAVE_LIBCRYPTSETUP
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_free_ uint8_t *rh = NULL;
- size_t rhs;
+static int context_mkfs(Context *context) {
+ _cleanup_set_free_ Set *denylist = NULL;
int r;
- assert(data_device);
- assert(hash_device);
- assert(sector_size > 0);
- assert(ret_roothash);
- assert(ret_roothash_size);
+ assert(context);
- r = dlopen_cryptsetup();
- if (r < 0)
- return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m");
+ /* Make a file system */
- r = sym_crypt_init(&cd, hash_device->node);
+ r = make_copy_files_denylist(context, &denylist);
if (r < 0)
- return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
+ return r;
- r = sym_crypt_format(
- cd, CRYPT_VERITY, NULL, NULL, NULL, NULL, 0,
- &(struct crypt_params_verity){
- .data_device = data_device->node,
- .flags = CRYPT_VERITY_CREATE_HASH,
- .hash_name = "sha256",
- .hash_type = 1,
- .data_block_size = sector_size,
- .hash_block_size = sector_size,
- .salt_size = 32,
- });
- if (r < 0)
- return log_error_errno(r, "Failed to setup verity hash data: %m");
+ LIST_FOREACH(partitions, p, context->partitions) {
+ _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
+ _cleanup_(partition_target_freep) PartitionTarget *t = NULL;
- r = sym_crypt_get_volume_key_size(cd);
- if (r < 0)
- return log_error_errno(r, "Failed to determine verity root hash size: %m");
- rhs = (size_t) r;
+ if (p->dropped)
+ continue;
- rh = malloc(rhs);
- if (!rh)
- return log_oom();
+ if (PARTITION_EXISTS(p)) /* Never format existing partitions */
+ continue;
- r = sym_crypt_volume_key_get(cd, CRYPT_ANY_SLOT, (char *) rh, &rhs, NULL, 0);
- if (r < 0)
- return log_error_errno(r, "Failed to get verity root hash: %m");
+ if (!p->format)
+ continue;
- *ret_roothash = TAKE_PTR(rh);
- *ret_roothash_size = rhs;
+ /* Minimized partitions will use the copy blocks logic so let's make sure to skip those here. */
+ if (p->copy_blocks_fd >= 0)
+ continue;
- return 0;
-#else
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes: %m");
-#endif
-}
+ if (partition_skip(p))
+ continue;
-static int context_verity_hash(Context *context) {
- int fd = -1, r;
+ assert(p->offset != UINT64_MAX);
+ assert(p->new_size != UINT64_MAX);
+ assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0));
+
+ /* If we're doing encryption, we make sure we keep free space at the end which is required
+ * for cryptsetup's offline encryption. */
+ r = partition_target_prepare(context, p,
+ p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0),
+ /*need_path=*/ true,
+ &t);
+ if (r < 0)
+ return r;
- assert(context);
+ log_info("Formatting future partition %" PRIu64 ".", p->partno);
- LIST_FOREACH(partitions, p, context->partitions) {
- Partition *dp;
- _cleanup_(loop_device_unrefp) LoopDevice *hash_device = NULL, *data_device = NULL;
- _cleanup_free_ uint8_t *rh = NULL;
- size_t rhs = 0; /* Initialize to work around for GCC false positive. */
+ /* We prefer (or are required in the case of read-only filesystems) to populate filesystems
+ * directly via the corresponding mkfs binary if it supports a --rootdir (or equivalent)
+ * option. To do that, we need to setup the final directory tree beforehand. */
- if (p->dropped)
- continue;
+ if (mkfs_supports_root_option(p->format)) {
+ r = partition_populate_directory(p, denylist, &root);
+ if (r < 0)
+ return r;
+ }
- if (PARTITION_EXISTS(p)) /* Never format existing partitions */
- continue;
+ r = make_filesystem(partition_target_path(t), p->format, strempty(p->new_label), root,
+ p->fs_uuid, arg_discard);
+ if (r < 0)
+ return r;
- if (p->verity != VERITY_HASH)
- continue;
+ log_info("Successfully formatted future partition %" PRIu64 ".", p->partno);
- assert_se(dp = p->siblings[VERITY_DATA]);
- assert(!dp->dropped);
+ /* Now, we can populate all the other filesystems that we couldn't populate earlier. */
+ if (!mkfs_supports_root_option(p->format)) {
+ r = partition_populate_filesystem(p, partition_target_path(t), denylist);
+ if (r < 0)
+ return r;
+ }
- if (fd < 0)
- assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+ if (p->encrypt != ENCRYPT_OFF) {
+ r = partition_target_grow(t, p->new_size);
+ if (r < 0)
+ return r;
- r = loop_device_make(fd, O_RDONLY, dp->offset, dp->new_size, 0, 0, LOCK_EX, &data_device);
- if (r < 0)
- return log_error_errno(r,
- "Failed to make loopback device of verity data partition %" PRIu64 ": %m",
- p->partno);
+ r = partition_encrypt(context, p, partition_target_path(t));
+ if (r < 0)
+ return log_error_errno(r, "Failed to encrypt device: %m");
+ }
- r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &hash_device);
- if (r < 0)
- return log_error_errno(r,
- "Failed to make loopback device of verity hash partition %" PRIu64 ": %m",
- p->partno);
+ /* Note that we always sync explicitly here, since mkfs.fat doesn't do that on its own, and
+ * if we don't sync before detaching a block device the in-flight sectors possibly won't hit
+ * the disk. */
- r = do_verity_format(data_device, hash_device, context->sector_size, &rh, &rhs);
+ r = partition_target_sync(context, p, t);
if (r < 0)
return r;
- assert(rhs >= sizeof(sd_id128_t) * 2);
-
- if (!dp->new_uuid_is_set) {
- memcpy_safe(dp->new_uuid.bytes, rh, sizeof(sd_id128_t));
- dp->new_uuid_is_set = true;
+ if (p->siblings[VERITY_HASH]) {
+ r = partition_format_verity_hash(context, p->siblings[VERITY_HASH],
+ partition_target_path(t));
+ if (r < 0)
+ return r;
}
- if (!p->new_uuid_is_set) {
- memcpy_safe(p->new_uuid.bytes, rh + rhs - sizeof(sd_id128_t), sizeof(sd_id128_t));
- p->new_uuid_is_set = true;
+ if (p->siblings[VERITY_SIG]) {
+ r = partition_format_verity_sig(context, p->siblings[VERITY_SIG]);
+ if (r < 0)
+ return r;
}
-
- p->roothash = TAKE_PTR(rh);
- p->roothash_size = rhs;
}
return 0;
#endif
}
-static int sign_verity_roothash(
- const uint8_t *roothash,
- size_t roothash_size,
- uint8_t **ret_signature,
- size_t *ret_signature_size) {
-
-#if HAVE_OPENSSL
- _cleanup_(BIO_freep) BIO *rb = NULL;
- _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
- _cleanup_free_ char *hex = NULL;
- _cleanup_free_ uint8_t *sig = NULL;
- int sigsz;
-
- assert(roothash);
- assert(roothash_size > 0);
- assert(ret_signature);
- assert(ret_signature_size);
-
- hex = hexmem(roothash, roothash_size);
- if (!hex)
- return log_oom();
-
- rb = BIO_new_mem_buf(hex, -1);
- if (!rb)
- return log_oom();
-
- p7 = PKCS7_sign(arg_certificate, arg_private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY);
- if (!p7)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s",
- ERR_error_string(ERR_get_error(), NULL));
-
- sigsz = i2d_PKCS7(p7, &sig);
- if (sigsz < 0)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s",
- ERR_error_string(ERR_get_error(), NULL));
-
- *ret_signature = TAKE_PTR(sig);
- *ret_signature_size = sigsz;
-
- return 0;
-#else
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot setup verity signature: %m");
-#endif
-}
-
-static int context_verity_sig(Context *context) {
- int fd = -1, r;
-
- assert(context);
-
- LIST_FOREACH(partitions, p, context->partitions) {
- _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- _cleanup_free_ uint8_t *sig = NULL;
- _cleanup_free_ char *text = NULL;
- Partition *hp;
- uint8_t fp[X509_FINGERPRINT_SIZE];
- size_t sigsz = 0, padsz; /* avoid false maybe-uninitialized warning */
-
- if (p->dropped)
- continue;
-
- if (PARTITION_EXISTS(p))
- continue;
-
- if (p->verity != VERITY_SIG)
- continue;
-
- assert_se(hp = p->siblings[VERITY_HASH]);
- assert(!hp->dropped);
-
- assert(arg_certificate);
-
- if (fd < 0)
- assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
-
- r = sign_verity_roothash(hp->roothash, hp->roothash_size, &sig, &sigsz);
- if (r < 0)
- return r;
-
- r = x509_fingerprint(arg_certificate, fp);
- if (r < 0)
- return log_error_errno(r, "Unable to calculate X509 certificate fingerprint: %m");
-
- r = json_build(&v,
- JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR("rootHash", JSON_BUILD_HEX(hp->roothash, hp->roothash_size)),
- JSON_BUILD_PAIR(
- "certificateFingerprint",
- JSON_BUILD_HEX(fp, sizeof(fp))
- ),
- JSON_BUILD_PAIR("signature", JSON_BUILD_BASE64(sig, sigsz))
- )
- );
- if (r < 0)
- return log_error_errno(r, "Failed to build JSON object: %m");
-
- r = json_variant_format(v, 0, &text);
- if (r < 0)
- return log_error_errno(r, "Failed to format JSON object: %m");
-
- padsz = round_up_size(strlen(text), 4096);
- assert_se(padsz <= p->new_size);
-
- r = strgrowpad0(&text, padsz);
- if (r < 0)
- return log_error_errno(r, "Failed to pad string to %s", FORMAT_BYTES(padsz));
-
- if (lseek(fd, p->offset, SEEK_SET) == (off_t) -1)
- return log_error_errno(errno, "Failed to seek to partition offset: %m");
-
- r = loop_write(fd, text, padsz, /*do_poll=*/ false);
- if (r < 0)
- return log_error_errno(r, "Failed to write verity signature to partition: %m");
-
- if (fsync(fd) < 0)
- return log_error_errno(errno, "Failed to synchronize verity signature JSON: %m");
- }
-
- return 0;
-}
-
static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) {
struct {
sd_id128_t type_uuid;
if (p == q)
break;
- if (!sd_id128_equal(p->type_uuid, q->type_uuid))
+ if (!sd_id128_equal(p->type.uuid, q->type.uuid))
continue;
k++;
}
- plaintext.type_uuid = p->type_uuid;
+ plaintext.type_uuid = p->type.uuid;
plaintext.counter = htole64(k);
hmac_sha256(context->seed.bytes, sizeof(context->seed.bytes),
assert(p);
assert(ret);
- prefix = gpt_partition_type_uuid_to_string(p->type_uuid);
+ prefix = gpt_partition_type_uuid_to_string(p->type.uuid);
if (!prefix)
prefix = "linux";
p->new_uuid_is_set = true;
}
+ /* Calculate the UUID for the file system as HMAC-SHA256 of the string "file-system-uuid",
+ * keyed off the partition UUID. */
+ r = derive_uuid(p->new_uuid, "file-system-uuid", &p->fs_uuid);
+ if (r < 0)
+ return r;
+
if (!isempty(p->current_label)) {
/* never change initialized labels */
r = free_and_strdup_warn(&p->new_label, p->current_label);
f = p->gpt_flags;
if (p->no_auto >= 0) {
- if (gpt_partition_type_knows_no_auto(p->type_uuid))
+ if (gpt_partition_type_knows_no_auto(p->type))
SET_FLAG(f, SD_GPT_FLAG_NO_AUTO, p->no_auto);
else {
char buffer[SD_ID128_UUID_STRING_MAX];
log_warning("Configured NoAuto=%s for partition type '%s' that doesn't support it, ignoring.",
yes_no(p->no_auto),
- gpt_partition_type_uuid_to_string_harder(p->type_uuid, buffer));
+ gpt_partition_type_uuid_to_string_harder(p->type.uuid, buffer));
}
}
if (p->read_only >= 0) {
- if (gpt_partition_type_knows_read_only(p->type_uuid))
+ if (gpt_partition_type_knows_read_only(p->type))
SET_FLAG(f, SD_GPT_FLAG_READ_ONLY, p->read_only);
else {
char buffer[SD_ID128_UUID_STRING_MAX];
log_warning("Configured ReadOnly=%s for partition type '%s' that doesn't support it, ignoring.",
yes_no(p->read_only),
- gpt_partition_type_uuid_to_string_harder(p->type_uuid, buffer));
+ gpt_partition_type_uuid_to_string_harder(p->type.uuid, buffer));
}
}
if (p->growfs >= 0) {
- if (gpt_partition_type_knows_growfs(p->type_uuid))
+ if (gpt_partition_type_knows_growfs(p->type))
SET_FLAG(f, SD_GPT_FLAG_GROWFS, p->growfs);
else {
char buffer[SD_ID128_UUID_STRING_MAX];
log_warning("Configured GrowFileSystem=%s for partition type '%s' that doesn't support it, ignoring.",
yes_no(p->growfs),
- gpt_partition_type_uuid_to_string_harder(p->type_uuid, buffer));
+ gpt_partition_type_uuid_to_string_harder(p->type.uuid, buffer));
}
}
if (p->dropped)
continue;
+ if (partition_skip(p))
+ continue;
+
assert(p->new_size != UINT64_MAX);
assert(p->offset != UINT64_MAX);
assert(p->partno != UINT64_MAX);
if (!t)
return log_oom();
- r = fdisk_parttype_set_typestr(t, SD_ID128_TO_UUID_STRING(p->type_uuid));
+ r = fdisk_parttype_set_typestr(t, SD_ID128_TO_UUID_STRING(p->type.uuid));
if (r < 0)
return log_error_errno(r, "Failed to initialize partition type: %m");
assert(p);
const Specifier table[] = {
- { 't', specifier_string, GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(p->type_uuid) },
- { 'T', specifier_id128, &p->type_uuid },
+ { 't', specifier_string, GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(p->type.uuid) },
+ { 'T', specifier_id128, &p->type.uuid },
{ 'U', specifier_id128, &p->new_uuid },
{ 'n', specifier_uint64, &p->partno },
if (!p->split_name_resolved)
continue;
+ if (partition_skip(p))
+ continue;
+
fname = strjoin(base, ".", p->split_name_resolved, ext);
if (!fname)
return log_oom();
if (lseek(fd, p->offset, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to seek to partition offset: %m");
- r = copy_bytes_full(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES, NULL, NULL, NULL, NULL);
+ r = copy_bytes(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES);
if (r < 0)
return log_error_errno(r, "Failed to copy to split partition %s: %m", fname);
}
if (r < 0)
return r;
- r = context_verity_hash(context);
- if (r < 0)
- return r;
-
- r = context_verity_sig(context);
- if (r < 0)
- return r;
-
r = context_mangle_partitions(context);
if (r < 0)
return r;
static int resolve_copy_blocks_auto_candidate(
dev_t partition_devno,
- sd_id128_t partition_type_uuid,
+ GptPartitionType partition_type,
dev_t restrict_devno,
sd_id128_t *ret_uuid) {
return false;
}
- if (!sd_id128_equal(pt_parsed, partition_type_uuid)) {
+ if (!sd_id128_equal(pt_parsed, partition_type.uuid)) {
log_debug("Partition %u:%u has non-matching partition type " SD_ID128_FORMAT_STR " (needed: " SD_ID128_FORMAT_STR "), ignoring.",
major(partition_devno), minor(partition_devno),
- SD_ID128_FORMAT_VAL(pt_parsed), SD_ID128_FORMAT_VAL(partition_type_uuid));
+ SD_ID128_FORMAT_VAL(pt_parsed), SD_ID128_FORMAT_VAL(partition_type.uuid));
return false;
}
}
static int resolve_copy_blocks_auto(
- sd_id128_t type_uuid,
+ GptPartitionType type,
const char *root,
dev_t restrict_devno,
dev_t *ret_devno,
* partitions in the host, using the appropriate directory as key and ensuring that the partition
* type matches. */
- if (gpt_partition_type_is_root(type_uuid))
+ if (type.designator == PARTITION_ROOT)
try1 = "/";
- else if (gpt_partition_type_is_usr(type_uuid))
+ else if (type.designator == PARTITION_USR)
try1 = "/usr/";
- else if (gpt_partition_type_is_root_verity(type_uuid))
+ else if (type.designator == PARTITION_ROOT_VERITY)
try1 = "/";
- else if (gpt_partition_type_is_usr_verity(type_uuid))
+ else if (type.designator == PARTITION_USR_VERITY)
try1 = "/usr/";
- else if (sd_id128_equal(type_uuid, SD_GPT_ESP)) {
+ else if (type.designator == PARTITION_ESP) {
try1 = "/efi/";
try2 = "/boot/";
- } else if (sd_id128_equal(type_uuid, SD_GPT_XBOOTLDR))
+ } else if (type.designator == PARTITION_XBOOTLDR)
try1 = "/boot/";
else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Partition type " SD_ID128_FORMAT_STR " not supported from automatic source block device discovery.",
- SD_ID128_FORMAT_VAL(type_uuid));
+ SD_ID128_FORMAT_VAL(type.uuid));
r = find_backing_devno(try1, root, &devno);
if (r == -ENOENT && try2)
r = find_backing_devno(try2, root, &devno);
if (r < 0)
return log_error_errno(r, "Failed to resolve automatic CopyBlocks= path for partition type " SD_ID128_FORMAT_STR ", sorry: %m",
- SD_ID128_FORMAT_VAL(type_uuid));
+ SD_ID128_FORMAT_VAL(type.uuid));
xsprintf_sys_block_path(p, "/slaves", devno);
d = opendir(p);
continue;
}
- r = resolve_copy_blocks_auto_candidate(sl, type_uuid, restrict_devno, &u);
+ r = resolve_copy_blocks_auto_candidate(sl, type, restrict_devno, &u);
if (r < 0)
return r;
if (r > 0) {
} else if (errno != ENOENT)
return log_error_errno(errno, "Failed open %s: %m", p);
else {
- r = resolve_copy_blocks_auto_candidate(devno, type_uuid, restrict_devno, &found_uuid);
+ r = resolve_copy_blocks_auto_candidate(devno, type, restrict_devno, &found_uuid);
if (r < 0)
return r;
if (r > 0)
static int context_open_copy_block_paths(
Context *context,
- const char *root,
dev_t restrict_devno) {
int r;
if (p->copy_blocks_path) {
- source_fd = chase_symlinks_and_open(p->copy_blocks_path, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
+ source_fd = chase_symlinks_and_open(p->copy_blocks_path, p->copy_blocks_root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
if (source_fd < 0)
return log_error_errno(source_fd, "Failed to open '%s': %m", p->copy_blocks_path);
} else if (p->copy_blocks_auto) {
dev_t devno;
- r = resolve_copy_blocks_auto(p->type_uuid, root, restrict_devno, &devno, &uuid);
+ r = resolve_copy_blocks_auto(p->type, p->copy_blocks_root, restrict_devno, &devno, &uuid);
if (r < 0)
return r;
return 0;
}
+static int fd_apparent_size(int fd, uint64_t *ret) {
+ off_t initial = 0;
+ uint64_t size = 0;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ initial = lseek(fd, 0, SEEK_CUR);
+ if (initial < 0)
+ return log_error_errno(errno, "Failed to get file offset: %m");
+
+ for (off_t off = 0;;) {
+ off_t r;
+
+ r = lseek(fd, off, SEEK_DATA);
+ if (r < 0 && errno == ENXIO)
+ /* If errno == ENXIO, that means we've reached the final hole of the file and
+ * that hole isn't followed by more data. */
+ break;
+ if (r < 0)
+ return log_error_errno(errno, "Failed to seek data in file from offset %"PRIi64": %m", off);
+
+ off = r; /* Set the offset to the start of the data segment. */
+
+ /* After copying a potential hole, find the end of the data segment by looking for
+ * the next hole. If we get ENXIO, we're at EOF. */
+ r = lseek(fd, off, SEEK_HOLE);
+ if (r < 0) {
+ if (errno == ENXIO)
+ break;
+ return log_error_errno(errno, "Failed to seek hole in file from offset %"PRIi64": %m", off);
+ }
+
+ size += r - off;
+ off = r;
+ }
+
+ if (lseek(fd, initial, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to reset file offset: %m");
+
+ *ret = size;
+
+ return 0;
+}
+
+static int context_minimize(Context *context) {
+ _cleanup_set_free_ Set *denylist = NULL;
+ const char *vt;
+ int r;
+
+ assert(context);
+
+ r = make_copy_files_denylist(context, &denylist);
+ if (r < 0)
+ return r;
+
+ r = var_tmp_dir(&vt);
+ if (r < 0)
+ return log_error_errno(r, "Could not determine temporary directory: %m");
+
+ LIST_FOREACH(partitions, p, context->partitions) {
+ _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
+ _cleanup_(unlink_and_freep) char *temp = NULL;
+ _cleanup_close_ int fd = -1;
+ sd_id128_t fs_uuid;
+ uint64_t fsz;
+
+ if (p->dropped)
+ continue;
+
+ if (PARTITION_EXISTS(p)) /* Never format existing partitions */
+ continue;
+
+ if (!p->format)
+ continue;
+
+ if (!p->minimize)
+ continue;
+
+ assert(!p->copy_blocks_path);
+
+ r = tempfn_random_child(vt, "repart", &temp);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate temporary file path: %m");
+
+ if (fstype_is_ro(p->format))
+ fs_uuid = p->fs_uuid;
+ else {
+ fd = open(temp, O_CREAT|O_EXCL|O_CLOEXEC|O_RDWR|O_NOCTTY, 0600);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open temporary file %s: %m", temp);
+
+ /* This may seem huge but it will be created sparse so it doesn't take up any space
+ * on disk until written to. */
+ if (ftruncate(fd, 1024ULL * 1024ULL * 1024ULL * 1024ULL) < 0)
+ return log_error_errno(errno, "Failed to truncate temporary file to %s: %m",
+ FORMAT_BYTES(1024ULL * 1024ULL * 1024ULL * 1024ULL));
+
+ /* We're going to populate this filesystem twice so use a random UUID the first time
+ * to avoid UUID conflicts. */
+ r = sd_id128_randomize(&fs_uuid);
+ if (r < 0)
+ return r;
+ }
+
+ if (mkfs_supports_root_option(p->format)) {
+ r = partition_populate_directory(p, denylist, &root);
+ if (r < 0)
+ return r;
+ }
+
+ r = make_filesystem(temp, p->format, strempty(p->new_label), root, fs_uuid, arg_discard);
+ if (r < 0)
+ return r;
+
+ /* Read-only filesystems are minimal from the first try because they create and size the
+ * loopback file for us. */
+ if (fstype_is_ro(p->format)) {
+ p->copy_blocks_path = TAKE_PTR(temp);
+ continue;
+ }
+
+ if (!mkfs_supports_root_option(p->format)) {
+ r = partition_populate_filesystem(p, temp, denylist);
+ if (r < 0)
+ return r;
+ }
+
+ /* Other filesystems need to be provided with a pre-sized loopback file and will adapt to
+ * fully occupy it. Because we gave the filesystem a 1T sparse file, we need to shrink the
+ * filesystem down to a reasonable size again to fit it in the disk image. While there are
+ * some filesystems that support shrinking, it doesn't always work properly (e.g. shrinking
+ * btrfs gives us a 2.0G filesystem regardless of what we put in it). Instead, let's populate
+ * the filesystem again, but this time, instead of providing the filesystem with a 1T sparse
+ * loopback file, let's size the loopback file based on the actual data used by the
+ * filesystem in the sparse file after the first attempt. This should be a good guess of the
+ * minimal amount of space needed in the filesystem to fit all the required data.
+ */
+ r = fd_apparent_size(fd, &fsz);
+ if (r < 0)
+ return r;
+
+ /* Massage the size a bit because just going by actual data used in the sparse file isn't
+ * fool-proof. */
+ fsz = round_up_size(fsz + (fsz / 2), context->grain_size);
+ if (minimal_size_by_fs_name(p->format) != UINT64_MAX)
+ fsz = MAX(minimal_size_by_fs_name(p->format), fsz);
+
+ /* Erase the previous filesystem first. */
+ if (ftruncate(fd, 0))
+ return log_error_errno(errno, "Failed to erase temporary file: %m");
+
+ if (ftruncate(fd, fsz))
+ return log_error_errno(errno, "Failed to truncate temporary file to %s: %m", FORMAT_BYTES(fsz));
+
+ r = make_filesystem(temp, p->format, strempty(p->new_label), root, p->fs_uuid, arg_discard);
+ if (r < 0)
+ return r;
+
+ if (!mkfs_supports_root_option(p->format)) {
+ r = partition_populate_filesystem(p, temp, denylist);
+ if (r < 0)
+ return r;
+ }
+
+ p->copy_blocks_path = TAKE_PTR(temp);
+ }
+
+ return 0;
+}
+
+static int parse_filter_partitions(const char *p) {
+ int r;
+
+ for (;;) {
+ _cleanup_free_ char *name = NULL;
+ GptPartitionType type;
+
+ r = extract_first_word(&p, &name, ",", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r == 0)
+ break;
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract partition designator: %s", optarg);
+
+ r = gpt_partition_type_from_string(name, &type);
+ if (r < 0)
+ return log_error_errno(r, "'%s' is not a valid partition designator", name);
+
+ if (!GREEDY_REALLOC(arg_filter_partitions, arg_filter_partitions_size + 1))
+ return log_oom();
+
+ arg_filter_partitions[arg_filter_partitions_size++] = type.uuid;
+ }
+
+ return 0;
+}
+
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --split=BOOL Whether to generate split artifacts\n"
+ " --include-partitions=PARTITION1,PARTITION2,PARTITION3,…\n"
+ " Only operate on partitions of the specified types\n"
+ " --exclude-partitions=PARTITION1,PARTITION2,PARTITION3,…\n"
+ " Don't operate on partitions of the specified types\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ARG_TPM2_PUBLIC_KEY,
ARG_TPM2_PUBLIC_KEY_PCRS,
ARG_SPLIT,
+ ARG_INCLUDE_PARTITIONS,
+ ARG_EXCLUDE_PARTITIONS,
};
static const struct option options[] = {
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
{ "split", required_argument, NULL, ARG_SPLIT },
+ { "include-partitions", required_argument, NULL, ARG_INCLUDE_PARTITIONS },
+ { "exclude-partitions", required_argument, NULL, ARG_EXCLUDE_PARTITIONS },
{}
};
arg_split = r;
break;
+ case ARG_INCLUDE_PARTITIONS:
+ if (arg_filter_partitions_type == FILTER_PARTITIONS_EXCLUDE)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Combination of --include-partitions= and --exclude-partitions= is invalid.");
+
+ r = parse_filter_partitions(optarg);
+ if (r < 0)
+ return r;
+
+ arg_filter_partitions_type = FILTER_PARTITIONS_INCLUDE;
+
+ break;
+
+ case ARG_EXCLUDE_PARTITIONS:
+ if (arg_filter_partitions_type == FILTER_PARTITIONS_INCLUDE)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Combination of --include-partitions= and --exclude-partitions= is invalid.");
+
+ r = parse_filter_partitions(optarg);
+ if (r < 0)
+ return r;
+
+ arg_filter_partitions_type = FILTER_PARTITIONS_EXCLUDE;
+
+ break;
+
case '?':
return -EINVAL;
if (r < 0)
return r;
+ /* Make sure each partition has a unique UUID and unique label */
+ r = context_acquire_partition_uuids_and_labels(context);
+ if (r < 0)
+ return r;
+
+ r = context_minimize(context);
+ if (r < 0)
+ return r;
+
/* Open all files to copy blocks from now, since we want to take their size into consideration */
r = context_open_copy_block_paths(
context,
- arg_root,
loop_device ? loop_device->devno : /* if --image= is specified, only allow partitions on the loopback device */
arg_root && !arg_image ? 0 : /* if --root= is specified, don't accept any block device */
(dev_t) -1); /* if neither is specified, make no restrictions */
/* Now calculate where each new partition gets placed */
context_place_partitions(context);
- /* Make sure each partition has a unique UUID and unique label */
- r = context_acquire_partition_uuids_and_labels(context);
- if (r < 0)
- return r;
-
(void) context_dump(context, node, /*late=*/ false);
r = context_write_partition_table(context, node, from_scratch);
#include "alloc-util.h"
#include "build.h"
+#include "chase-symlinks.h"
+#include "efi-loader.h"
#include "fd-util.h"
+#include "find-esp.h"
#include "fs-util.h"
#include "io-util.h"
#include "log.h"
#include "mkdir.h"
#include "parse-argument.h"
#include "parse-util.h"
+#include "path-util.h"
#include "pretty-print.h"
#include "random-util.h"
#include "string-table.h"
if (ret_hash_state) {
struct sha256_ctx *hash_state;
- hash_state = malloc(sizeof(struct sha256_ctx));
+ hash_state = new(struct sha256_ctx, 1);
if (!hash_state)
return log_oom();
return 0;
}
+static int refresh_boot_seed(void) {
+ uint8_t buffer[RANDOM_EFI_SEED_SIZE];
+ struct sha256_ctx hash_state;
+ _cleanup_free_ void *seed_file_bytes = NULL;
+ _cleanup_free_ char *esp_path = NULL;
+ _cleanup_close_ int seed_fd = -1, dir_fd = -1;
+ size_t len;
+ ssize_t n;
+ int r;
+
+ assert_cc(RANDOM_EFI_SEED_SIZE == SHA256_DIGEST_SIZE);
+
+ r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path,
+ NULL, NULL, NULL, NULL, NULL);
+ if (r < 0) {
+ if (r == -ENOKEY) {
+ log_debug_errno(r, "Couldn't find any ESP, so not updating ESP random seed.");
+ return 0;
+ }
+ return r; /* find_esp_and_warn() already logged */
+ }
+
+ r = chase_symlinks("/loader", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, &dir_fd);
+ if (r < 0) {
+ if (r == -ENOENT) {
+ log_debug_errno(r, "Couldn't find ESP loader directory, so not updating ESP random seed.");
+ return 0;
+ }
+ return log_error_errno(r, "Failed to open ESP loader directory: %m");
+ }
+ seed_fd = openat(dir_fd, "random-seed", O_NOFOLLOW|O_RDWR|O_CLOEXEC|O_NOCTTY);
+ if (seed_fd < 0 && errno == ENOENT) {
+ uint64_t features;
+ r = efi_loader_get_features(&features);
+ if (r == 0 && FLAGS_SET(features, EFI_LOADER_FEATURE_RANDOM_SEED))
+ seed_fd = openat(dir_fd, "random-seed", O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
+ else {
+ log_debug_errno(seed_fd, "Couldn't find ESP random seed, and not booted with systemd-boot, so not updating ESP random seed.");
+ return 0;
+ }
+ }
+ if (seed_fd < 0)
+ return log_error_errno(errno, "Failed to open EFI seed path: %m");
+ r = random_seed_size(seed_fd, &len);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine EFI seed path length: %m");
+ seed_file_bytes = malloc(len);
+ if (!seed_file_bytes)
+ return log_oom();
+ n = loop_read(seed_fd, seed_file_bytes, len, false);
+ if (n < 0)
+ return log_error_errno(n, "Failed to read EFI seed file: %m");
+
+ /* Hash the old seed in so that we never regress in entropy. */
+ sha256_init_ctx(&hash_state);
+ sha256_process_bytes(&n, sizeof(n), &hash_state);
+ sha256_process_bytes(seed_file_bytes, n, &hash_state);
+
+ /* We're doing this opportunistically, so if the seeding dance before didn't manage to initialize the
+ * RNG, there's no point in doing it here. Secondly, getrandom(GRND_NONBLOCK) has been around longer
+ * than EFI seeding anyway, so there's no point in having non-getrandom() fallbacks here. So if this
+ * fails, just return early to cut our losses. */
+ n = getrandom(buffer, sizeof(buffer), GRND_NONBLOCK);
+ if (n < 0) {
+ if (errno == EAGAIN) {
+ log_debug_errno(errno, "Random pool not initialized yet, so skipping EFI seed update");
+ return 0;
+ }
+ if (errno == ENOSYS) {
+ log_debug_errno(errno, "getrandom() not available, so skipping EFI seed update");
+ return 0;
+ }
+ return log_error_errno(errno, "Failed to generate random bytes for EFI seed: %m");
+ }
+ assert(n == sizeof(buffer));
+
+ /* Hash the new seed into the state containing the old one to generate our final seed. */
+ sha256_process_bytes(&n, sizeof(n), &hash_state);
+ sha256_process_bytes(buffer, n, &hash_state);
+ sha256_finish_ctx(&hash_state, buffer);
+
+ if (lseek(seed_fd, 0, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek to beginning of EFI seed file: %m");
+ r = loop_write(seed_fd, buffer, sizeof(buffer), false);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write new EFI seed file: %m");
+ if (ftruncate(seed_fd, sizeof(buffer)) < 0)
+ return log_error_errno(errno, "Failed to truncate EFI seed file: %m");
+ r = fsync_full(seed_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to fsync EFI seed file: %m");
+
+ log_debug("Updated random seed in ESP");
+ return 0;
+}
+
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
int r;
if (r < 0)
return log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m");
+ random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY);
+ if (random_fd < 0)
+ return log_error_errno(errno, "Failed to open /dev/urandom: %m");
+
/* When we load the seed we read it and write it to the device and then immediately update the saved
* seed with new data, to make sure the next boot gets seeded differently. */
switch (arg_action) {
case ACTION_LOAD:
- random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY);
- if (random_fd < 0)
- return log_error_errno(errno, "Failed to open /dev/urandom: %m");
-
/* First, let's write the machine ID into /dev/urandom, not crediting entropy. See
* load_machine_id() for an explanation why. */
load_machine_id(random_fd);
log_full_errno(level, open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m");
log_full_errno(level, errno, "Failed to open " RANDOM_SEED " for reading: %m");
+ r = -errno;
- return missing ? 0 : -errno;
+ (void) refresh_boot_seed();
+ return missing ? 0 : r;
}
} else
write_seed_file = true;
break;
case ACTION_SAVE:
- random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (random_fd < 0)
- return log_error_errno(errno, "Failed to open /dev/urandom: %m");
-
+ (void) refresh_boot_seed();
seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
if (seed_fd < 0)
return log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m");
if (r < 0)
return r;
- if (read_seed_file)
+ if (read_seed_file) {
r = load_seed_file(seed_fd, random_fd, seed_size,
write_seed_file ? &hash_state : NULL);
+ (void) refresh_boot_seed();
+ }
if (r >= 0 && write_seed_file)
r = save_seed_file(seed_fd, random_fd, seed_size, synchronous, hash_state);
#include "pretty-print.h"
#include "process-util.h"
#include "resolvconf-compat.h"
+#include "resolve-util.h"
#include "resolvectl.h"
#include "resolved-def.h"
#include "resolved-dns-packet.h"
if (r < 0)
return bus_log_parse_error(r);
- table = table_new("key", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
r = table_add_many(table,
TABLE_STRING, "Transactions",
TABLE_SET_COLOR, ansi_highlight(),
+ TABLE_SET_ALIGN_PERCENT, 0,
TABLE_EMPTY,
- TABLE_STRING, "Current Transactions:",
+ TABLE_FIELD, "Current Transactions",
TABLE_SET_ALIGN_PERCENT, 100,
TABLE_UINT64, n_current_transactions,
- TABLE_STRING, "Total Transactions:",
+ TABLE_SET_ALIGN_PERCENT, 100,
+ TABLE_FIELD, "Total Transactions",
TABLE_UINT64, n_total_transactions,
TABLE_EMPTY, TABLE_EMPTY,
TABLE_STRING, "Cache",
TABLE_SET_COLOR, ansi_highlight(),
TABLE_SET_ALIGN_PERCENT, 0,
TABLE_EMPTY,
- TABLE_STRING, "Current Cache Size:",
+ TABLE_FIELD, "Current Cache Size",
TABLE_SET_ALIGN_PERCENT, 100,
TABLE_UINT64, cache_size,
- TABLE_STRING, "Cache Hits:",
+ TABLE_FIELD, "Cache Hits",
TABLE_UINT64, n_cache_hit,
- TABLE_STRING, "Cache Misses:",
+ TABLE_FIELD, "Cache Misses",
TABLE_UINT64, n_cache_miss,
TABLE_EMPTY, TABLE_EMPTY,
TABLE_STRING, "DNSSEC Verdicts",
TABLE_SET_COLOR, ansi_highlight(),
TABLE_SET_ALIGN_PERCENT, 0,
TABLE_EMPTY,
- TABLE_STRING, "Secure:",
+ TABLE_FIELD, "Secure",
TABLE_SET_ALIGN_PERCENT, 100,
TABLE_UINT64, n_dnssec_secure,
- TABLE_STRING, "Insecure:",
+ TABLE_FIELD, "Insecure",
TABLE_UINT64, n_dnssec_insecure,
- TABLE_STRING, "Bogus:",
+ TABLE_FIELD, "Bogus",
TABLE_UINT64, n_dnssec_bogus,
- TABLE_STRING, "Indeterminate:",
+ TABLE_FIELD, "Indeterminate:",
TABLE_UINT64, n_dnssec_indeterminate);
if (r < 0)
- table_log_add_error(r);
+ return table_log_add_error(r);
r = table_print(table, NULL);
if (r < 0)
strv_free(p->ntas);
}
-static int dump_list(Table *table, const char *prefix, char * const *l) {
+static int dump_list(Table *table, const char *field, char * const *l) {
int r;
if (strv_isempty(l))
return 0;
r = table_add_many(table,
- TABLE_STRING, prefix,
+ TABLE_FIELD, field,
TABLE_STRV_WRAPPED, l);
if (r < 0)
return table_log_add_error(r);
printf("%sLink %i (%s)%s\n",
ansi_highlight(), ifindex, name, ansi_normal());
- table = table_new("key", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
r = table_add_many(table,
- TABLE_STRING, "Current Scopes:",
- TABLE_SET_ALIGN_PERCENT, 100);
+ TABLE_FIELD, "Current Scopes",
+ TABLE_SET_MINIMUM_WIDTH, 19);
if (r < 0)
return table_log_add_error(r);
return log_oom();
r = table_add_many(table,
- TABLE_STRING, "Protocols:",
+ TABLE_FIELD, "Protocols",
TABLE_STRV_WRAPPED, pstatus);
if (r < 0)
return table_log_add_error(r);
if (link_info.current_dns) {
r = table_add_many(table,
- TABLE_STRING, "Current DNS Server:",
+ TABLE_FIELD, "Current DNS Server",
TABLE_STRING, link_info.current_dns_ex ?: link_info.current_dns);
if (r < 0)
return table_log_add_error(r);
}
- r = dump_list(table, "DNS Servers:", link_info.dns_ex ?: link_info.dns);
+ r = dump_list(table, "DNS Servers", link_info.dns_ex ?: link_info.dns);
if (r < 0)
return r;
- r = dump_list(table, "DNS Domain:", link_info.domains);
+ r = dump_list(table, "DNS Domain", link_info.domains);
if (r < 0)
return r;
printf("%sGlobal%s\n", ansi_highlight(), ansi_normal());
- table = table_new("key", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
_cleanup_strv_free_ char **pstatus = global_protocol_status(&global_info);
if (!pstatus)
return log_oom();
r = table_add_many(table,
- TABLE_STRING, "Protocols:",
- TABLE_SET_ALIGN_PERCENT, 100,
+ TABLE_FIELD, "Protocols",
+ TABLE_SET_MINIMUM_WIDTH, 19,
TABLE_STRV_WRAPPED, pstatus);
if (r < 0)
return table_log_add_error(r);
if (global_info.resolv_conf_mode) {
r = table_add_many(table,
- TABLE_STRING, "resolv.conf mode:",
+ TABLE_FIELD, "resolv.conf mode",
TABLE_STRING, global_info.resolv_conf_mode);
if (r < 0)
return table_log_add_error(r);
if (global_info.current_dns) {
r = table_add_many(table,
- TABLE_STRING, "Current DNS Server:",
+ TABLE_FIELD, "Current DNS Server",
TABLE_STRING, global_info.current_dns_ex ?: global_info.current_dns);
if (r < 0)
return table_log_add_error(r);
static int verb_llmnr(int argc, char **argv, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *global_llmnr_support_str = NULL;
+ ResolveSupport global_llmnr_support, llmnr_support;
sd_bus *bus = ASSERT_PTR(userdata);
int r;
if (argc < 3)
return status_ifindex(bus, arg_ifindex, NULL, STATUS_LLMNR, NULL);
+ llmnr_support = resolve_support_from_string(argv[2]);
+ if (llmnr_support < 0)
+ return log_error_errno(llmnr_support, "Invalid LLMNR setting: %s", argv[2]);
+
+ r = bus_get_property_string(bus, bus_resolve_mgr, "LLMNR", &error, &global_llmnr_support_str);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get the global LLMNR support state: %s", bus_error_message(&error, r));
+
+ global_llmnr_support = resolve_support_from_string(global_llmnr_support_str);
+ if (global_llmnr_support < 0)
+ return log_error_errno(global_llmnr_support, "Received invalid global LLMNR setting: %s", global_llmnr_support_str);
+
+ if (global_llmnr_support < llmnr_support)
+ log_warning("Setting LLMNR support level \"%s\" for \"%s\", but the global support level is \"%s\".",
+ argv[2], arg_ifname, global_llmnr_support_str);
+
r = bus_call_method(bus, bus_resolve_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]);
if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) {
sd_bus_error_free(&error);
static int verb_mdns(int argc, char **argv, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *global_mdns_support_str = NULL;
+ ResolveSupport global_mdns_support, mdns_support;
sd_bus *bus = ASSERT_PTR(userdata);
int r;
if (argc < 3)
return status_ifindex(bus, arg_ifindex, NULL, STATUS_MDNS, NULL);
+ mdns_support = resolve_support_from_string(argv[2]);
+ if (mdns_support < 0)
+ return log_error_errno(mdns_support, "Invalid mDNS setting: %s", argv[2]);
+
+ r = bus_get_property_string(bus, bus_resolve_mgr, "MulticastDNS", &error, &global_mdns_support_str);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get the global mDNS support state: %s", bus_error_message(&error, r));
+
+ global_mdns_support = resolve_support_from_string(global_mdns_support_str);
+ if (global_mdns_support < 0)
+ return log_error_errno(global_mdns_support, "Received invalid global mDNS setting: %s", global_mdns_support_str);
+
+ if (global_mdns_support < mdns_support)
+ log_warning("Setting mDNS support level \"%s\" for \"%s\", but the global support level is \"%s\".",
+ argv[2], arg_ifname, global_mdns_support_str);
+
r = bus_call_method(bus, bus_resolve_mgr, "SetLinkMulticastDNS", &error, NULL, "is", arg_ifindex, argv[2]);
if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) {
sd_bus_error_free(&error);
struct DnsQuestion {
unsigned n_ref;
size_t n_keys, n_allocated;
- DnsQuestionItem items[0];
+ DnsQuestionItem items[];
};
DnsQuestion *dns_question_new(size_t n);
#define DNS_SERVER_FEATURE_LEVEL_IS_DNSSEC(x) ((x) >= DNS_SERVER_FEATURE_LEVEL_DO)
#define DNS_SERVER_FEATURE_LEVEL_IS_UDP(x) IN_SET(x, DNS_SERVER_FEATURE_LEVEL_UDP, DNS_SERVER_FEATURE_LEVEL_EDNS0, DNS_SERVER_FEATURE_LEVEL_DO)
-const char* dns_server_feature_level_to_string(int i) _const_;
-int dns_server_feature_level_from_string(const char *s) _pure_;
+const char* dns_server_feature_level_to_string(DnsServerFeatureLevel i) _const_;
+DnsServerFeatureLevel dns_server_feature_level_from_string(const char *s) _pure_;
struct DnsServer {
Manager *manager;
/* Defined by RFC 8375. The most official choice. */
"home.arpa\0";
- const char *name;
int r;
assert(d);
static BUS_DEFINE_PROPERTY_GET(property_get_dnssec_supported, "b", Link, link_dnssec_supported);
static BUS_DEFINE_PROPERTY_GET2(property_get_dnssec_mode, "s", Link, link_get_dnssec_mode, dnssec_mode_to_string);
+static BUS_DEFINE_PROPERTY_GET2(property_get_llmnr_support, "s", Link, link_get_llmnr_support, resolve_support_to_string);
+static BUS_DEFINE_PROPERTY_GET2(property_get_mdns_support, "s", Link, link_get_mdns_support, resolve_support_to_string);
static int property_get_dns_over_tls_mode(
sd_bus *bus,
SD_BUS_PROPERTY("CurrentDNSServerEx", "(iayqs)", property_get_current_dns_server_ex, offsetof(Link, current_dns_server), 0),
SD_BUS_PROPERTY("Domains", "a(sb)", property_get_domains, 0, 0),
SD_BUS_PROPERTY("DefaultRoute", "b", property_get_default_route, 0, 0),
- SD_BUS_PROPERTY("LLMNR", "s", bus_property_get_resolve_support, offsetof(Link, llmnr_support), 0),
- SD_BUS_PROPERTY("MulticastDNS", "s", bus_property_get_resolve_support, offsetof(Link, mdns_support), 0),
+ SD_BUS_PROPERTY("LLMNR", "s", property_get_llmnr_support, 0, 0),
+ SD_BUS_PROPERTY("MulticastDNS", "s", property_get_mdns_support, 0, 0),
SD_BUS_PROPERTY("DNSOverTLS", "s", property_get_dns_over_tls_mode, 0, 0),
SD_BUS_PROPERTY("DNSSEC", "s", property_get_dnssec_mode, 0, 0),
SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0),
l->unicast_scope = dns_scope_free(l->unicast_scope);
if (link_relevant(l, AF_INET, true) &&
- l->llmnr_support != RESOLVE_SUPPORT_NO &&
- l->manager->llmnr_support != RESOLVE_SUPPORT_NO) {
+ link_get_llmnr_support(l) != RESOLVE_SUPPORT_NO) {
if (!l->llmnr_ipv4_scope) {
r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET);
if (r < 0)
l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope);
if (link_relevant(l, AF_INET6, true) &&
- l->llmnr_support != RESOLVE_SUPPORT_NO &&
- l->manager->llmnr_support != RESOLVE_SUPPORT_NO &&
- socket_ipv6_is_supported()) {
+ link_get_llmnr_support(l) != RESOLVE_SUPPORT_NO) {
if (!l->llmnr_ipv6_scope) {
r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6);
if (r < 0)
l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope);
if (link_relevant(l, AF_INET, true) &&
- l->mdns_support != RESOLVE_SUPPORT_NO &&
- l->manager->mdns_support != RESOLVE_SUPPORT_NO) {
+ link_get_mdns_support(l) != RESOLVE_SUPPORT_NO) {
if (!l->mdns_ipv4_scope) {
r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET);
if (r < 0)
l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope);
if (link_relevant(l, AF_INET6, true) &&
- l->mdns_support != RESOLVE_SUPPORT_NO &&
- l->manager->mdns_support != RESOLVE_SUPPORT_NO) {
+ link_get_mdns_support(l) != RESOLVE_SUPPORT_NO) {
if (!l->mdns_ipv6_scope) {
r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6);
if (r < 0)
link_address_add_rrs(a, force_remove);
if (!force_remove &&
- l->mdns_support == RESOLVE_SUPPORT_YES &&
- l->manager->mdns_support == RESOLVE_SUPPORT_YES) {
+ link_get_mdns_support(l) == RESOLVE_SUPPORT_YES) {
if (l->mdns_ipv4_scope) {
r = dns_scope_add_dnssd_services(l->mdns_ipv4_scope);
if (r < 0)
return r;
- if (l->llmnr_support != RESOLVE_SUPPORT_NO) {
+ if (link_get_llmnr_support(l) != RESOLVE_SUPPORT_NO) {
r = manager_llmnr_start(l->manager);
if (r < 0)
return r;
}
- if (l->mdns_support != RESOLVE_SUPPORT_NO) {
+ if (link_get_mdns_support(l) != RESOLVE_SUPPORT_NO) {
r = manager_mdns_start(l->manager);
if (r < 0)
return r;
return true;
}
+ResolveSupport link_get_llmnr_support(Link *link) {
+ assert(link);
+ assert(link->manager);
+
+ /* This provides the effective LLMNR support level for the link, instead of the 'internal' per-link setting. */
+
+ return MIN(link->llmnr_support, link->manager->llmnr_support);
+}
+
+ResolveSupport link_get_mdns_support(Link *link) {
+ assert(link);
+ assert(link->manager);
+
+ /* This provides the effective mDNS support level for the link, instead of the 'internal' per-link setting. */
+
+ return MIN(link->mdns_support, link->manager->mdns_support);
+}
+
int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) {
LinkAddress *a;
if (!force_remove &&
link_address_relevant(a, true) &&
a->link->llmnr_ipv4_scope &&
- a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
- a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
+ link_get_llmnr_support(a->link) == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->llmnr_host_ipv4_key) {
a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname);
if (!force_remove &&
link_address_relevant(a, true) &&
a->link->mdns_ipv4_scope &&
- a->link->mdns_support == RESOLVE_SUPPORT_YES &&
- a->link->manager->mdns_support == RESOLVE_SUPPORT_YES) {
+ link_get_mdns_support(a->link) == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->mdns_host_ipv4_key) {
a->link->manager->mdns_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->mdns_hostname);
if (!a->link->manager->mdns_host_ipv4_key) {
if (!force_remove &&
link_address_relevant(a, true) &&
a->link->llmnr_ipv6_scope &&
- a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
- a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
+ link_get_llmnr_support(a->link) == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->llmnr_host_ipv6_key) {
a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname);
if (!force_remove &&
link_address_relevant(a, true) &&
a->link->mdns_ipv6_scope &&
- a->link->mdns_support == RESOLVE_SUPPORT_YES &&
- a->link->manager->mdns_support == RESOLVE_SUPPORT_YES) {
+ link_get_mdns_support(a->link) == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->mdns_host_ipv6_key) {
a->link->manager->mdns_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->mdns_hostname);
DnsOverTlsMode link_get_dns_over_tls_mode(Link *l);
+ResolveSupport link_get_llmnr_support(Link *link);
+ResolveSupport link_get_mdns_support(Link *link);
+
int link_save_user(Link *l);
int link_load_user(Link *l);
void link_remove_user(Link *l);
uint64_t exit_services_exit;
} _packed;
+/* /dev/mem is deprecated on many systems, try using /sys/firmware/acpi/fpdt parsing instead.
+ * This code requires kernel version 5.12 on x86 based machines or 6.2 for arm64 */
+static int acpi_get_boot_usec_kernel_parsed(usec_t *ret_loader_start, usec_t *ret_loader_exit) {
+ usec_t start, end;
+ int r;
+
+ r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/exitbootservice_end_ns", &end);
+ if (r < 0)
+ return r;
+
+ if (end == 0)
+ /* Non-UEFI compatible boot. */
+ return -ENODATA;
+
+ r = read_timestamp_file("/sys/firmware/acpi/fpdt/boot/bootloader_launch_ns", &start);
+ if (r < 0)
+ return r;
+
+ if (start == 0 || end < start)
+ return -EINVAL;
+ if (end > NSEC_PER_HOUR)
+ return -EINVAL;
+
+ if (ret_loader_start)
+ *ret_loader_start = start / 1000;
+ if (ret_loader_exit)
+ *ret_loader_exit = end / 1000;
+
+ return 0;
+}
+
int acpi_get_boot_usec(usec_t *ret_loader_start, usec_t *ret_loader_exit) {
_cleanup_free_ char *buf = NULL;
struct acpi_table_header *tbl;
struct acpi_fpdt_boot_header hbrec;
struct acpi_fpdt_boot brec;
+ r = acpi_get_boot_usec_kernel_parsed(ret_loader_start, ret_loader_exit);
+ if (r != -ENOENT) /* fallback to /dev/mem hack only if kernel doesn't support the new sysfs files */
+ return r;
+
r = read_full_virtual_file("/sys/firmware/acpi/tables/FPDT", &buf, &l);
if (r < 0)
return r;
#include "memory-util.h"
#include "missing_syscall.h"
#include "mkdir-label.h"
+#include "nulstr-util.h"
#include "process-util.h"
#include "random-util.h"
#include "signal-util.h"
if (r < 0)
return r;
+ /* chop off the final NUL byte. We do this because we want to use the separator NUL bytes only if we
+ * have multiple passwords. */
+ n = LESS_BY(n, (size_t) 1);
+
serial = add_key("user", keyname, p, n, KEY_SPEC_USER_KEYRING);
if (serial == -1)
return -errno;
continue;
if (table[i].target) {
- const char *target = NULL, *s;
+ const char *target = NULL;
/* check if one of the targets exists */
NULSTR_FOREACH(s, table[i].target) {
#include "pretty-print.h"
#include "recurse-dir.h"
#include "sort-util.h"
+#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
#include "terminal-util.h"
assert(config);
assert(path);
- r = chase_symlinks_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", &full, &f);
+ r = chase_symlinks_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
if (r == -ENOENT)
return 0;
if (r < 0)
return -strverscmp_improved(a->id, b->id);
}
-static void inode_hash_func(const struct stat *q, struct siphash *state) {
- siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
- siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
-}
-
-static int inode_compare_func(const struct stat *a, const struct stat *b) {
- int r;
-
- r = CMP(a->st_dev, b->st_dev);
- if (r != 0)
- return r;
-
- return CMP(a->st_ino, b->st_ino);
-}
-
-DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
-
static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
_cleanup_free_ char *d = NULL;
struct stat st;
assert(root);
assert(dir);
- dir_fd = chase_symlinks_and_open(dir, root, CHASE_PREFIX_ROOT, O_DIRECTORY|O_CLOEXEC, &full);
+ dir_fd = chase_symlinks_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
if (dir_fd == -ENOENT)
return 0;
if (dir_fd < 0)
if (!tmp.root)
return log_oom();
- tmp.kernel = strdup(skip_leading_chars(k, "/"));
+ tmp.kernel = path_make_absolute(k, "/");
if (!tmp.kernel)
return log_oom();
assert(config);
assert(dir);
- r = chase_symlinks_and_opendir(dir, root, CHASE_PREFIX_ROOT, &full, &d);
+ r = chase_symlinks_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
if (!id)
return -1;
+ if (id[0] == '@') {
+ if (!strcaseeq(id, "@saved"))
+ return -1;
+ id = config->entry_selected;
+ }
+
for (size_t i = 0; i < config->n_entries; i++)
if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
return i;
assert(p);
assert(ret_status);
- int status = chase_symlinks_and_access(p, root, CHASE_PREFIX_ROOT, F_OK, NULL, NULL);
+ int status = chase_symlinks_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL, NULL);
+
+ /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
+ * the abscence of color support) to the user that the boot loader is only interested in the second
+ * part of the file. */
+ printf("%13s%s %s%s/%s", strempty(field), field ? ":" : " ", ansi_grey(), root, ansi_normal());
- printf("%13s%s ", strempty(field), field ? ":" : " ");
if (status < 0) {
errno = -status;
printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
assert(infd >= 0);
assert(outfd >= 0);
- assert(sz > 0);
r = fd_verify_regular(outfd);
if (r < 0)
bus_print_property_value(name, expected_value, flags, "[not set]");
- else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "MemoryAvailable") && u == CGROUP_LIMIT_MAX) ||
+ else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit", "MemoryAvailable") && u == CGROUP_LIMIT_MAX) ||
(STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) ||
(startswith(name, "Limit") && u == UINT64_MAX) ||
(startswith(name, "DefaultLimit") && u == UINT64_MAX))
"MemoryHigh",
"MemoryMax",
"MemorySwapMax",
+ "MemoryZSwapMax",
"MemoryLimit",
"TasksMax")) {
if (FLAGS_SET(flags, OUTPUT_CGROUP_XATTRS) && fd >= 0) {
_cleanup_free_ char *nl = NULL;
- char *xa;
r = flistxattr_malloc(fd, &nl);
if (r < 0)
#include "fs-util.h"
#include "glob-util.h"
#include "hostname-util.h"
-#include "initrd-util.h"
#include "ima-util.h"
+#include "initrd-util.h"
#include "limits-util.h"
#include "list.h"
#include "macro.h"
#include "mountpoint-util.h"
+#include "nulstr-util.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
if (sections && !nulstr_contains(sections, n)) {
bool ignore;
- const char *t;
ignore = (flags & CONFIG_PARSE_RELAXED) || startswith(n, "X-");
uid_t override_uid,
gid_t override_gid,
CopyFlags copy_flags,
+ const Set *denylist,
HardlinkContext *hardlink_context,
const char *display_path,
copy_progress_path_t progress_path,
uid_t override_uid,
gid_t override_gid,
CopyFlags copy_flags,
+ const Set *denylist,
HardlinkContext *hardlink_context,
const char *display_path,
copy_progress_path_t progress_path,
return r;
}
+ if (set_contains(denylist, &buf)) {
+ log_debug("%s/%s is in the denylist, skipping", from, de->d_name);
+ continue;
+ }
+
if (S_ISDIR(buf.st_mode)) {
/*
* Don't descend into directories on other file systems, if this is requested. We do a simple
}
}
- q = fd_copy_tree_generic(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, depth_left-1, override_uid, override_gid, copy_flags, hardlink_context, child_display_path, progress_path, progress_bytes, userdata);
+ q = fd_copy_tree_generic(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device,
+ depth_left-1, override_uid, override_gid, copy_flags, denylist,
+ hardlink_context, child_display_path, progress_path, progress_bytes,
+ userdata);
if (q == -EINTR) /* Propagate SIGINT/SIGTERM up instantly */
return q;
uid_t override_uid,
gid_t override_gid,
CopyFlags copy_flags,
+ const Set *denylist,
HardlinkContext *hardlink_context,
const char *display_path,
copy_progress_path_t progress_path,
int r;
if (S_ISDIR(st->st_mode))
- return fd_copy_directory(df, from, st, dt, to, original_device, depth_left-1, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_path, progress_bytes, userdata);
+ return fd_copy_directory(df, from, st, dt, to, original_device, depth_left-1, override_uid,
+ override_gid, copy_flags, denylist, hardlink_context, display_path,
+ progress_path, progress_bytes, userdata);
r = fd_copy_leaf(df, from, st, dt, to, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_bytes, userdata);
/* We just tried to copy a leaf node of the tree. If it failed because the node already exists *and* the COPY_REPLACE flag has been provided, we should unlink the node and re-copy. */
uid_t override_uid,
gid_t override_gid,
CopyFlags copy_flags,
+ const Set *denylist,
copy_progress_path_t progress_path,
copy_progress_bytes_t progress_bytes,
void *userdata) {
if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0)
return -errno;
- r = fd_copy_tree_generic(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
+ r = fd_copy_tree_generic(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid,
+ override_gid, copy_flags, denylist, NULL, NULL, progress_path,
+ progress_bytes, userdata);
if (r < 0)
return r;
COPY_DEPTH_MAX,
UID_INVALID, GID_INVALID,
copy_flags,
- NULL, NULL,
+ NULL, NULL, NULL,
progress_path,
progress_bytes,
userdata);
COPY_DEPTH_MAX,
UID_INVALID, GID_INVALID,
copy_flags,
- NULL, NULL,
+ NULL, NULL, NULL,
progress_path,
progress_bytes,
userdata);
int copy_xattr(int fdf, int fdt, CopyFlags copy_flags) {
_cleanup_free_ char *names = NULL;
int ret = 0, r;
- const char *p;
r = flistxattr_malloc(fdf, &names);
if (r < 0)
#include <sys/stat.h>
#include <sys/types.h>
+#include "set.h"
+
typedef enum CopyFlags {
COPY_REFLINK = 1 << 0, /* Try to reflink */
COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */
return copy_file_atomic_full(from, to, mode, chattr_flags, chattr_mask, copy_flags, NULL, NULL);
}
-int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata);
-static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags) {
- return copy_tree_at_full(fdf, from, fdt, to, override_uid, override_gid, copy_flags, NULL, NULL, NULL);
+int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, const Set *denylist, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata);
+static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, const Set *denylist) {
+ return copy_tree_at_full(fdf, from, fdt, to, override_uid, override_gid, copy_flags, denylist, NULL, NULL, NULL);
}
-static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags) {
- return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, NULL, NULL, NULL);
+static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, const Set *denylist) {
+ return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, denylist, NULL, NULL, NULL);
}
int copy_directory_fd_full(int dirfd, const char *to, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata);
#endif
crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
+#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
+int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
+#endif
+#if HAVE_CRYPT_REENCRYPT
+int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
+#endif
+int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
+#if HAVE_CRYPT_SET_DATA_OFFSET
+int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
+#endif
+int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
+int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) {
DLSYM_ARG(crypt_token_max),
#endif
DLSYM_ARG(crypt_token_status),
- DLSYM_ARG(crypt_volume_key_get));
+ DLSYM_ARG(crypt_volume_key_get),
+#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
+ DLSYM_ARG(crypt_reencrypt_init_by_passphrase),
+#endif
+#if HAVE_CRYPT_REENCRYPT
+ DLSYM_ARG(crypt_reencrypt),
+#endif
+ DLSYM_ARG(crypt_metadata_locking),
+#if HAVE_CRYPT_SET_DATA_OFFSET
+ DLSYM_ARG(crypt_set_data_offset),
+#endif
+ DLSYM_ARG(crypt_header_restore),
+ DLSYM_ARG(crypt_volume_key_keyring));
if (r <= 0)
return r;
#endif
extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
+#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
+extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
+#endif
+#if HAVE_CRYPT_REENCRYPT
+extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
+#endif
+extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
+#if HAVE_CRYPT_SET_DATA_OFFSET
+extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
+#endif
+extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
+extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL);
"/proc/self/fd/1\0" "/dev/stdout\0"
"/proc/self/fd/2\0" "/dev/stderr\0";
- const char *j, *k;
int r;
NULSTR_FOREACH_PAIR(j, k, symlinks) {
const char *root,
Image **ret) {
- const char *path;
int r;
assert(class >= 0);
const char *root,
Hashmap *h) {
- const char *path;
int r;
assert(class >= 0);
const char *root,
const char *image) {
- const char *path;
-
assert(image);
NULSTR_FOREACH(path, image_search_path[class]) {
return TAKE_FD(fd);
}
+static int compare_arch(Architecture a, Architecture b) {
+ if (a == b)
+ return 0;
+
+ if (a == native_architecture())
+ return 1;
+
+ if (b == native_architecture())
+ return -1;
+
+#ifdef ARCHITECTURE_SECONDARY
+ if (a == ARCHITECTURE_SECONDARY)
+ return 1;
+
+ if (b == ARCHITECTURE_SECONDARY)
+ return -1;
+#endif
+
+ return 0;
+}
+
static int dissect_image(
DissectedImage *m,
int fd,
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *generic_node = NULL;
sd_id128_t generic_uuid = SD_ID128_NULL;
- const char *pttype = NULL;
+ const char *pttype = NULL, *sptuuid = NULL;
blkid_partlist pl;
int r, generic_nr = -1, n_partitions;
if ((flags & DISSECT_IMAGE_GPT_ONLY) == 0) {
/* Look for file system superblocks, unless we only shall look for GPT partition tables */
blkid_probe_enable_superblocks(b, 1);
- blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_USAGE);
+ blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_USAGE|BLKID_SUBLKS_UUID);
}
blkid_probe_enable_partitions(b, 1);
(void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL);
if (STRPTR_IN_SET(usage, "filesystem", "crypto")) {
_cleanup_free_ char *t = NULL, *n = NULL, *o = NULL;
- const char *fstype = NULL, *options = NULL;
+ const char *fstype = NULL, *options = NULL, *suuid = NULL;
_cleanup_close_ int mount_node_fd = -1;
+ sd_id128_t uuid = SD_ID128_NULL;
if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(devname, /* is_partition = */ false, m->loop);
/* OK, we have found a file system, that's our root partition then. */
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+ (void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
if (fstype) {
t = strdup(fstype);
return -ENOMEM;
}
+ if (suuid) {
+ /* blkid will return FAT's serial number as UUID, hence it is quite possible
+ * that parsing this will fail. We'll ignore the ID, since it's just too
+ * short to be useful as tru identifier. */
+ r = sd_id128_from_string(suuid, &uuid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse file system UUID '%s', ignoring: %m", suuid);
+ }
+
n = strdup(devname);
if (!n)
return -ENOMEM;
m->verity_sig_ready = m->verity_ready &&
verity->root_hash_sig;
+ m->image_uuid = uuid;
+
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
if (options) {
o = strdup(options);
return -EPROTONOSUPPORT;
}
+ (void) blkid_probe_lookup_value(b, "PTUUID", &sptuuid, NULL);
+ if (sptuuid) {
+ r = sd_id128_from_string(sptuuid, &m->image_uuid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse partition table UUID '%s', ignoring: %m", sptuuid);
+ }
+
errno = 0;
pl = blkid_probe_get_partitions(b);
if (!pl)
}
if (is_gpt) {
- PartitionDesignator designator = _PARTITION_DESIGNATOR_INVALID;
- Architecture architecture = _ARCHITECTURE_INVALID;
const char *stype, *sid, *fstype = NULL, *label;
sd_id128_t type_id, id;
+ GptPartitionType type;
bool rw = true, growfs = false;
sid = blkid_partition_get_uuid(pp);
if (sd_id128_from_string(stype, &type_id) < 0)
continue;
+ type = gpt_partition_type_from_uuid(type_id);
+
label = blkid_partition_get_name(pp); /* libblkid returns NULL here if empty */
- if (sd_id128_equal(type_id, SD_GPT_HOME)) {
+ if (type.designator == PARTITION_HOME) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
if (pflags & SD_GPT_FLAG_NO_AUTO)
continue;
- designator = PARTITION_HOME;
rw = !(pflags & SD_GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS);
- } else if (sd_id128_equal(type_id, SD_GPT_SRV)) {
+ } else if (type.designator == PARTITION_SRV) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
if (pflags & SD_GPT_FLAG_NO_AUTO)
continue;
- designator = PARTITION_SRV;
rw = !(pflags & SD_GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS);
- } else if (sd_id128_equal(type_id, SD_GPT_ESP)) {
+ } else if (type.designator == PARTITION_ESP) {
/* Note that we don't check the SD_GPT_FLAG_NO_AUTO flag for the ESP, as it is
* not defined there. We instead check the SD_GPT_FLAG_NO_BLOCK_IO_PROTOCOL, as
if (pflags & SD_GPT_FLAG_NO_BLOCK_IO_PROTOCOL)
continue;
- designator = PARTITION_ESP;
fstype = "vfat";
- } else if (sd_id128_equal(type_id, SD_GPT_XBOOTLDR)) {
+ } else if (type.designator == PARTITION_XBOOTLDR) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
if (pflags & SD_GPT_FLAG_NO_AUTO)
continue;
- designator = PARTITION_XBOOTLDR;
rw = !(pflags & SD_GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS);
- } else if (gpt_partition_type_is_root(type_id)) {
+ } else if (type.designator == PARTITION_ROOT) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id))
continue;
- assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
- designator = PARTITION_ROOT_OF_ARCH(architecture);
rw = !(pflags & SD_GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS);
- } else if (gpt_partition_type_is_root_verity(type_id)) {
+ } else if (type.designator == PARTITION_ROOT_VERITY) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY);
if (!sd_id128_is_null(root_verity_uuid) && !sd_id128_equal(root_verity_uuid, id))
continue;
- assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
- designator = PARTITION_VERITY_OF(PARTITION_ROOT_OF_ARCH(architecture));
fstype = "DM_verity_hash";
rw = false;
- } else if (gpt_partition_type_is_root_verity_sig(type_id)) {
+ } else if (type.designator == PARTITION_ROOT_VERITY_SIG) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY);
if (verity->designator >= 0 && verity->designator != PARTITION_ROOT)
continue;
- assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
- designator = PARTITION_VERITY_SIG_OF(PARTITION_ROOT_OF_ARCH(architecture));
fstype = "verity_hash_signature";
rw = false;
- } else if (gpt_partition_type_is_usr(type_id)) {
+ } else if (type.designator == PARTITION_USR) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
if (!sd_id128_is_null(usr_uuid) && !sd_id128_equal(usr_uuid, id))
continue;
- assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
- designator = PARTITION_USR_OF_ARCH(architecture);
rw = !(pflags & SD_GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS);
- } else if (gpt_partition_type_is_usr_verity(type_id)) {
+ } else if (type.designator == PARTITION_USR_VERITY) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY);
if (!sd_id128_is_null(usr_verity_uuid) && !sd_id128_equal(usr_verity_uuid, id))
continue;
- assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
- designator = PARTITION_VERITY_OF(PARTITION_USR_OF_ARCH(architecture));
fstype = "DM_verity_hash";
rw = false;
- } else if (gpt_partition_type_is_usr_verity_sig(type_id)) {
+ } else if (type.designator == PARTITION_USR_VERITY_SIG) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY);
if (verity->designator >= 0 && verity->designator != PARTITION_USR)
continue;
- assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
- designator = PARTITION_VERITY_SIG_OF(PARTITION_USR_OF_ARCH(architecture));
fstype = "verity_hash_signature";
rw = false;
- } else if (sd_id128_equal(type_id, SD_GPT_SWAP)) {
+ } else if (type.designator == PARTITION_SWAP) {
check_partition_flags(node, pflags, SD_GPT_FLAG_NO_AUTO);
if (pflags & SD_GPT_FLAG_NO_AUTO)
continue;
- designator = PARTITION_SWAP;
-
- } else if (sd_id128_equal(type_id, SD_GPT_LINUX_GENERIC)) {
+ } else if (type.designator == PARTITION_LINUX_GENERIC) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
return -ENOMEM;
}
- } else if (sd_id128_equal(type_id, SD_GPT_TMP)) {
+ } else if (type.designator == PARTITION_TMP) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
if (pflags & SD_GPT_FLAG_NO_AUTO)
continue;
- designator = PARTITION_TMP;
rw = !(pflags & SD_GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS);
- } else if (sd_id128_equal(type_id, SD_GPT_VAR)) {
+ } else if (type.designator == PARTITION_VAR) {
check_partition_flags(node, pflags,
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
}
}
- designator = PARTITION_VAR;
rw = !(pflags & SD_GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS);
}
- if (designator != _PARTITION_DESIGNATOR_INVALID) {
+ if (type.designator != _PARTITION_DESIGNATOR_INVALID) {
_cleanup_free_ char *t = NULL, *o = NULL, *l = NULL;
_cleanup_close_ int mount_node_fd = -1;
const char *options = NULL;
- if (m->partitions[designator].found) {
+ if (m->partitions[type.designator].found) {
/* For most partition types the first one we see wins. Except for the
* rootfs and /usr, where we do a version compare of the label, and
* let the newest version win. This permits a simple A/B versioning
* scheme in OS images. */
- if (!PARTITION_DESIGNATOR_VERSIONED(designator) ||
- strverscmp_improved(m->partitions[designator].label, label) >= 0)
+ if (compare_arch(type.arch, m->partitions[type.designator].architecture) <= 0)
+ continue;
+
+ if (!partition_designator_is_versioned(type.designator) ||
+ strverscmp_improved(m->partitions[type.designator].label, label) >= 0)
continue;
- dissected_partition_done(m->partitions + designator);
+ dissected_partition_done(m->partitions + type.designator);
}
if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) {
return -ENOMEM;
}
- options = mount_options_from_designator(mount_options, designator);
+ options = mount_options_from_designator(mount_options, type.designator);
if (options) {
o = strdup(options);
if (!o)
return -ENOMEM;
}
- m->partitions[designator] = (DissectedPartition) {
+ m->partitions[type.designator] = (DissectedPartition) {
.found = true,
.partno = nr,
.rw = rw,
.growfs = growfs,
- .architecture = architecture,
+ .architecture = type.arch,
.node = TAKE_PTR(node),
.fstype = TAKE_PTR(t),
.label = TAKE_PTR(l),
}
}
- if (m->partitions[PARTITION_ROOT].found) {
- /* If we found the primary arch, then invalidate the secondary and other arch to avoid any
- * ambiguities, since we never want to mount the secondary or other arch in this case. */
- m->partitions[PARTITION_ROOT_SECONDARY].found = false;
- m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found = false;
- m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG].found = false;
- m->partitions[PARTITION_USR_SECONDARY].found = false;
- m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
- m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found = false;
-
- m->partitions[PARTITION_ROOT_OTHER].found = false;
- m->partitions[PARTITION_ROOT_OTHER_VERITY].found = false;
- m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG].found = false;
- m->partitions[PARTITION_USR_OTHER].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
-
- } else if (m->partitions[PARTITION_ROOT_VERITY].found ||
- m->partitions[PARTITION_ROOT_VERITY_SIG].found)
- return -EADDRNOTAVAIL; /* Verity found but no matching rootfs? Something is off, refuse. */
-
- else if (m->partitions[PARTITION_ROOT_SECONDARY].found) {
-
- /* No root partition found but there's one for the secondary architecture? Then upgrade
- * secondary arch to first and invalidate the other arch. */
-
- log_debug("No root partition found of the native architecture, falling back to a root "
- "partition of the secondary architecture.");
-
- m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY];
- zero(m->partitions[PARTITION_ROOT_SECONDARY]);
- m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY];
- zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]);
- m->partitions[PARTITION_ROOT_VERITY_SIG] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG];
- zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG]);
-
- m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY];
- zero(m->partitions[PARTITION_USR_SECONDARY]);
- m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY];
- zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]);
- m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG];
- zero(m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG]);
-
- m->partitions[PARTITION_ROOT_OTHER].found = false;
- m->partitions[PARTITION_ROOT_OTHER_VERITY].found = false;
- m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG].found = false;
- m->partitions[PARTITION_USR_OTHER].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
-
- } else if (m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found ||
- m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG].found)
- return -EADDRNOTAVAIL; /* as above */
-
- else if (m->partitions[PARTITION_ROOT_OTHER].found) {
-
- /* No root or secondary partition found but there's one for another architecture? Then
- * upgrade the other architecture to first. */
-
- log_debug("No root partition found of the native architecture or the secondary architecture, "
- "falling back to a root partition of a non-native architecture (%s).",
- architecture_to_string(m->partitions[PARTITION_ROOT_OTHER].architecture));
-
- m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_OTHER];
- zero(m->partitions[PARTITION_ROOT_OTHER]);
- m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_OTHER_VERITY];
- zero(m->partitions[PARTITION_ROOT_OTHER_VERITY]);
- m->partitions[PARTITION_ROOT_VERITY_SIG] = m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG];
- zero(m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG]);
-
- m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_OTHER];
- zero(m->partitions[PARTITION_USR_OTHER]);
- m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_OTHER_VERITY];
- zero(m->partitions[PARTITION_USR_OTHER_VERITY]);
- m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_OTHER_VERITY_SIG];
- zero(m->partitions[PARTITION_USR_OTHER_VERITY_SIG]);
- }
+ if (!m->partitions[PARTITION_ROOT].found &&
+ (m->partitions[PARTITION_ROOT_VERITY].found ||
+ m->partitions[PARTITION_ROOT_VERITY_SIG].found))
+ return -EADDRNOTAVAIL; /* Verity found but no matching rootfs? Something is off, refuse. */
/* Hmm, we found a signature partition but no Verity data? Something is off. */
if (m->partitions[PARTITION_ROOT_VERITY_SIG].found && !m->partitions[PARTITION_ROOT_VERITY].found)
return -EADDRNOTAVAIL;
- if (m->partitions[PARTITION_USR].found) {
- /* Invalidate secondary and other arch /usr/ if we found the primary arch */
- m->partitions[PARTITION_USR_SECONDARY].found = false;
- m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
- m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found = false;
-
- m->partitions[PARTITION_USR_OTHER].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
-
- } else if (m->partitions[PARTITION_USR_VERITY].found ||
- m->partitions[PARTITION_USR_VERITY_SIG].found)
- return -EADDRNOTAVAIL; /* as above */
-
- else if (m->partitions[PARTITION_USR_SECONDARY].found) {
-
- log_debug("No usr partition found of the native architecture, falling back to a usr "
- "partition of the secondary architecture.");
-
- /* Upgrade secondary arch to primary */
- m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY];
- zero(m->partitions[PARTITION_USR_SECONDARY]);
- m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY];
- zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]);
- m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG];
- zero(m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG]);
-
- m->partitions[PARTITION_USR_OTHER].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
- m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
-
- } else if (m->partitions[PARTITION_USR_SECONDARY_VERITY].found ||
- m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found)
- return -EADDRNOTAVAIL; /* as above */
-
- else if (m->partitions[PARTITION_USR_OTHER].found) {
-
- log_debug("No usr partition found of the native architecture or the secondary architecture, "
- "falling back to a usr partition of a non-native architecture (%s).",
- architecture_to_string(m->partitions[PARTITION_ROOT_OTHER].architecture));
-
- /* Upgrade other arch to primary */
- m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_OTHER];
- zero(m->partitions[PARTITION_USR_OTHER]);
- m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_OTHER_VERITY];
- zero(m->partitions[PARTITION_USR_OTHER_VERITY]);
- m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_OTHER_VERITY_SIG];
- zero(m->partitions[PARTITION_USR_OTHER_VERITY_SIG]);
- }
+ if (!m->partitions[PARTITION_USR].found &&
+ (m->partitions[PARTITION_USR_VERITY].found ||
+ m->partitions[PARTITION_USR_VERITY_SIG].found))
+ return -EADDRNOTAVAIL; /* as above */
- /* Hmm, we found a signature partition but no Verity data? Something is off. */
+ /* as above */
if (m->partitions[PARTITION_USR_VERITY_SIG].found && !m->partitions[PARTITION_USR_VERITY].found)
return -EADDRNOTAVAIL;
if (r < 0)
return r;
- k = PARTITION_VERITY_OF(i);
+ k = partition_verity_of(i);
if (k >= 0) {
r = verity_partition(i, p, m->partitions + k, verity, flags | DISSECT_IMAGE_VERITY_SHARE, d);
if (r < 0)
if (r == 0)
return 0;
- d = PARTITION_VERITY_SIG_OF(verity->designator < 0 ? PARTITION_ROOT : verity->designator);
+ d = partition_verity_sig_of(verity->designator < 0 ? PARTITION_ROOT : verity->designator);
assert(d >= 0);
p = m->partitions + d;
for (unsigned k = 0; k < _META_MAX; k++) {
_cleanup_close_ int fd = -ENOENT;
- const char *p;
if (!paths[k])
continue;
if (image->single_file_system)
return partition_designator == PARTITION_ROOT && image->has_verity;
- return PARTITION_VERITY_OF(partition_designator) >= 0;
+ return partition_verity_of(partition_designator) >= 0;
}
bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator partition_designator) {
if (image->single_file_system)
return partition_designator == PARTITION_ROOT;
- k = PARTITION_VERITY_OF(partition_designator);
+ k = partition_verity_of(partition_designator);
return k >= 0 && image->partitions[k].found;
}
if (image->single_file_system)
return partition_designator == PARTITION_ROOT;
- k = PARTITION_VERITY_SIG_OF(partition_designator);
+ k = partition_verity_sig_of(partition_designator);
return k >= 0 && image->partitions[k].found;
}
return 0;
}
-static const char *const partition_designator_table[] = {
- [PARTITION_ROOT] = "root",
- [PARTITION_ROOT_SECONDARY] = "root-secondary",
- [PARTITION_ROOT_OTHER] = "root-other",
- [PARTITION_USR] = "usr",
- [PARTITION_USR_SECONDARY] = "usr-secondary",
- [PARTITION_USR_OTHER] = "usr-other",
- [PARTITION_HOME] = "home",
- [PARTITION_SRV] = "srv",
- [PARTITION_ESP] = "esp",
- [PARTITION_XBOOTLDR] = "xbootldr",
- [PARTITION_SWAP] = "swap",
- [PARTITION_ROOT_VERITY] = "root-verity",
- [PARTITION_ROOT_SECONDARY_VERITY] = "root-secondary-verity",
- [PARTITION_ROOT_OTHER_VERITY] = "root-other-verity",
- [PARTITION_USR_VERITY] = "usr-verity",
- [PARTITION_USR_SECONDARY_VERITY] = "usr-secondary-verity",
- [PARTITION_USR_OTHER_VERITY] = "usr-other-verity",
- [PARTITION_ROOT_VERITY_SIG] = "root-verity-sig",
- [PARTITION_ROOT_SECONDARY_VERITY_SIG] = "root-secondary-verity-sig",
- [PARTITION_ROOT_OTHER_VERITY_SIG] = "root-other-verity-sig",
- [PARTITION_USR_VERITY_SIG] = "usr-verity-sig",
- [PARTITION_USR_SECONDARY_VERITY_SIG] = "usr-secondary-verity-sig",
- [PARTITION_USR_OTHER_VERITY_SIG] = "usr-other-verity-sig",
- [PARTITION_TMP] = "tmp",
- [PARTITION_VAR] = "var",
-};
-
static bool mount_options_relax_extension_release_checks(const MountOptions *options) {
if (!options)
return false;
return 0;
}
-
-DEFINE_STRING_TABLE_LOOKUP(partition_designator, PartitionDesignator);
#include "sd-id128.h"
#include "architecture.h"
+#include "gpt.h"
#include "list.h"
#include "loop-util.h"
#include "macro.h"
.architecture = _ARCHITECTURE_INVALID, \
.mount_node_fd = -1, \
})
-
-typedef enum PartitionDesignator {
- PARTITION_ROOT,
- PARTITION_ROOT_SECONDARY, /* Secondary architecture */
- PARTITION_ROOT_OTHER,
- PARTITION_USR,
- PARTITION_USR_SECONDARY,
- PARTITION_USR_OTHER,
- PARTITION_HOME,
- PARTITION_SRV,
- PARTITION_ESP,
- PARTITION_XBOOTLDR,
- PARTITION_SWAP,
- PARTITION_ROOT_VERITY, /* verity data for the PARTITION_ROOT partition */
- PARTITION_ROOT_SECONDARY_VERITY, /* verity data for the PARTITION_ROOT_SECONDARY partition */
- PARTITION_ROOT_OTHER_VERITY,
- PARTITION_USR_VERITY,
- PARTITION_USR_SECONDARY_VERITY,
- PARTITION_USR_OTHER_VERITY,
- PARTITION_ROOT_VERITY_SIG, /* PKCS#7 signature for root hash for the PARTITION_ROOT partition */
- PARTITION_ROOT_SECONDARY_VERITY_SIG, /* ditto for the PARTITION_ROOT_SECONDARY partition */
- PARTITION_ROOT_OTHER_VERITY_SIG,
- PARTITION_USR_VERITY_SIG,
- PARTITION_USR_SECONDARY_VERITY_SIG,
- PARTITION_USR_OTHER_VERITY_SIG,
- PARTITION_TMP,
- PARTITION_VAR,
- _PARTITION_DESIGNATOR_MAX,
- _PARTITION_DESIGNATOR_INVALID = -EINVAL,
-} PartitionDesignator;
-
-static inline bool PARTITION_DESIGNATOR_VERSIONED(PartitionDesignator d) {
- /* Returns true for all designators where we want to support a concept of "versioning", i.e. which
- * likely contain software binaries (or hashes thereof) that make sense to be versioned as a
- * whole. We use this check to automatically pick the newest version of these partitions, by version
- * comparing the partition labels. */
-
- return IN_SET(d,
- PARTITION_ROOT,
- PARTITION_ROOT_SECONDARY,
- PARTITION_ROOT_OTHER,
- PARTITION_USR,
- PARTITION_USR_SECONDARY,
- PARTITION_USR_OTHER,
- PARTITION_ROOT_VERITY,
- PARTITION_ROOT_SECONDARY_VERITY,
- PARTITION_ROOT_OTHER_VERITY,
- PARTITION_USR_VERITY,
- PARTITION_USR_SECONDARY_VERITY,
- PARTITION_USR_OTHER_VERITY,
- PARTITION_ROOT_VERITY_SIG,
- PARTITION_ROOT_SECONDARY_VERITY_SIG,
- PARTITION_ROOT_OTHER_VERITY_SIG,
- PARTITION_USR_VERITY_SIG,
- PARTITION_USR_SECONDARY_VERITY_SIG,
- PARTITION_USR_OTHER_VERITY_SIG);
-}
-
-static inline PartitionDesignator PARTITION_VERITY_OF(PartitionDesignator p) {
- switch (p) {
-
- case PARTITION_ROOT:
- return PARTITION_ROOT_VERITY;
-
- case PARTITION_ROOT_SECONDARY:
- return PARTITION_ROOT_SECONDARY_VERITY;
-
- case PARTITION_ROOT_OTHER:
- return PARTITION_ROOT_OTHER_VERITY;
-
- case PARTITION_USR:
- return PARTITION_USR_VERITY;
-
- case PARTITION_USR_SECONDARY:
- return PARTITION_USR_SECONDARY_VERITY;
-
- case PARTITION_USR_OTHER:
- return PARTITION_USR_OTHER_VERITY;
-
- default:
- return _PARTITION_DESIGNATOR_INVALID;
- }
-}
-
-static inline PartitionDesignator PARTITION_VERITY_SIG_OF(PartitionDesignator p) {
- switch (p) {
-
- case PARTITION_ROOT:
- return PARTITION_ROOT_VERITY_SIG;
-
- case PARTITION_ROOT_SECONDARY:
- return PARTITION_ROOT_SECONDARY_VERITY_SIG;
-
- case PARTITION_ROOT_OTHER:
- return PARTITION_ROOT_OTHER_VERITY_SIG;
-
- case PARTITION_USR:
- return PARTITION_USR_VERITY_SIG;
-
- case PARTITION_USR_SECONDARY:
- return PARTITION_USR_SECONDARY_VERITY_SIG;
-
- case PARTITION_USR_OTHER:
- return PARTITION_USR_OTHER_VERITY_SIG;
-
- default:
- return _PARTITION_DESIGNATOR_INVALID;
- }
-}
-
-static inline PartitionDesignator PARTITION_ROOT_OF_ARCH(Architecture arch) {
- switch (arch) {
-
- case native_architecture():
- return PARTITION_ROOT;
-
-#ifdef ARCHITECTURE_SECONDARY
- case ARCHITECTURE_SECONDARY:
- return PARTITION_ROOT_SECONDARY;
-#endif
-
- default:
- return PARTITION_ROOT_OTHER;
- }
-}
-
-static inline PartitionDesignator PARTITION_USR_OF_ARCH(Architecture arch) {
- switch (arch) {
-
- case native_architecture():
- return PARTITION_USR;
-
-#ifdef ARCHITECTURE_SECONDARY
- case ARCHITECTURE_SECONDARY:
- return PARTITION_USR_SECONDARY;
-#endif
-
- default:
- return PARTITION_USR_OTHER;
- }
-}
+#define TAKE_PARTITION(p) \
+ ({ \
+ DissectedPartition *_pp = &(p), _p = *_pp; \
+ *_pp = DISSECTED_PARTITION_NULL; \
+ _p; \
+ })
typedef enum DissectImageFlags {
DISSECT_IMAGE_DEVICE_READ_ONLY = 1 << 0, /* Make device read-only */
/* Meta information extracted from /etc/os-release and similar */
char *image_name;
+ sd_id128_t image_uuid;
char *hostname;
sd_id128_t machine_id;
char **machine_info;
int dissected_image_relinquish(DissectedImage *m);
-const char* partition_designator_to_string(PartitionDesignator d) _const_;
-PartitionDesignator partition_designator_from_string(const char *name) _pure_;
-
int verity_settings_load(VeritySettings *verity, const char *image, const char *root_hash_path, const char *root_hash_sig_path);
void verity_settings_done(VeritySettings *verity);
r = sd_device_get_devname(d, &node);
if (r < 0)
- return log_error_errno(r, "Failed to get device node: %m");
+ return log_device_error_errno(d, r, "Failed to get device node: %m");
r = sd_device_get_property_value(d, "ID_FS_TYPE", &v);
if (r < 0)
- return log_error_errno(r, "Failed to get device property: %m");
+ return log_device_error_errno(d, r, "Failed to get device property: %m");
if (!streq(v, "vfat"))
- return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
- SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
- "File system \"%s\" is not FAT.", node );
+ return log_device_full_errno(d,
+ searching ? LOG_DEBUG : LOG_ERR,
+ SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+ "File system \"%s\" is not FAT.", node );
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
if (r < 0)
- return log_error_errno(r, "Failed to get device property: %m");
+ return log_device_full_errno(d,
+ searching && r == -ENOENT ? LOG_DEBUG : LOG_ERR,
+ searching && r == -ENOENT ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : r,
+ "Failed to get device property: %m");
if (!streq(v, "gpt"))
- return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
- SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
- "File system \"%s\" is not on a GPT partition table.", node);
+ return log_device_full_errno(d,
+ searching ? LOG_DEBUG : LOG_ERR,
+ SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+ "File system \"%s\" is not on a GPT partition table.", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
if (r < 0)
- return log_error_errno(r, "Failed to get device property: %m");
+ return log_device_error_errno(d, r, "Failed to get device property: %m");
if (sd_id128_string_equal(v, SD_GPT_ESP) <= 0)
- return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
- SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
- "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
+ return log_device_full_errno(d,
+ searching ? LOG_DEBUG : LOG_ERR,
+ SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
+ "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
if (r < 0)
- return log_error_errno(r, "Failed to get device property: %m");
+ return log_device_error_errno(d, r, "Failed to get device property: %m");
r = sd_id128_from_string(v, &uuid);
if (r < 0)
- return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
+ return log_device_error_errno(d, r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
r = sd_device_get_property_value(d, "ID_PART_ENTRY_NUMBER", &v);
if (r < 0)
- return log_error_errno(r, "Failed to get device property: %m");
+ return log_device_error_errno(d, r, "Failed to get device property: %m");
r = safe_atou32(v, &part);
if (r < 0)
- return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
+ return log_device_error_errno(d, r, "Failed to parse PART_ENTRY_NUMBER field.");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_OFFSET", &v);
if (r < 0)
- return log_error_errno(r, "Failed to get device property: %m");
+ return log_device_error_errno(d, r, "Failed to get device property: %m");
r = safe_atou64(v, &pstart);
if (r < 0)
- return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
+ return log_device_error_errno(d, r, "Failed to parse PART_ENTRY_OFFSET field.");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SIZE", &v);
if (r < 0)
- return log_error_errno(r, "Failed to get device property: %m");
+ return log_device_error_errno(d, r, "Failed to get device property: %m");
r = safe_atou64(v, &psize);
if (r < 0)
- return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
+ return log_device_error_errno(d, r, "Failed to parse PART_ENTRY_SIZE field.");
if (ret_part)
*ret_part = part;
else if (r != 0)
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "%s: Failed to probe file system: %m", node);
- errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &type, NULL);
if (r != 0)
- return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "%s: Failed to probe PART_ENTRY_SCHEME: %m", node);
+ return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
+ searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(EIO),
+ "%s: Failed to probe PART_ENTRY_SCHEME: %m", node);
if (streq(type, "gpt")) {
errno = 0;
r = sd_device_get_devname(d, &node);
if (r < 0)
- return log_error_errno(r, "Failed to get device node: %m");
+ return log_device_error_errno(d, r, "Failed to get device node: %m");
r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &type);
if (r < 0)
- return log_device_error_errno(d, r, "Failed to query ID_PART_ENTRY_SCHEME: %m");
+ return log_device_full_errno(d,
+ searching && r == -ENOENT ? LOG_DEBUG : LOG_ERR,
+ searching && r == -ENOENT ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : r,
+ "Failed to query ID_PART_ENTRY_SCHEME: %m");
if (streq(type, "gpt")) {
size_t n_cells;
bool header; /* Whether to show the header row? */
+ bool vertical; /* Whether to field names are on the left rather than the first line */
+
TableErsatz ersatz; /* What to show when we have an empty cell or an invalid value that cannot be rendered. */
size_t width; /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
TableCell *cell;
- r = table_add_cell(t, &cell, TABLE_STRING, h);
- if (r < 0) {
- va_end(ap);
- return NULL;
- }
-
- /* Make the table header uppercase */
- r = table_set_uppercase(t, cell, true);
+ r = table_add_cell(t, &cell, TABLE_HEADER, h);
if (r < 0) {
va_end(ap);
return NULL;
return TAKE_PTR(t);
}
+Table *table_new_vertical(void) {
+ _cleanup_(table_unrefp) Table *t = NULL;
+ TableCell *cell;
+
+ t = table_new_raw(2);
+ if (!t)
+ return NULL;
+
+ t->vertical = true;
+ t->header = false;
+
+ if (table_add_cell(t, &cell, TABLE_HEADER, "key") < 0)
+ return NULL;
+
+ if (table_set_align_percent(t, cell, 100) < 0)
+ return NULL;
+
+ if (table_add_cell(t, &cell, TABLE_HEADER, "value") < 0)
+ return NULL;
+
+ if (table_set_align_percent(t, cell, 0) < 0)
+ return NULL;
+
+ return TAKE_PTR(t);
+}
+
static TableData *table_data_free(TableData *d) {
assert(d);
case TABLE_STRING:
case TABLE_PATH:
+ case TABLE_FIELD:
+ case TABLE_HEADER:
return strlen(data) + 1;
case TABLE_STRV:
size_t maximum_width,
unsigned weight,
unsigned align_percent,
- unsigned ellipsize_percent) {
+ unsigned ellipsize_percent,
+ bool uppercase) {
size_t k, l;
assert(d);
if (d->ellipsize_percent != ellipsize_percent)
return false;
- /* If a color/url/uppercase flag is set, refuse to merge */
+ if (d->uppercase != uppercase)
+ return false;
+
+ /* If a color/url is set, refuse to merge */
if (d->color || d->rgap_color)
return false;
if (d->url)
return false;
- if (d->uppercase)
- return false;
k = table_data_size(type, data);
l = table_data_size(d->type, d->data);
size_t maximum_width,
unsigned weight,
unsigned align_percent,
- unsigned ellipsize_percent) {
+ unsigned ellipsize_percent,
+ bool uppercase) {
_cleanup_free_ TableData *d = NULL;
size_t data_size;
d->weight = weight;
d->align_percent = align_percent;
d->ellipsize_percent = ellipsize_percent;
+ d->uppercase = uppercase;
if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
d->strv = strv_copy(data);
unsigned ellipsize_percent) {
_cleanup_(table_data_unrefp) TableData *d = NULL;
+ bool uppercase;
TableData *p;
assert(t);
assert(align_percent <= 100);
assert(ellipsize_percent <= 100);
+ uppercase = type == TABLE_HEADER;
+
/* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
* formatting. Let's see if we can reuse the cell data and ref it once more. */
- if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
+ if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
d = table_data_ref(p);
else {
- d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
+ d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
if (!d)
return -ENOMEM;
}
return 0;
}
-int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
+int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt, const char *format, ...) {
_cleanup_free_ char *buffer = NULL;
va_list ap;
int r;
+ assert(t);
+ assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_FIELD, TABLE_HEADER));
+
va_start(ap, format);
r = vasprintf(&buffer, format, ap);
va_end(ap);
if (r < 0)
return -ENOMEM;
- return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
+ return table_add_cell(t, ret_cell, dt, buffer);
}
int table_fill_empty(Table *t, size_t until_column) {
od->maximum_width,
od->weight,
od->align_percent,
- od->ellipsize_percent);
+ od->ellipsize_percent,
+ od->uppercase);
if (!nd)
return -ENOMEM;
nd->color = od->color;
nd->rgap_color = od->rgap_color;
nd->url = TAKE_PTR(curl);
- nd->uppercase = od->uppercase;
table_data_unref(od);
t->data[i] = nd;
od->maximum_width,
od->weight,
od->align_percent,
- od->ellipsize_percent);
+ od->ellipsize_percent,
+ od->uppercase);
if (!nd)
return -ENOMEM;
nd->color = od->color;
nd->rgap_color = od->rgap_color;
nd->url = TAKE_PTR(curl);
- nd->uppercase = od->uppercase;
table_data_unref(od);
t->data[i] = nd;
case TABLE_STRING:
case TABLE_PATH:
+ case TABLE_FIELD:
+ case TABLE_HEADER:
data = va_arg(ap, const char *);
break;
switch (a->type) {
case TABLE_STRING:
+ case TABLE_FIELD:
+ case TABLE_HEADER:
return strcmp(a->string, b->string);
case TABLE_PATH:
case TABLE_STRING:
case TABLE_PATH:
+ case TABLE_FIELD:
+ case TABLE_HEADER:
if (d->uppercase && !avoid_uppercasing) {
- d->formatted = new(char, strlen(d->string) + 1);
+ d->formatted = new(char, strlen(d->string) + (d->type == TABLE_FIELD) + 1);
if (!d->formatted)
return NULL;
char *q = d->formatted;
- for (char *p = d->string; *p; p++, q++)
- *q = (char) toupper((unsigned char) *p);
+ for (char *p = d->string; *p; p++)
+ *(q++) = (char) toupper((unsigned char) *p);
+
+ if (d->type == TABLE_FIELD)
+ *(q++) = ':';
+
*q = 0;
+ return d->formatted;
+ } else if (d->type == TABLE_FIELD) {
+ d->formatted = strjoin(d->string, ":");
+ if (!d->formatted)
+ return NULL;
return d->formatted;
}
if (table_data_isempty(d))
return ansi_grey();
+ if (d->type == TABLE_FIELD)
+ return ansi_bright_blue();
+ if (d->type == TABLE_HEADER)
+ return ansi_underline();
+
return NULL;
}
if (d->rgap_color)
return d->rgap_color;
+ if (d->type == TABLE_HEADER)
+ return ansi_underline();
+
return NULL;
}
/* Drop trailing white spaces of last column when no cosmetics is set. */
if (j == display_columns - 1 &&
- (!colors_enabled() || (!table_data_color(d) && row != t->data)) &&
+ (!colors_enabled() || !table_data_color(d)) &&
(!urlify_enabled() || !d->url))
delete_trailing_chars(aligned, NULL);
field = buffer;
}
- if (colors_enabled()) {
- if (gap_color)
- fputs(gap_color, f);
- else if (row == t->data) /* underline header line fully, including the column separator */
- fputs(ansi_underline(), f);
- }
+ if (colors_enabled() && gap_color)
+ fputs(gap_color, f);
if (j > 0)
fputc(' ', f); /* column separator left of cell */
color = table_data_color(d);
/* Undo gap color */
- if (gap_color || (color && row == t->data))
+ if (gap_color)
fputs(ANSI_NORMAL, f);
if (color)
fputs(color, f);
- else if (gap_color && row == t->data) /* underline header line cell */
- fputs(ansi_underline(), f);
}
fputs(field, f);
- if (colors_enabled() && (color || row == t->data))
+ if (colors_enabled() && color)
fputs(ANSI_NORMAL, f);
gap_color = table_data_rgap_color(d);
case TABLE_STRING:
case TABLE_PATH:
+ case TABLE_FIELD:
+ case TABLE_HEADER:
return json_variant_new_string(ret, d->string);
case TABLE_STRV:
return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
case TABLE_ID128:
- return json_variant_new_string(ret, SD_ID128_TO_STRING(d->id128));
+ return json_variant_new_id128(ret, d->id128);
case TABLE_UUID:
- return json_variant_new_string(ret, SD_ID128_TO_UUID_STRING(d->id128));
+ return json_variant_new_uuid(ret, d->id128);
case TABLE_UID:
if (!uid_is_valid(d->uid))
return c;
}
-static const char *table_get_json_field_name(Table *t, size_t column) {
+static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
+ _cleanup_free_ char *mangled = NULL;
+ const char *n;
+
assert(t);
+ assert(d);
+ assert(ret);
- return column < t->n_json_fields ? t->json_fields[column] : NULL;
+ if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
+ n = d->string;
+ else {
+ n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL);
+ if (!n)
+ return -ENOMEM;
+ }
+
+ mangled = string_to_json_field_name(n);
+ if (!mangled)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(mangled);
+ return 0;
}
-int table_to_json(Table *t, JsonVariant **ret) {
+static const char *table_get_json_field_name(Table *t, size_t idx) {
+ assert(t);
+
+ return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
+}
+
+static int table_to_json_regular(Table *t, JsonVariant **ret) {
JsonVariant **rows = NULL, **elements = NULL;
_cleanup_free_ size_t *sorted = NULL;
size_t n_rows, display_columns;
int r;
assert(t);
+ assert(!t->vertical);
/* Ensure we have no incomplete rows */
+ assert(t->n_columns > 0);
assert(t->n_cells % t->n_columns == 0);
n_rows = t->n_cells / t->n_columns;
/* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
n = table_get_json_field_name(t, c);
if (!n) {
- const char *formatted;
- TableData *d;
-
- assert_se(d = t->data[c]);
-
- /* Field names must be strings, hence format whatever we got here as a string first */
- formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
- if (!formatted) {
- r = -ENOMEM;
+ r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
+ if (r < 0)
goto finish;
- }
- /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
- mangled = string_to_json_field_name(formatted);
- if (!mangled) {
- r = -ENOMEM;
- goto finish;
- }
n = mangled;
}
return r;
}
+static int table_to_json_vertical(Table *t, JsonVariant **ret) {
+ JsonVariant **elements = NULL;
+ size_t n_elements = 0;
+ int r;
+
+ assert(t);
+ assert(t->vertical);
+
+ if (t->n_columns != 2)
+ return -EINVAL;
+
+ /* Ensure we have no incomplete rows */
+ assert(t->n_cells % t->n_columns == 0);
+
+ elements = new0(JsonVariant *, t->n_cells);
+ if (!elements) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ for (size_t i = t->n_columns; i < t->n_cells; i++) {
+
+ if (i % t->n_columns == 0) {
+ _cleanup_free_ char *mangled = NULL;
+ const char *n;
+
+ n = table_get_json_field_name(t, i / t->n_columns - 1);
+ if (!n) {
+ r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
+ if (r < 0)
+ goto finish;
+
+ n = mangled;
+ }
+
+ r = json_variant_new_string(elements + n_elements, n);
+ } else
+ r = table_data_to_json(t->data[i], elements + n_elements);
+ if (r < 0)
+ goto finish;
+
+ n_elements++;
+ }
+
+ r = json_variant_new_object(ret, elements, n_elements);
+
+finish:
+ if (elements) {
+ json_variant_unref_many(elements, n_elements);
+ free(elements);
+ }
+
+ return r;
+}
+
+int table_to_json(Table *t, JsonVariant **ret) {
+ assert(t);
+
+ if (t->vertical)
+ return table_to_json_vertical(t, ret);
+
+ return table_to_json_regular(t, ret);
+}
+
int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
int r;
return 0;
}
-int table_set_json_field_name(Table *t, size_t column, const char *name) {
+int table_set_json_field_name(Table *t, size_t idx, const char *name) {
int r;
assert(t);
if (name) {
size_t m;
- m = MAX(column + 1, t->n_json_fields);
+ m = MAX(idx + 1, t->n_json_fields);
if (!GREEDY_REALLOC0(t->json_fields, m))
return -ENOMEM;
- r = free_and_strdup(t->json_fields + column, name);
+ r = free_and_strdup(t->json_fields + idx, name);
if (r < 0)
return r;
t->n_json_fields = m;
return r;
} else {
- if (column >= t->n_json_fields)
+ if (idx >= t->n_json_fields)
return 0;
- t->json_fields[column] = mfree(t->json_fields[column]);
+ t->json_fields[idx] = mfree(t->json_fields[idx]);
return 1;
}
}
typedef enum TableDataType {
TABLE_EMPTY,
TABLE_STRING,
+ TABLE_HEADER, /* in regular mode: the cells in the first row, that carry the column names */
+ TABLE_FIELD, /* in vertical mode: the cells in the first column, that carry the field names */
TABLE_STRV,
TABLE_STRV_WRAPPED,
TABLE_PATH,
Table *table_new_internal(const char *first_header, ...) _sentinel_;
#define table_new(...) table_new_internal(__VA_ARGS__, NULL)
Table *table_new_raw(size_t n_columns);
+Table *table_new_vertical(void);
Table *table_unref(Table *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(Table*, table_unref);
static inline int table_add_cell(Table *t, TableCell **ret_cell, TableDataType type, const void *data) {
return table_add_cell_full(t, ret_cell, type, data, SIZE_MAX, SIZE_MAX, UINT_MAX, UINT_MAX, UINT_MAX);
}
-int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) _printf_(3, 4);
+int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType type, const char *format, ...) _printf_(4, 5);
+#define table_add_cell_stringf(t, ret_cell, format, ...) table_add_cell_stringf_full(t, ret_cell, TABLE_STRING, format, __VA_ARGS__)
int table_fill_empty(Table *t, size_t until_column);
int table_print_with_pager(Table *t, JsonFormatFlags json_format_flags, PagerFlags pager_flags, bool show_header);
-int table_set_json_field_name(Table *t, size_t column, const char *name);
+int table_set_json_field_name(Table *t, size_t idx, const char *name);
#define table_log_add_error(r) \
log_error_errno(r, "Failed to add cells to table: %m")
char ***ret_values,
char **ret_filtered) {
- const char *name, *namefound = NULL, *x;
+ const char *namefound = NULL, *x;
_cleanup_strv_free_ char **stor = NULL, **values = NULL;
_cleanup_free_ char *value = NULL, **filtered = NULL;
int r;
if (!x)
continue;
/* Match name, but when ret_values, only when followed by assignment. */
- if (*x == '=' || (!ret_values && *x == '\0'))
+ if (*x == '=' || (!ret_values && *x == '\0')) {
+ /* Keep the last occurrence found */
+ namefound = name;
goto found;
+ }
}
*t = *s;
t++;
continue;
found:
- /* Keep the last occurrence found */
- namefound = name;
-
if (ret_value || ret_values) {
assert(IN_SET(*x, '=', '\0'));
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "gpt.h"
+#include "string-table.h"
#include "string-util.h"
#include "utf8.h"
#pragma message "Please define GPT partition types for your architecture."
#endif
+bool partition_designator_is_versioned(PartitionDesignator d) {
+ /* Returns true for all designators where we want to support a concept of "versioning", i.e. which
+ * likely contain software binaries (or hashes thereof) that make sense to be versioned as a
+ * whole. We use this check to automatically pick the newest version of these partitions, by version
+ * comparing the partition labels. */
+
+ return IN_SET(d,
+ PARTITION_ROOT,
+ PARTITION_USR,
+ PARTITION_ROOT_VERITY,
+ PARTITION_USR_VERITY,
+ PARTITION_ROOT_VERITY_SIG,
+ PARTITION_USR_VERITY_SIG);
+}
+
+PartitionDesignator partition_verity_of(PartitionDesignator p) {
+ switch (p) {
+
+ case PARTITION_ROOT:
+ return PARTITION_ROOT_VERITY;
+
+ case PARTITION_USR:
+ return PARTITION_USR_VERITY;
+
+ default:
+ return _PARTITION_DESIGNATOR_INVALID;
+ }
+}
+
+PartitionDesignator partition_verity_sig_of(PartitionDesignator p) {
+ switch (p) {
+
+ case PARTITION_ROOT:
+ return PARTITION_ROOT_VERITY_SIG;
+
+ case PARTITION_USR:
+ return PARTITION_USR_VERITY_SIG;
+
+ default:
+ return _PARTITION_DESIGNATOR_INVALID;
+ }
+}
+
+
+static const char *const partition_designator_table[] = {
+ [PARTITION_ROOT] = "root",
+ [PARTITION_USR] = "usr",
+ [PARTITION_HOME] = "home",
+ [PARTITION_SRV] = "srv",
+ [PARTITION_ESP] = "esp",
+ [PARTITION_XBOOTLDR] = "xbootldr",
+ [PARTITION_SWAP] = "swap",
+ [PARTITION_ROOT_VERITY] = "root-verity",
+ [PARTITION_USR_VERITY] = "usr-verity",
+ [PARTITION_ROOT_VERITY_SIG] = "root-verity-sig",
+ [PARTITION_USR_VERITY_SIG] = "usr-verity-sig",
+ [PARTITION_TMP] = "tmp",
+ [PARTITION_VAR] = "var",
+ [PARTITION_USER_HOME] = "user-home",
+ [PARTITION_LINUX_GENERIC] = "linux-generic",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(partition_designator, PartitionDesignator);
+
+static const char *const partition_mountpoint_table[] = {
+ [PARTITION_ROOT] = "/\0",
+ [PARTITION_USR] = "/usr\0",
+ [PARTITION_HOME] = "/home\0",
+ [PARTITION_SRV] = "/srv\0",
+ [PARTITION_ESP] = "/efi\0/boot\0",
+ [PARTITION_XBOOTLDR] = "/boot\0",
+ [PARTITION_TMP] = "/var/tmp\0",
+ [PARTITION_VAR] = "/var\0",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(partition_mountpoint, PartitionDesignator);
+
#define _GPT_ARCH_SEXTET(arch, name) \
- { SD_GPT_ROOT_##arch, "root-" name, ARCHITECTURE_##arch, .is_root = true }, \
- { SD_GPT_ROOT_##arch##_VERITY, "root-" name "-verity", ARCHITECTURE_##arch, .is_root_verity = true }, \
- { SD_GPT_ROOT_##arch##_VERITY_SIG, "root-" name "-verity-sig", ARCHITECTURE_##arch, .is_root_verity_sig = true }, \
- { SD_GPT_USR_##arch, "usr-" name, ARCHITECTURE_##arch, .is_usr = true }, \
- { SD_GPT_USR_##arch##_VERITY, "usr-" name "-verity", ARCHITECTURE_##arch, .is_usr_verity = true }, \
- { SD_GPT_USR_##arch##_VERITY_SIG, "usr-" name "-verity-sig", ARCHITECTURE_##arch, .is_usr_verity_sig = true }
+ { SD_GPT_ROOT_##arch, "root-" name, ARCHITECTURE_##arch, .designator = PARTITION_ROOT }, \
+ { SD_GPT_ROOT_##arch##_VERITY, "root-" name "-verity", ARCHITECTURE_##arch, .designator = PARTITION_ROOT_VERITY }, \
+ { SD_GPT_ROOT_##arch##_VERITY_SIG, "root-" name "-verity-sig", ARCHITECTURE_##arch, .designator = PARTITION_ROOT_VERITY_SIG }, \
+ { SD_GPT_USR_##arch, "usr-" name, ARCHITECTURE_##arch, .designator = PARTITION_USR }, \
+ { SD_GPT_USR_##arch##_VERITY, "usr-" name "-verity", ARCHITECTURE_##arch, .designator = PARTITION_USR_VERITY }, \
+ { SD_GPT_USR_##arch##_VERITY_SIG, "usr-" name "-verity-sig", ARCHITECTURE_##arch, .designator = PARTITION_USR_VERITY_SIG }
const GptPartitionType gpt_partition_type_table[] = {
_GPT_ARCH_SEXTET(ALPHA, "alpha"),
_GPT_ARCH_SEXTET(X86, "x86"),
_GPT_ARCH_SEXTET(X86_64, "x86-64"),
#ifdef SD_GPT_ROOT_NATIVE
- { SD_GPT_ROOT_NATIVE, "root", native_architecture(), .is_root = true },
- { SD_GPT_ROOT_NATIVE_VERITY, "root-verity", native_architecture(), .is_root_verity = true },
- { SD_GPT_ROOT_NATIVE_VERITY_SIG, "root-verity-sig", native_architecture(), .is_root_verity_sig = true },
- { SD_GPT_USR_NATIVE, "usr", native_architecture(), .is_usr = true },
- { SD_GPT_USR_NATIVE_VERITY, "usr-verity", native_architecture(), .is_usr_verity = true },
- { SD_GPT_USR_NATIVE_VERITY_SIG, "usr-verity-sig", native_architecture(), .is_usr_verity_sig = true },
+ { SD_GPT_ROOT_NATIVE, "root", native_architecture(), .designator = PARTITION_ROOT },
+ { SD_GPT_ROOT_NATIVE_VERITY, "root-verity", native_architecture(), .designator = PARTITION_ROOT_VERITY },
+ { SD_GPT_ROOT_NATIVE_VERITY_SIG, "root-verity-sig", native_architecture(), .designator = PARTITION_ROOT_VERITY_SIG },
+ { SD_GPT_USR_NATIVE, "usr", native_architecture(), .designator = PARTITION_USR },
+ { SD_GPT_USR_NATIVE_VERITY, "usr-verity", native_architecture(), .designator = PARTITION_USR_VERITY },
+ { SD_GPT_USR_NATIVE_VERITY_SIG, "usr-verity-sig", native_architecture(), .designator = PARTITION_USR_VERITY_SIG },
#endif
#ifdef SD_GPT_ROOT_SECONDARY
- _GPT_ARCH_SEXTET(SECONDARY, "secondary"),
+ { SD_GPT_ROOT_NATIVE, "root-secondary", native_architecture(), .designator = PARTITION_ROOT },
+ { SD_GPT_ROOT_NATIVE_VERITY, "root-secondary-verity", native_architecture(), .designator = PARTITION_ROOT_VERITY },
+ { SD_GPT_ROOT_NATIVE_VERITY_SIG, "root-secondary-verity-sig", native_architecture(), .designator = PARTITION_ROOT_VERITY_SIG },
+ { SD_GPT_USR_NATIVE, "usr-secondary", native_architecture(), .designator = PARTITION_USR },
+ { SD_GPT_USR_NATIVE_VERITY, "usr-secondary-verity", native_architecture(), .designator = PARTITION_USR_VERITY },
+ { SD_GPT_USR_NATIVE_VERITY_SIG, "usr-secondary-verity-sig", native_architecture(), .designator = PARTITION_USR_VERITY_SIG },
#endif
- { SD_GPT_ESP, "esp", _ARCHITECTURE_INVALID },
- { SD_GPT_XBOOTLDR, "xbootldr", _ARCHITECTURE_INVALID },
- { SD_GPT_SWAP, "swap", _ARCHITECTURE_INVALID },
- { SD_GPT_HOME, "home", _ARCHITECTURE_INVALID },
- { SD_GPT_SRV, "srv", _ARCHITECTURE_INVALID },
- { SD_GPT_VAR, "var", _ARCHITECTURE_INVALID },
- { SD_GPT_TMP, "tmp", _ARCHITECTURE_INVALID },
- { SD_GPT_USER_HOME, "user-home", _ARCHITECTURE_INVALID },
- { SD_GPT_LINUX_GENERIC, "linux-generic", _ARCHITECTURE_INVALID },
+ { SD_GPT_ESP, "esp", _ARCHITECTURE_INVALID, .designator = PARTITION_ESP },
+ { SD_GPT_XBOOTLDR, "xbootldr", _ARCHITECTURE_INVALID, .designator = PARTITION_XBOOTLDR },
+ { SD_GPT_SWAP, "swap", _ARCHITECTURE_INVALID, .designator = PARTITION_SWAP },
+ { SD_GPT_HOME, "home", _ARCHITECTURE_INVALID, .designator = PARTITION_HOME },
+ { SD_GPT_SRV, "srv", _ARCHITECTURE_INVALID, .designator = PARTITION_SRV },
+ { SD_GPT_VAR, "var", _ARCHITECTURE_INVALID, .designator = PARTITION_VAR },
+ { SD_GPT_TMP, "tmp", _ARCHITECTURE_INVALID, .designator = PARTITION_TMP },
+ { SD_GPT_USER_HOME, "user-home", _ARCHITECTURE_INVALID, .designator = PARTITION_USER_HOME },
+ { SD_GPT_LINUX_GENERIC, "linux-generic", _ARCHITECTURE_INVALID, .designator = PARTITION_LINUX_GENERIC },
{}
};
return sd_id128_to_uuid_string(id, buffer);
}
-int gpt_partition_type_uuid_from_string(const char *s, sd_id128_t *ret) {
+int gpt_partition_type_from_string(const char *s, GptPartitionType *ret) {
+ sd_id128_t id;
+ int r;
+
assert(s);
for (size_t i = 0; i < ELEMENTSOF(gpt_partition_type_table) - 1; i++)
if (streq(s, gpt_partition_type_table[i].name)) {
if (ret)
- *ret = gpt_partition_type_table[i].uuid;
+ *ret = gpt_partition_type_table[i];
return 0;
}
- return sd_id128_from_string(s, ret);
+ r = sd_id128_from_string(s, &id);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = gpt_partition_type_from_uuid(id);
+
+ return 0;
}
Architecture gpt_partition_type_uuid_to_arch(sd_id128_t id) {
return char16_strlen(recoded) <= GPT_LABEL_MAX;
}
-static GptPartitionType gpt_partition_type_from_uuid(sd_id128_t id) {
+GptPartitionType gpt_partition_type_from_uuid(sd_id128_t id) {
const GptPartitionType *pt;
pt = gpt_partition_type_find_by_uuid(id);
if (pt)
return *pt;
- return (GptPartitionType) { .uuid = id, .arch = _ARCHITECTURE_INVALID };
-}
-
-bool gpt_partition_type_is_root(sd_id128_t id) {
- return gpt_partition_type_from_uuid(id).is_root;
-}
-
-bool gpt_partition_type_is_root_verity(sd_id128_t id) {
- return gpt_partition_type_from_uuid(id).is_root_verity;
-}
-
-bool gpt_partition_type_is_root_verity_sig(sd_id128_t id) {
- return gpt_partition_type_from_uuid(id).is_root_verity_sig;
-}
-
-bool gpt_partition_type_is_usr(sd_id128_t id) {
- return gpt_partition_type_from_uuid(id).is_usr;
-}
-
-bool gpt_partition_type_is_usr_verity(sd_id128_t id) {
- return gpt_partition_type_from_uuid(id).is_usr_verity;
+ return (GptPartitionType) {
+ .uuid = id,
+ .arch = _ARCHITECTURE_INVALID,
+ .designator = _PARTITION_DESIGNATOR_INVALID,
+ };
}
-bool gpt_partition_type_is_usr_verity_sig(sd_id128_t id) {
- return gpt_partition_type_from_uuid(id).is_usr_verity_sig;
+const char *gpt_partition_type_mountpoint_nulstr(GptPartitionType type) {
+ return partition_mountpoint_to_string(type.designator);
}
-bool gpt_partition_type_knows_read_only(sd_id128_t id) {
- return gpt_partition_type_is_root(id) ||
- gpt_partition_type_is_usr(id) ||
- sd_id128_in_set(id,
- SD_GPT_HOME,
- SD_GPT_SRV,
- SD_GPT_VAR,
- SD_GPT_TMP,
- SD_GPT_XBOOTLDR) ||
- gpt_partition_type_is_root_verity(id) || /* pretty much implied, but let's set the bit to make things really clear */
- gpt_partition_type_is_usr_verity(id); /* ditto */
+bool gpt_partition_type_knows_read_only(GptPartitionType type) {
+ return IN_SET(type.designator,
+ PARTITION_ROOT,
+ PARTITION_USR,
+ /* pretty much implied, but let's set the bit to make things really clear */
+ PARTITION_ROOT_VERITY,
+ PARTITION_USR_VERITY,
+ PARTITION_HOME,
+ PARTITION_SRV,
+ PARTITION_VAR,
+ PARTITION_TMP,
+ PARTITION_XBOOTLDR);
}
-bool gpt_partition_type_knows_growfs(sd_id128_t id) {
- return gpt_partition_type_is_root(id) ||
- gpt_partition_type_is_usr(id) ||
- sd_id128_in_set(id,
- SD_GPT_HOME,
- SD_GPT_SRV,
- SD_GPT_VAR,
- SD_GPT_TMP,
- SD_GPT_XBOOTLDR);
+bool gpt_partition_type_knows_growfs(GptPartitionType type) {
+ return IN_SET(type.designator,
+ PARTITION_ROOT,
+ PARTITION_USR,
+ PARTITION_HOME,
+ PARTITION_SRV,
+ PARTITION_VAR,
+ PARTITION_TMP,
+ PARTITION_XBOOTLDR);
}
-bool gpt_partition_type_knows_no_auto(sd_id128_t id) {
- return gpt_partition_type_is_root(id) ||
- gpt_partition_type_is_root_verity(id) ||
- gpt_partition_type_is_usr(id) ||
- gpt_partition_type_is_usr_verity(id) ||
- sd_id128_in_set(id,
- SD_GPT_HOME,
- SD_GPT_SRV,
- SD_GPT_VAR,
- SD_GPT_TMP,
- SD_GPT_XBOOTLDR,
- SD_GPT_SWAP);
+bool gpt_partition_type_knows_no_auto(GptPartitionType type) {
+ return IN_SET(type.designator,
+ PARTITION_ROOT,
+ PARTITION_ROOT_VERITY,
+ PARTITION_USR,
+ PARTITION_USR_VERITY,
+ PARTITION_HOME,
+ PARTITION_SRV,
+ PARTITION_VAR,
+ PARTITION_TMP,
+ PARTITION_XBOOTLDR,
+ PARTITION_SWAP);
}
/* maximum length of gpt label */
#define GPT_LABEL_MAX 36
+typedef enum PartitionDesignator {
+ PARTITION_ROOT, /* Primary architecture */
+ PARTITION_USR,
+ PARTITION_HOME,
+ PARTITION_SRV,
+ PARTITION_ESP,
+ PARTITION_XBOOTLDR,
+ PARTITION_SWAP,
+ PARTITION_ROOT_VERITY, /* verity data for the PARTITION_ROOT partition */
+ PARTITION_USR_VERITY,
+ PARTITION_ROOT_VERITY_SIG, /* PKCS#7 signature for root hash for the PARTITION_ROOT partition */
+ PARTITION_USR_VERITY_SIG,
+ PARTITION_TMP,
+ PARTITION_VAR,
+ PARTITION_USER_HOME,
+ PARTITION_LINUX_GENERIC,
+ _PARTITION_DESIGNATOR_MAX,
+ _PARTITION_DESIGNATOR_INVALID = -EINVAL,
+} PartitionDesignator;
+
+bool partition_designator_is_versioned(PartitionDesignator d);
+
+PartitionDesignator partition_verity_of(PartitionDesignator p);
+PartitionDesignator partition_verity_sig_of(PartitionDesignator p);
+
+const char* partition_designator_to_string(PartitionDesignator d) _const_;
+PartitionDesignator partition_designator_from_string(const char *name) _pure_;
+
const char *gpt_partition_type_uuid_to_string(sd_id128_t id);
const char *gpt_partition_type_uuid_to_string_harder(
sd_id128_t id,
char buffer[static SD_ID128_UUID_STRING_MAX]);
-int gpt_partition_type_uuid_from_string(const char *s, sd_id128_t *ret);
#define GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(id) \
gpt_partition_type_uuid_to_string_harder((id), (char[SD_ID128_UUID_STRING_MAX]) {})
sd_id128_t uuid;
const char *name;
Architecture arch;
-
- bool is_root:1;
- bool is_root_verity:1;
- bool is_root_verity_sig:1;
- bool is_usr:1;
- bool is_usr_verity:1;
- bool is_usr_verity_sig:1;
+ PartitionDesignator designator;
} GptPartitionType;
extern const GptPartitionType gpt_partition_type_table[];
int gpt_partition_label_valid(const char *s);
-bool gpt_partition_type_is_root(sd_id128_t id);
-bool gpt_partition_type_is_root_verity(sd_id128_t id);
-bool gpt_partition_type_is_root_verity_sig(sd_id128_t id);
-bool gpt_partition_type_is_usr(sd_id128_t id);
-bool gpt_partition_type_is_usr_verity(sd_id128_t id);
-bool gpt_partition_type_is_usr_verity_sig(sd_id128_t id);
+GptPartitionType gpt_partition_type_from_uuid(sd_id128_t id);
+int gpt_partition_type_from_string(const char *s, GptPartitionType *ret);
+
+const char *gpt_partition_type_mountpoint_nulstr(GptPartitionType type);
-bool gpt_partition_type_knows_read_only(sd_id128_t id);
-bool gpt_partition_type_knows_growfs(sd_id128_t id);
-bool gpt_partition_type_knows_no_auto(sd_id128_t id);
+bool gpt_partition_type_knows_read_only(GptPartitionType type);
+bool gpt_partition_type_knows_growfs(GptPartitionType type);
+bool gpt_partition_type_knows_no_auto(GptPartitionType type);
int hwdb_query(const char *modalias, const char *root) {
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
- const char *key, *value, *p;
+ const char *key, *value;
int r;
assert(modalias);
bool hwdb_should_reload(sd_hwdb *hwdb) {
bool found = false;
- const char* p;
struct stat st;
if (!hwdb)
return -ENOMEM;
for (;;) {
- const char *sfx;
bool changed = false;
NULSTR_FOREACH(sfx, suffixes) {
int unit_file_get_list(LookupScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns);
Hashmap* unit_file_list_free(Hashmap *h);
-InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, int type, const char *path, const char *source);
+InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, InstallChangeType type, const char *path, const char *source);
void install_changes_free(InstallChange *changes, size_t n_changes);
void install_changes_dump(int r, const char *verb, const InstallChange *changes, size_t n_changes, bool quiet);
/* from_string conversion is unreliable because of the overlap between -EPERM and -1 for error. */
const char *install_change_type_to_string(InstallChangeType t) _const_;
-int install_change_type_from_string(const char *s) _pure_;
+InstallChangeType install_change_type_from_string(const char *s) _pure_;
const char *unit_file_preset_mode_to_string(UnitFilePresetMode m) _const_;
UnitFilePresetMode unit_file_preset_mode_from_string(const char *s) _pure_;
return json_variant_new_string(ret, SD_ID128_TO_STRING(id));
}
+int json_variant_new_uuid(JsonVariant **ret, sd_id128_t id) {
+ return json_variant_new_string(ret, SD_ID128_TO_UUID_STRING(id));
+}
+
static void json_variant_set(JsonVariant *a, JsonVariant *b) {
assert(a);
break;
}
- case _JSON_BUILD_ID128: {
+ case _JSON_BUILD_ID128:
+ case _JSON_BUILD_UUID: {
const sd_id128_t *id;
if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
assert_se(id = va_arg(ap, sd_id128_t*));
if (current->n_suppress == 0) {
- r = json_variant_new_id128(&add, *id);
+ r = command == _JSON_BUILD_ID128 ?
+ json_variant_new_id128(&add, *id) :
+ json_variant_new_uuid(&add, *id);
if (r < 0)
goto finish;
}
int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n);
int json_variant_new_null(JsonVariant **ret);
int json_variant_new_id128(JsonVariant **ret, sd_id128_t id);
+int json_variant_new_uuid(JsonVariant **ret, sd_id128_t id);
static inline int json_variant_new_string(JsonVariant **ret, const char *s) {
return json_variant_new_stringn(ret, s, SIZE_MAX);
_JSON_BUILD_HEX,
_JSON_BUILD_OCTESCAPE,
_JSON_BUILD_ID128,
+ _JSON_BUILD_UUID,
_JSON_BUILD_BYTE_ARRAY,
_JSON_BUILD_HW_ADDR,
_JSON_BUILD_PAIR_UNSIGNED_NON_ZERO,
#define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_OCTESCAPE(p, n) _JSON_BUILD_OCTESCAPE, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, (const sd_id128_t*) { &(id) }
+#define JSON_BUILD_UUID(id) _JSON_BUILD_UUID, (const sd_id128_t*) { &(id) }
#define JSON_BUILD_BYTE_ARRAY(v, n) _JSON_BUILD_BYTE_ARRAY, (const void*) { v }, (size_t) { n }
#define JSON_BUILD_CONST_STRING(s) _JSON_BUILD_VARIANT, JSON_VARIANT_STRING_CONST(s)
#define JSON_BUILD_IN4_ADDR(v) JSON_BUILD_BYTE_ARRAY((const struct in_addr*) { v }, sizeof(struct in_addr))
#define JSON_BUILD_PAIR_BASE64(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_BASE64(p, n))
#define JSON_BUILD_PAIR_HEX(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_HEX(p, n))
#define JSON_BUILD_PAIR_ID128(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_ID128(id))
+#define JSON_BUILD_PAIR_UUID(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_UUID(id))
#define JSON_BUILD_PAIR_BYTE_ARRAY(name, v, n) JSON_BUILD_PAIR(name, JSON_BUILD_BYTE_ARRAY(v, n))
#define JSON_BUILD_PAIR_IN4_ADDR(name, v) JSON_BUILD_PAIR(name, JSON_BUILD_IN4_ADDR(v))
#define JSON_BUILD_PAIR_IN6_ADDR(name, v) JSON_BUILD_PAIR(name, JSON_BUILD_IN6_ADDR(v))
if (!keymaps)
return -ENOMEM;
- const char *dir;
NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
r = recurse_dir_at(
AT_FDCWD,
if (!keymap_is_valid(name))
return -EINVAL;
- const char *dir;
NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
r = recurse_dir_at(
AT_FDCWD,
return 0;
}
+static int fido2_assert_set_basic_properties(
+ fido_assert_t *a,
+ const char *rp_id,
+ const void *cid,
+ size_t cid_size) {
+ int r;
+
+ assert(a);
+ assert(rp_id);
+ assert(cid);
+ assert(cid_size > 0);
+
+ r = sym_fido_assert_set_rp(a, rp_id);
+ if (r != FIDO_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
+
+ r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
+ if (r != FIDO_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
+
+ r = sym_fido_assert_allow_cred(a, cid, cid_size);
+ if (r != FIDO_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
+
+ return 0;
+}
+
+static int fido2_common_assert_error_handle(int r) {
+ switch (r) {
+ case FIDO_OK:
+ return 0;
+ case FIDO_ERR_NO_CREDENTIALS:
+ return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
+ "Wrong security token; needed credentials not present on token.");
+ case FIDO_ERR_PIN_REQUIRED:
+ return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
+ "Security token requires PIN.");
+ case FIDO_ERR_PIN_AUTH_BLOCKED:
+ return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
+ "PIN of security token is blocked, please remove/reinsert token.");
+#ifdef FIDO_ERR_UV_BLOCKED
+ case FIDO_ERR_UV_BLOCKED:
+ return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
+ "Verification of security token is blocked, please remove/reinsert token.");
+#endif
+ case FIDO_ERR_PIN_INVALID:
+ return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
+ "PIN of security token incorrect.");
+ case FIDO_ERR_UP_REQUIRED:
+ return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
+ "User presence required.");
+ case FIDO_ERR_ACTION_TIMEOUT:
+ return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
+ "Token action timeout. (User didn't interact with token quickly enough.)");
+ default:
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to ask token for assertion: %s", sym_fido_strerr(r));
+ }
+}
+
+static int fido2_is_cred_in_specific_token(
+ const char *path,
+ const char *rp_id,
+ const void *cid,
+ size_t cid_size,
+ char **pins,
+ Fido2EnrollFlags flags) {
+
+ assert(path);
+ assert(rp_id);
+ assert(cid);
+ assert(cid_size);
+
+ _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
+ _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
+ bool has_up = false, has_uv = false;
+ int r;
+
+ d = sym_fido_dev_new();
+ if (!d)
+ return log_oom();
+
+ r = sym_fido_dev_open(d, path);
+ if (r != FIDO_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r));
+
+ r = verify_features(d, path, LOG_ERR, NULL, NULL, &has_up, &has_uv);
+ if (r < 0)
+ return r;
+
+ a = sym_fido_assert_new();
+ if (!a)
+ return log_oom();
+
+ r = fido2_assert_set_basic_properties(a, rp_id, cid, cid_size);
+ if (r < 0)
+ return r;
+
+ /* According to CTAP 2.1 specification, to do pre-flight we need to set up option to false
+ * with optionally pinUvAuthParam in assertion[1]. But for authenticator that doesn't support
+ * user presence, once up option is present, the authenticator may return CTAP2_ERR_UNSUPPORTED_OPTION[2].
+ * So we simplely omit the option in that case.
+ * Reference:
+ * 1: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight
+ * 2: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion (in step 5)
+ */
+ if (has_up)
+ r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE);
+ else
+ r = sym_fido_assert_set_up(a, FIDO_OPT_OMIT);
+ if (r != FIDO_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to set assertion user presence: %s", sym_fido_strerr(r));
+
+
+ /* According to CTAP 2.1 specification, if the credential is marked as userVerificationRequired,
+ * we may pass pinUvAuthParam parameter or set uv option to true in order to check whether the
+ * credential is in the token. Here we choose to use PIN (pinUvAuthParam) to achieve this.
+ * If we don't do that, the authenticator will remove the credential from the applicable
+ * credentials list, hence CTAP2_ERR_NO_CREDENTIALS error will be returned.
+ * Please see
+ * https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#sctn-getAssert-authnr-alg
+ * step 7.4 for detailed information.
+ */
+ if (FLAGS_SET(flags, FIDO2ENROLL_UV) && has_uv) {
+ if (strv_isempty(pins))
+ r = FIDO_ERR_PIN_REQUIRED;
+ else
+ STRV_FOREACH(i, pins) {
+ r = sym_fido_dev_get_assert(d, a, *i);
+ if (r != FIDO_ERR_PIN_INVALID)
+ break;
+ }
+
+ } else
+ r = sym_fido_dev_get_assert(d, a, NULL);
+
+ switch (r) {
+ case FIDO_OK:
+ return true;
+ case FIDO_ERR_NO_CREDENTIALS:
+ return false;
+ default:
+ return fido2_common_assert_error_handle(r);
+ }
+}
+
static int fido2_use_hmac_hash_specific_token(
const char *path,
const char *rp_id,
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
- r = sym_fido_assert_set_rp(a, rp_id);
- if (r != FIDO_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
-
- r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
- if (r != FIDO_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
-
- r = sym_fido_assert_allow_cred(a, cid, cid_size);
- if (r != FIDO_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
+ r = fido2_assert_set_basic_properties(a, rp_id, cid, cid_size);
+ if (r < 0)
+ return r;
log_info("Asking FIDO2 token for authentication.");
required |= FIDO2ENROLL_PIN;
}
- switch (r) {
- case FIDO_OK:
- break;
- case FIDO_ERR_NO_CREDENTIALS:
- return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
- "Wrong security token; needed credentials not present on token.");
- case FIDO_ERR_PIN_REQUIRED:
- return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
- "Security token requires PIN.");
- case FIDO_ERR_PIN_AUTH_BLOCKED:
- return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
- "PIN of security token is blocked, please remove/reinsert token.");
-#ifdef FIDO_ERR_UV_BLOCKED
- case FIDO_ERR_UV_BLOCKED:
- return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
- "Verification of security token is blocked, please remove/reinsert token.");
-#endif
- case FIDO_ERR_PIN_INVALID:
- return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
- "PIN of security token incorrect.");
- case FIDO_ERR_UP_REQUIRED:
- return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
- "User presence required.");
- case FIDO_ERR_ACTION_TIMEOUT:
- return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
- "Token action timeout. (User didn't interact with token quickly enough.)");
- default:
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to ask token for assertion: %s", sym_fido_strerr(r));
- }
+ r = fido2_common_assert_error_handle(r);
+ if (r < 0)
+ return r;
hmac = sym_fido_assert_hmac_secret_ptr(a, 0);
if (!hmac)
goto finish;
}
+ r = fido2_is_cred_in_specific_token(path, rp_id, cid, cid_size, pins, required);
+ /* We handle PIN related errors here since we have used PIN in the check.
+ * If PIN error happens, we can avoid pin retries decreased the second time. */
+ if (IN_SET(r, -ENOANO, /* → pin required */
+ -ENOLCK, /* → pin incorrect */
+ -EOWNERDEAD)) { /* → uv blocked */
+ /* If it's not the last device, try next one */
+ if (i < found - 1)
+ continue;
+
+ /* -EOWNERDEAD is returned when uv is blocked. Map it to EAGAIN so users can reinsert
+ * the token and try again. */
+ if (r == -EOWNERDEAD)
+ r = -EAGAIN;
+ goto finish;
+ } else if (r == -ENODEV) /* not a FIDO2 device or lacking HMAC-SECRET extension */
+ continue;
+ else if (r < 0)
+ log_error_errno(r, "Failed to determine whether the credential is in the token, trying anyway: %m");
+ else if (r == 0) {
+ log_debug("The credential is not in the token %s, skipping.", path);
+ continue;
+ }
+
r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size);
if (!IN_SET(r,
-EBADSLT, /* device doesn't understand our credential hash */
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
- r = sym_fido_assert_set_rp(a, rp_id);
- if (r != FIDO_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
-
- r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
- if (r != FIDO_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
-
- r = sym_fido_assert_allow_cred(a, cid, cid_size);
- if (r != FIDO_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
+ r = fido2_assert_set_basic_properties(a, rp_id, cid, cid_size);
+ if (r < 0)
+ return r;
log_info("Generating secret key on FIDO2 security token.");
#include <unistd.h>
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
#include "id128-util.h"
#include "mkfs-util.h"
#include "mountpoint-util.h"
#include "path-util.h"
#include "process-util.h"
+#include "recurse-dir.h"
+#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
+#include "tmpfile-util.h"
#include "utf8.h"
int mkfs_exists(const char *fstype) {
return true;
}
+int mkfs_supports_root_option(const char *fstype) {
+ return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat", "xfs");
+}
+
static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) {
/* Not more than max_len bytes (12 or 16) */
return 0;
}
+static int setup_userns(uid_t uid, gid_t gid) {
+ int r;
+
+ /* mkfs programs tend to keep ownership intact when bootstrapping themselves from a root directory.
+ * However, we'd like for the files to be owned by root instead, so we fork off a user namespace and
+ * inside of it, map the uid/gid of the root directory to root in the user namespace. mkfs programs
+ * will pick up on this and the files will be owned by root in the generated filesystem. */
+
+ r = write_string_filef("/proc/self/uid_map", WRITE_STRING_FILE_DISABLE_BUFFER,
+ UID_FMT " " UID_FMT " " UID_FMT, 0u, uid, 1u);
+ if (r < 0)
+ return log_error_errno(r,
+ "Failed to write mapping for "UID_FMT" to /proc/self/uid_map: %m",
+ uid);
+
+ r = write_string_file("/proc/self/setgroups", "deny", WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write 'deny' to /proc/self/setgroups: %m");
+
+ r = write_string_filef("/proc/self/gid_map", WRITE_STRING_FILE_DISABLE_BUFFER,
+ UID_FMT " " UID_FMT " " UID_FMT, 0u, gid, 1u);
+ if (r < 0)
+ return log_error_errno(r,
+ "Failed to write mapping for "UID_FMT" to /proc/self/gid_map: %m",
+ gid);
+
+ return 0;
+}
+
+static int do_mcopy(const char *node, const char *root) {
+ _cleanup_strv_free_ char **argv = NULL;
+ _cleanup_closedir_ DIR *rootdir = NULL;
+ struct stat st;
+ int r;
+
+ assert(node);
+ assert(root);
+
+ /* Return early if there's nothing to copy. */
+ if (dir_is_empty(root, /*ignore_hidden_or_backup=*/ false))
+ return 0;
+
+ argv = strv_new("mcopy", "-b", "-s", "-p", "-Q", "-n", "-m", "-i", node);
+ if (!argv)
+ return log_oom();
+
+ /* mcopy copies the top level directory instead of everything in it so we have to pass all
+ * the subdirectories to mcopy instead to end up with the correct directory structure. */
+
+ rootdir = opendir(root);
+ if (!rootdir)
+ return log_error_errno(errno, "Failed to open directory '%s'", root);
+
+ FOREACH_DIRENT(de, rootdir, return -errno) {
+ char *p = path_join(root, de->d_name);
+ if (!p)
+ return log_oom();
+
+ r = strv_consume(&argv, TAKE_PTR(p));
+ if (r < 0)
+ return log_oom();
+ }
+
+ r = strv_extend(&argv, "::");
+ if (r < 0)
+ return log_oom();
+
+ if (stat(root, &st) < 0)
+ return log_error_errno(errno, "Failed to stat '%s': %m", root);
+
+ r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_NEW_USERNS, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ r = setup_userns(st.st_uid, st.st_gid);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
+ * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
+ execvpe("mcopy", argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1"));
+
+ log_error_errno(errno, "Failed to execute mcopy: %m");
+
+ _exit(EXIT_FAILURE);
+ }
+
+ return 0;
+}
+
+static int protofile_print_item(
+ RecurseDirEvent event,
+ const char *path,
+ int dir_fd,
+ int inode_fd,
+ const struct dirent *de,
+ const struct statx *sx,
+ void *userdata) {
+
+ FILE *f = ASSERT_PTR(userdata);
+ int r;
+
+ if (event == RECURSE_DIR_LEAVE) {
+ fputs("$\n", f);
+ return 0;
+ }
+
+ if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
+ return RECURSE_DIR_CONTINUE;
+
+ char type = S_ISDIR(sx->stx_mode) ? 'd' :
+ S_ISREG(sx->stx_mode) ? '-' :
+ S_ISLNK(sx->stx_mode) ? 'l' :
+ S_ISFIFO(sx->stx_mode) ? 'p' :
+ S_ISBLK(sx->stx_mode) ? 'b' :
+ S_ISCHR(sx->stx_mode) ? 'c' : 0;
+ if (type == 0)
+ return RECURSE_DIR_CONTINUE;
+
+ fprintf(f, "%s %c%c%c%03o 0 0 ",
+ de->d_name,
+ type,
+ sx->stx_mode & S_ISUID ? 'u' : '-',
+ sx->stx_mode & S_ISGID ? 'g' : '-',
+ (unsigned) (sx->stx_mode & 0777));
+
+ if (S_ISREG(sx->stx_mode))
+ fputs(path, f);
+ else if (S_ISLNK(sx->stx_mode)) {
+ _cleanup_free_ char *p = NULL;
+
+ r = readlinkat_malloc(dir_fd, de->d_name, &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read symlink %s: %m", path);
+
+ fputs(p, f);
+ } else if (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode))
+ fprintf(f, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
+
+ fputc('\n', f);
+
+ return RECURSE_DIR_CONTINUE;
+}
+
+static int make_protofile(const char *root, char **ret) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_(unlink_and_freep) char *p = NULL;
+ const char *vt;
+ int r;
+
+ assert(ret);
+
+ r = var_tmp_dir(&vt);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get persistent temporary directory: %m");
+
+ r = fopen_temporary_child(vt, &f, &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open temporary file: %m");
+
+ fputs("/\n"
+ "0 0\n"
+ "d--755 0 0\n", f);
+
+ r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE, UINT_MAX, 0, protofile_print_item, f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to recurse through %s: %m", root);
+
+ fputs("$\n", f);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to flush %s: %m", p);
+
+ *ret = TAKE_PTR(p);
+
+ return 0;
+}
+
int make_filesystem(
const char *node,
const char *fstype,
bool discard) {
_cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
+ _cleanup_strv_free_ char **argv = NULL;
+ _cleanup_(unlink_and_freep) char *protofile = NULL;
char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
+ struct stat st;
int r;
assert(node);
"Don't know how to create read-only file system '%s', refusing.",
fstype);
} else {
- if (root)
+ if (root && !mkfs_supports_root_option(fstype))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "Populating with source tree is only supported for read-only filesystems");
+ "Populating with source tree is not supported for %s", fstype);
r = mkfs_exists(fstype);
if (r < 0)
return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype);
if (isempty(vol_id))
assert_se(sd_id128_to_uuid_string(uuid, vol_id));
- r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, NULL);
+ /* When changing this conditional, also adjust the log statement below. */
+ if (streq(fstype, "ext2")) {
+ argv = strv_new(mkfs,
+ "-q",
+ "-L", label,
+ "-U", vol_id,
+ "-I", "256",
+ "-m", "0",
+ "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
+ node);
+ if (!argv)
+ return log_oom();
+
+ if (root) {
+ r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
+ if (r < 0)
+ return log_oom();
+ }
+
+ } else if (STR_IN_SET(fstype, "ext3", "ext4")) {
+ argv = strv_new(mkfs,
+ "-q",
+ "-L", label,
+ "-U", vol_id,
+ "-I", "256",
+ "-O", "has_journal",
+ "-m", "0",
+ "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
+ node);
+
+ if (root) {
+ r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
+ if (r < 0)
+ return log_oom();
+ }
+
+ } else if (streq(fstype, "btrfs")) {
+ argv = strv_new(mkfs,
+ "-q",
+ "-L", label,
+ "-U", vol_id,
+ node);
+ if (!argv)
+ return log_oom();
+
+ if (!discard) {
+ r = strv_extend(&argv, "--nodiscard");
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (root) {
+ r = strv_extend_strv(&argv, STRV_MAKE("-r", root), false);
+ if (r < 0)
+ return log_oom();
+ }
+
+ } else if (streq(fstype, "f2fs")) {
+ argv = strv_new(mkfs,
+ "-q",
+ "-g", /* "default options" */
+ "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
+ "-l", label,
+ "-U", vol_id,
+ "-t", one_zero(discard),
+ node);
+
+ } else if (streq(fstype, "xfs")) {
+ const char *j;
+
+ j = strjoina("uuid=", vol_id);
+
+ argv = strv_new(mkfs,
+ "-q",
+ "-L", label,
+ "-m", j,
+ "-m", "reflink=1",
+ node);
+ if (!argv)
+ return log_oom();
+
+ if (!discard) {
+ r = strv_extend(&argv, "-K");
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (root) {
+ r = make_protofile(root, &protofile);
+ if (r < 0)
+ return r;
+
+ r = strv_extend_strv(&argv, STRV_MAKE("-p", protofile), false);
+ if (r < 0)
+ return log_oom();
+ }
+
+ } else if (streq(fstype, "vfat"))
+
+ argv = strv_new(mkfs,
+ "-i", vol_id,
+ "-n", label,
+ "-F", "32", /* yes, we force FAT32 here */
+ node);
+
+ else if (streq(fstype, "swap"))
+ /* TODO: add --quiet here if
+ * https://github.com/util-linux/util-linux/issues/1499 resolved. */
+
+ argv = strv_new(mkfs,
+ "-L", label,
+ "-U", vol_id,
+ node);
+
+ else if (streq(fstype, "squashfs"))
+
+ argv = strv_new(mkfs,
+ root, node,
+ "-quiet",
+ "-noappend");
+ else
+ /* Generic fallback for all other file systems */
+ argv = strv_new(mkfs, node);
+
+ if (!argv)
+ return log_oom();
+
+ if (root && stat(root, &st) < 0)
+ return log_error_errno(errno, "Failed to stat %s: %m", root);
+
+ r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|(root ? FORK_NEW_USERNS : 0), NULL);
if (r < 0)
return r;
if (r == 0) {
/* Child */
- /* When changing this conditional, also adjust the log statement below. */
- if (streq(fstype, "ext2"))
- (void) execlp(mkfs, mkfs,
- "-q",
- "-L", label,
- "-U", vol_id,
- "-I", "256",
- "-m", "0",
- "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
- node, NULL);
-
- else if (STR_IN_SET(fstype, "ext3", "ext4"))
- (void) execlp(mkfs, mkfs,
- "-q",
- "-L", label,
- "-U", vol_id,
- "-I", "256",
- "-O", "has_journal",
- "-m", "0",
- "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
- node, NULL);
-
- else if (streq(fstype, "btrfs")) {
- (void) execlp(mkfs, mkfs,
- "-q",
- "-L", label,
- "-U", vol_id,
- node,
- discard ? NULL : "--nodiscard",
- NULL);
-
- } else if (streq(fstype, "f2fs")) {
- (void) execlp(mkfs, mkfs,
- "-q",
- "-g", /* "default options" */
- "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
- "-l", label,
- "-U", vol_id,
- "-t", one_zero(discard),
- node,
- NULL);
-
- } else if (streq(fstype, "xfs")) {
- const char *j;
-
- j = strjoina("uuid=", vol_id);
-
- (void) execlp(mkfs, mkfs,
- "-q",
- "-L", label,
- "-m", j,
- "-m", "reflink=1",
- node,
- discard ? NULL : "-K",
- NULL);
-
- } else if (streq(fstype, "vfat"))
-
- (void) execlp(mkfs, mkfs,
- "-i", vol_id,
- "-n", label,
- "-F", "32", /* yes, we force FAT32 here */
- node, NULL);
-
- else if (streq(fstype, "swap"))
- /* TODO: add --quiet here if
- * https://github.com/util-linux/util-linux/issues/1499 resolved. */
-
- (void) execlp(mkfs, mkfs,
- "-L", label,
- "-U", vol_id,
- node, NULL);
-
- else if (streq(fstype, "squashfs"))
-
- (void) execlp(mkfs, mkfs,
- root, node,
- "-quiet",
- "-noappend",
- NULL);
- else
- /* Generic fallback for all other file systems */
- (void) execlp(mkfs, mkfs, node, NULL);
+ if (root) {
+ r = setup_userns(st.st_uid, st.st_gid);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+ }
+
+ execvp(mkfs, argv);
log_error_errno(errno, "Failed to execute %s: %m", mkfs);
_exit(EXIT_FAILURE);
}
+ if (root && streq(fstype, "vfat")) {
+ r = do_mcopy(node, root);
+ if (r < 0)
+ return r;
+ }
+
if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
node, fstype, label, vol_id);
int mkfs_exists(const char *fstype);
+int mkfs_supports_root_option(const char *fstype);
+
int make_filesystem(const char *node, const char *fstype, const char *label, const char *root, sd_id128_t uuid, bool discard);
#include <errno.h>
#include "module-util.h"
+#include "proc-cmdline.h"
+#include "strv.h"
+
+static int denylist_modules(const char *p, char ***denylist) {
+ _cleanup_strv_free_ char **k = NULL;
+
+ assert(p);
+ assert(denylist);
+
+ k = strv_split(p, ",");
+ if (!k)
+ return -ENOMEM;
+
+ if (strv_extend_strv(denylist, k, true) < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
+ int r;
+
+ if (proc_cmdline_key_streq(key, "module_blacklist")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ r = denylist_modules(value, data);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) {
const int probe_flags = KMOD_PROBE_APPLY_BLACKLIST;
struct kmod_list *itr;
_cleanup_(kmod_module_unref_listp) struct kmod_list *modlist = NULL;
+ _cleanup_strv_free_ char **denylist = NULL;
+ bool denylist_parsed = false;
int r;
/* verbose==true means we should log at non-debug level if we
"Inserted module '%s'", kmod_module_get_name(mod));
else if (err == KMOD_PROBE_APPLY_BLACKLIST)
log_full(verbose ? LOG_INFO : LOG_DEBUG,
- "Module '%s' is deny-listed", kmod_module_get_name(mod));
+ "Module '%s' is deny-listed (by kmod)", kmod_module_get_name(mod));
else {
assert(err < 0);
+ if (err == -EPERM) {
+ if (!denylist_parsed) {
+ r = proc_cmdline_parse(parse_proc_cmdline_item, &denylist, 0);
+ if (r < 0)
+ log_full_errno(!verbose ? LOG_DEBUG : LOG_WARNING,
+ r,
+ "Failed to parse kernel command line, ignoring: %m");
+
+ denylist_parsed = true;
+ }
+ if (strv_contains(denylist, kmod_module_get_name(mod))) {
+ log_full(verbose ? LOG_INFO : LOG_DEBUG,
+ "Module '%s' is deny-listed (by kernel)", kmod_module_get_name(mod));
+ continue;
+ }
+ }
+
log_full_errno(!verbose ? LOG_DEBUG :
err == -ENODEV ? LOG_NOTICE :
err == -ENOENT ? LOG_WARNING :
int resize_fs(int fd, uint64_t sz, uint64_t *ret_size);
#define BTRFS_MINIMAL_SIZE (256U*1024U*1024U)
-#define XFS_MINIMAL_SIZE (14U*1024U*1024U)
+#define XFS_MINIMAL_SIZE (16U*1024U*1024U)
#define EXT4_MINIMAL_SIZE (1024U*1024U)
uint64_t minimal_size_by_fs_magic(statfs_f_type_t magic);
typedef enum DnssecMode DnssecMode;
typedef enum DnsOverTlsMode DnsOverTlsMode;
+/* Do not change the order, see link_get_llmnr_support() or link_get_mdns_support(). */
enum ResolveSupport {
RESOLVE_SUPPORT_NO,
- RESOLVE_SUPPORT_YES,
RESOLVE_SUPPORT_RESOLVE,
+ RESOLVE_SUPPORT_YES,
_RESOLVE_SUPPORT_MAX,
_RESOLVE_SUPPORT_INVALID = -EINVAL,
};
#include "alloc-util.h"
#include "errno-util.h"
-#include "fd-util.h"
typedef enum RemoveFlags {
REMOVE_ONLY_DIRECTORIES = 1 << 0, /* Only remove empty directories, no files */
return mfree(p);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_subvolume_and_free);
-
-static inline int rm_rf_physical_and_close(int fd) {
- _cleanup_free_ char *p = NULL;
-
- if (fd < 0)
- return -1;
-
- if (fd_get_path(fd, &p) < 0)
- return safe_close(fd);
-
- safe_close(fd);
- (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK|REMOVE_CHMOD);
-
- return -1;
-}
-DEFINE_TRIVIAL_CLEANUP_FUNC(int, rm_rf_physical_and_close);
bool log_missing,
char ***added) {
- const char *sys;
int r;
/* Any syscalls that are handled are added to the *added strv. It needs to be initialized. */
if (name[0] == '@') {
const SyscallFilterSet *set;
- const char *i;
set = syscall_filter_set_find(name);
if (!set) {
}
int seccomp_filter_set_add(Hashmap *filter, bool add, const SyscallFilterSet *set) {
- const char *i;
int r;
assert(set);
SECCOMP_FOREACH_LOCAL_ARCH(arch) {
_cleanup_(seccomp_releasep) scmp_filter_ctx seccomp = NULL;
- const char *c;
r = seccomp_init_for_arch(&seccomp, arch, SCMP_ACT_ALLOW);
if (r < 0)
int r;
+ assert(path);
+
r = mac_selinux_create_file_prepare(target, S_IFREG);
if (r < 0)
return r;
if (r < 0)
return log_error_errno(r, "TPM2 support not installed: %m");
- if (!device)
+ if (!device) {
device = secure_getenv("SYSTEMD_TPM2_DEVICE");
+ if (device)
+ /* Setting the env var to an empty string forces tpm2-tss' own device picking
+ * logic to be used. */
+ device = empty_to_null(device);
+ else
+ /* If nothing was specified explicitly, we'll use a hardcoded default: the "device" tcti
+ * driver and the "/dev/tpmrm0" device. We do this since on some distributions the tpm2-abrmd
+ * might be used and we really don't want that, since it is a system service and that creates
+ * various ordering issues/deadlocks during early boot. */
+ device = "device:/dev/tpmrm0";
+ }
if (device) {
const char *param, *driver, *fn;
param = strchr(device, ':');
if (param) {
+ /* Syntax #1: Pair of driver string and arbitrary parameter */
driver = strndupa_safe(device, param - device);
+ if (isempty(driver))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name is empty, refusing.");
+
param++;
- } else {
+ } else if (path_is_absolute(device) && path_is_valid(device)) {
+ /* Syntax #2: TPM device node */
driver = "device";
param = device;
- }
+ } else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid TPM2 driver string, refusing.");
+
+ log_debug("Using TPM2 TCTI driver '%s' with device '%s'.", driver, param);
fn = strjoina("libtss2-tcti-", driver, ".so.0");
+ /* Better safe than sorry, let's refuse strings that cannot possibly be valid driver early, before going to disk. */
+ if (!filename_is_valid(fn))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name '%s' not valid, refusing.", driver);
+
dl = dlopen(fn, RTLD_NOW);
if (!dl)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror());
ESYS_TR_NONE,
NULL,
&pubkey_tpm2,
+#if HAVE_TSS2_ESYS3
+ /* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested
+ * hierarchy, older versions need TPM2_RH_* instead. */
+ ESYS_TR_RH_OWNER,
+#else
TPM2_RH_OWNER,
+#endif
&pubkey_handle);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
return found_sink || !found_source;
}
+static bool battery_is_discharging(sd_device *d) {
+ const char *val;
+ int r;
+
+ assert(d);
+
+ r = sd_device_get_sysattr_value(d, "scope", &val);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m");
+ } else if (streq(val, "Device")) {
+ log_device_debug(d, "The power supply is a device battery, ignoring device.");
+ return false;
+ }
+
+ r = device_get_sysattr_bool(d, "present");
+ if (r < 0)
+ log_device_debug_errno(d, r, "Failed to read 'present' sysfs attribute, assuming the battery is present: %m");
+ else if (r == 0) {
+ log_device_debug(d, "The battery is not present, ignoring the power supply.");
+ return false;
+ }
+
+ /* Possible values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */
+ r = sd_device_get_sysattr_value(d, "status", &val);
+ if (r < 0) {
+ log_device_debug_errno(d, r, "Failed to read 'status' sysfs attribute, assuming the battery is discharging: %m");
+ return true;
+ }
+ if (!streq(val, "Discharging")) {
+ log_device_debug(d, "The battery status is '%s', assuming the battery is not used as a power source of this machine.", val);
+ return false;
+ }
+
+ return true;
+}
+
int on_ac_power(void) {
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
- bool found_ac_online = false, found_battery = false;
+ bool found_ac_online = false, found_discharging_battery = false;
sd_device *d;
int r;
}
if (streq(val, "Battery")) {
- r = sd_device_get_sysattr_value(d, "scope", &val);
- if (r < 0) {
- if (r != -ENOENT)
- log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m");
- } else if (streq(val, "Device")) {
- log_device_debug(d, "The power supply is a device battery, ignoring device.");
- continue;
+ if (battery_is_discharging(d)) {
+ found_discharging_battery = true;
+ log_device_debug(d, "The power supply is a battery and currently discharging.");
}
-
- found_battery = true;
- log_device_debug(d, "The power supply is battery.");
continue;
}
if (found_ac_online) {
log_debug("Found at least one online non-battery power supply, system is running on AC.");
return true;
- } else if (found_battery) {
- log_debug("Found battery and no online power sources, assuming system is running from battery.");
+ } else if (found_discharging_battery) {
+ log_debug("Found at least one discharging battery and no online power sources, assuming system is running from battery.");
return false;
} else {
- log_debug("No power supply reported online and no battery, assuming system is running on AC.");
+ log_debug("No power supply reported online and no discharging battery found, assuming system is running on AC.");
return true;
}
}
#include "env-util.h"
#include "fd-util.h"
#include "fuzz.h"
+#include "nulstr-util.h"
#include "selinux-util.h"
#include "static-destruct.h"
#include "stdio-util.h"
uint64_t memory_high;
uint64_t memory_max;
uint64_t memory_swap_max;
+ uint64_t memory_zswap_max;
uint64_t memory_limit;
uint64_t memory_available;
uint64_t cpu_usage_nsec;
if (i->memory_min > 0 || i->memory_low > 0 ||
i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX ||
i->memory_swap_max != CGROUP_LIMIT_MAX ||
+ i->memory_zswap_max != CGROUP_LIMIT_MAX ||
i->memory_available != CGROUP_LIMIT_MAX ||
i->memory_limit != CGROUP_LIMIT_MAX) {
const char *prefix = "";
printf("%sswap max: %s", prefix, FORMAT_BYTES(i->memory_swap_max));
prefix = " ";
}
+ if (i->memory_zswap_max != CGROUP_LIMIT_MAX) {
+ printf("%szswap max: %s", prefix, FORMAT_BYTES(i->memory_zswap_max));
+ prefix = " ";
+ }
if (i->memory_limit != CGROUP_LIMIT_MAX) {
printf("%slimit: %s", prefix, FORMAT_BYTES(i->memory_limit));
prefix = " ";
{ "MemoryHigh", "t", NULL, offsetof(UnitStatusInfo, memory_high) },
{ "MemoryMax", "t", NULL, offsetof(UnitStatusInfo, memory_max) },
{ "MemorySwapMax", "t", NULL, offsetof(UnitStatusInfo, memory_swap_max) },
+ { "MemoryZSwapMax", "t", NULL, offsetof(UnitStatusInfo, memory_zswap_max) },
{ "MemoryLimit", "t", NULL, offsetof(UnitStatusInfo, memory_limit) },
{ "CPUUsageNSec", "t", NULL, offsetof(UnitStatusInfo, cpu_usage_nsec) },
{ "TasksCurrent", "t", NULL, offsetof(UnitStatusInfo, tasks_current) },
.memory_high = CGROUP_LIMIT_MAX,
.memory_max = CGROUP_LIMIT_MAX,
.memory_swap_max = CGROUP_LIMIT_MAX,
+ .memory_zswap_max = CGROUP_LIMIT_MAX,
.memory_limit = UINT64_MAX,
.memory_available = CGROUP_LIMIT_MAX,
.cpu_usage_nsec = UINT64_MAX,
if (!arg_states && !arg_types) {
if (show_mode == SYSTEMCTL_SHOW_PROPERTIES)
- r = show_one(bus, "/org/freedesktop/systemd1", NULL, show_mode, &new_line, &ellipsized);
- else
- r = show_system_status(bus);
+ /* systemctl show --all → show properties of the manager */
+ return show_one(bus, "/org/freedesktop/systemd1", NULL, show_mode, &new_line, &ellipsized);
+
+ r = show_system_status(bus);
if (r < 0)
return r;
struct fdisk_parttype *pt;
uint64_t start, size, flags;
sd_id128_t ptid, id;
+ GptPartitionType type;
size_t partno;
int r;
if (!label_copy)
return log_oom();
+ type = gpt_partition_type_from_uuid(ptid);
+
*ret = (PartitionInfo) {
.partno = partno,
.start = start,
.uuid = id,
.label = TAKE_PTR(label_copy),
.device = TAKE_PTR(device),
- .no_auto = FLAGS_SET(flags, SD_GPT_FLAG_NO_AUTO) && gpt_partition_type_knows_no_auto(ptid),
- .read_only = FLAGS_SET(flags, SD_GPT_FLAG_READ_ONLY) && gpt_partition_type_knows_read_only(ptid),
- .growfs = FLAGS_SET(flags, SD_GPT_FLAG_GROWFS) && gpt_partition_type_knows_growfs(ptid),
+ .no_auto = FLAGS_SET(flags, SD_GPT_FLAG_NO_AUTO) && gpt_partition_type_knows_no_auto(type),
+ .read_only = FLAGS_SET(flags, SD_GPT_FLAG_READ_ONLY) && gpt_partition_type_knows_read_only(type),
+ .growfs = FLAGS_SET(flags, SD_GPT_FLAG_GROWFS) && gpt_partition_type_knows_growfs(type),
};
return 1; /* found! */
_cleanup_(fdisk_unref_partitionp) struct fdisk_partition *pa = NULL;
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
bool tweak_no_auto, tweak_read_only, tweak_growfs;
+ GptPartitionType type;
int r, fd;
assert(device);
return log_error_errno(r, "Failed to update partition UUID: %m");
}
+ type = gpt_partition_type_from_uuid(info->type);
+
/* Tweak the read-only flag, but only if supported by the partition type */
tweak_no_auto =
FLAGS_SET(change, PARTITION_NO_AUTO) &&
- gpt_partition_type_knows_no_auto(info->type);
+ gpt_partition_type_knows_no_auto(type);
tweak_read_only =
FLAGS_SET(change, PARTITION_READ_ONLY) &&
- gpt_partition_type_knows_read_only(info->type);
+ gpt_partition_type_knows_read_only(type);
tweak_growfs =
FLAGS_SET(change, PARTITION_GROWFS) &&
- gpt_partition_type_knows_growfs(info->type);
+ gpt_partition_type_knows_growfs(type);
if (change & PARTITION_FLAGS) {
uint64_t flags;
#include "blockdev-util.h"
#include "chase-symlinks.h"
#include "device-util.h"
+#include "devnum-util.h"
#include "dirent-util.h"
#include "env-util.h"
#include "fd-util.h"
continue;
/* Check if partition type matches */
- if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type))
+ if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type.uuid))
continue;
/* A label of "_empty" means "not used so far" for us */
assert(rr);
if (rr->path_auto) {
+ struct stat orig_root_stats;
- /* NB: we don't actually check the backing device of the root fs "/", but of "/usr", in order
- * to support environments where the root fs is a tmpfs, and the OS itself placed exclusively
- * in /usr/. */
+ /* NB: If the root mount has been replaced by some form of volatile file system (overlayfs),
+ * the original root block device node is symlinked in /run/systemd/volatile-root. Let's
+ * follow that link here. If that doesn't exist, we check the backing device of "/usr". We
+ * don't actually check the backing device of the root fs "/", in order to support
+ * environments where the root fs is a tmpfs, and the OS itself placed exclusively in
+ * /usr/. */
if (rr->type != RESOURCE_PARTITION)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Block device is not allowed when using --root= mode.");
- r = get_block_device_harder("/usr/", &d);
+ r = stat("/run/systemd/volatile-root", &orig_root_stats);
+ if (r < 0) {
+ if (errno == -ENOENT) /* volatile-root not found */
+ r = get_block_device_harder("/usr/", &d);
+ else
+ return log_error_errno(r, "Failed to stat /run/systemd/volatile-root: %m");
+ } else if (!S_ISBLK(orig_root_stats.st_mode)) /* symlink was present but not block device */
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "/run/systemd/volatile-root is not linked to a block device.");
+ else /* symlink was present and a block device */
+ d = orig_root_stats.st_rdev;
} else if (rr->type == RESOURCE_PARTITION) {
_cleanup_close_ int fd = -1, real_fd = -1;
#include <stdbool.h>
#include <sys/types.h>
-#include "sd-id128.h"
-
+#include "gpt.h"
#include "hashmap.h"
#include "macro.h"
char *path;
bool path_auto; /* automatically find root path (only available if target resource, not source resource) */
char **patterns;
- sd_id128_t partition_type;
+ GptPartitionType partition_type;
bool partition_type_set;
/* All instances of this resource we found */
assert(rvalue);
- r = gpt_partition_type_uuid_from_string(rvalue, &rr->partition_type);
+ r = gpt_partition_type_from_string(rvalue, &rr->partition_type);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed parse partition type, ignoring: %s", rvalue);
if (t->target.n_empty + t->target.n_instances < 2)
return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
"Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), refusing.",
- SD_ID128_FORMAT_VAL(t->target.partition_type),
- gpt_partition_type_uuid_to_string(t->target.partition_type));
+ SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
+ gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
if (space > t->target.n_empty + t->target.n_instances)
return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
"Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s) for operation.",
- SD_ID128_FORMAT_VAL(t->target.partition_type),
- gpt_partition_type_uuid_to_string(t->target.partition_type));
+ SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
+ gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
if (space == t->target.n_empty + t->target.n_instances)
return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
"Asked to empty all partition table slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), can't allow that. One instance must always remain.",
- SD_ID128_FORMAT_VAL(t->target.partition_type),
- gpt_partition_type_uuid_to_string(t->target.partition_type));
+ SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
+ gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
rm = LESS_BY(space, t->target.n_empty);
remain = LESS_BY(t->target.n_instances, rm);
r = find_suitable_partition(
t->target.path,
i->metadata.size,
- t->target.partition_type_set ? &t->target.partition_type : NULL,
+ t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
&t->partition_info);
if (r < 0)
return r;
[files('test-strv.c')],
+ [files('test-nulstr-util.c')],
+
[files('test-path-util.c')],
[files('test-rm-rf.c')],
else
arg_start = getpid_cached();
- const char *i;
NULSTR_FOREACH(i, "zeros\0simple\0random\0") {
#if HAVE_XZ
test_compress_decompress("XZ", i, compress_blob_xz, decompress_blob_xz);
TEST(condition_test_virtualization) {
Condition *condition;
- const char *virt;
int r;
condition = condition_new(CONDITION_VIRTUALIZATION, "garbage oifdsjfoidsjoj", false, false);
/* The file exists- now overwrite original contents, and test the COPY_REPLACE flag. */
- assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK) == -EEXIST);
+ assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK, NULL) == -EEXIST);
assert_se(read_file_at_and_streq(AT_FDCWD, dst, "foo foo foo\n"));
- assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE) == 0);
+ assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE, NULL) == 0);
assert_se(read_file_at_and_streq(AT_FDCWD, dst, "bar bar\n"));
}
TEST(copy_tree_replace_dirs) {
- _cleanup_(rm_rf_physical_and_closep) int src = -1, dst = -1;
+ _cleanup_(rm_rf_physical_and_freep) char *srcp = NULL, *dstp = NULL;
+ _cleanup_close_ int src = -1, dst = -1;
/* Create the random source/destination directories */
- assert_se((src = mkdtemp_open(NULL, 0, NULL)) >= 0);
- assert_se((dst = mkdtemp_open(NULL, 0, NULL)) >= 0);
+ assert_se((src = mkdtemp_open(NULL, 0, &srcp)) >= 0);
+ assert_se((dst = mkdtemp_open(NULL, 0, &dstp)) >= 0);
/* Populate some data to differentiate the files. */
assert_se(write_string_file_at(src, "foo", "src file 1", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(dst, "bar", "dest file 2", WRITE_STRING_FILE_CREATE) == 0);
/* Copying without COPY_REPLACE should fail because the destination file already exists. */
- assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK) == -EEXIST);
+ assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK, NULL) == -EEXIST);
assert_se(read_file_at_and_streq(src, "foo", "src file 1\n"));
assert_se(read_file_at_and_streq(src, "bar", "src file 2\n"));
assert_se(read_file_at_and_streq(dst, "foo", "dest file 1\n"));
assert_se(read_file_at_and_streq(dst, "bar", "dest file 2\n"));
- assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE) == 0);
+ assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE, NULL) == 0);
assert_se(read_file_at_and_streq(src, "foo", "src file 1\n"));
assert_se(read_file_at_and_streq(src, "bar", "src file 2\n"));
}
TEST(copy_tree) {
+ _cleanup_set_free_ Set *denylist = NULL;
+ _cleanup_free_ char *cp = NULL;
char original_dir[] = "/tmp/test-copy_tree/";
char copy_dir[] = "/tmp/test-copy_tree-copy/";
char **files = STRV_MAKE("file", "dir1/file", "dir1/dir2/file", "dir1/dir2/dir3/dir4/dir5/file");
"link2", "dir1/file");
char **hardlinks = STRV_MAKE("hlink", "file",
"hlink2", "dir1/file");
- const char *unixsockp;
+ const char *unixsockp, *ignorep;
struct stat st;
int xattr_worked = -1; /* xattr support is optional in temporary directories, hence use it if we can,
* but don't fail if we can't */
unixsockp = strjoina(original_dir, "unixsock");
assert_se(mknod(unixsockp, S_IFSOCK|0644, 0) >= 0);
- assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS) == 0);
+ ignorep = strjoina(original_dir, "ignore/file");
+ assert_se(write_string_file(ignorep, "ignore", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) == 0);
+ assert_se(RET_NERRNO(stat(ignorep, &st)) >= 0);
+ assert_se(cp = memdup(&st, sizeof(st)));
+ assert_se(set_ensure_put(&denylist, &inode_hash_ops, cp) >= 0);
+ TAKE_PTR(cp);
+
+ assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS, denylist) == 0);
STRV_FOREACH(p, files) {
_cleanup_free_ char *buf, *f, *c = NULL;
assert_se(stat(unixsockp, &st) >= 0);
assert_se(S_ISSOCK(st.st_mode));
- assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK) < 0);
- assert_se(copy_tree("/tmp/inexistent/foo/bar/fsdoi", copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK) < 0);
+ assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist) < 0);
+ assert_se(copy_tree("/tmp/inexistent/foo/bar/fsdoi", copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist) < 0);
+
+ ignorep = strjoina(copy_dir, "ignore/file");
+ assert_se(RET_NERRNO(access(ignorep, F_OK)) == -ENOENT);
(void) rm_rf(copy_dir, REMOVE_ROOT|REMOVE_PHYSICAL);
(void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL);
"5min 5min \n"));
}
+TEST(vertical) {
+ _cleanup_(table_unrefp) Table *t = NULL;
+ _cleanup_free_ char *formatted = NULL;
+
+ assert_se(t = table_new_vertical());
+
+ assert_se(table_add_many(t,
+ TABLE_FIELD, "pfft aa", TABLE_STRING, "foo",
+ TABLE_FIELD, "uuu o", TABLE_SIZE, UINT64_C(1024),
+ TABLE_FIELD, "lllllllllllo", TABLE_STRING, "jjjjjjjjjjjjjjjjj") >= 0);
+
+ assert_se(table_set_json_field_name(t, 1, "dimpfelmoser") >= 0);
+
+ assert_se(table_format(t, &formatted) >= 0);
+
+ assert_se(streq(formatted,
+ " pfft aa: foo\n"
+ " uuu o: 1.0K\n"
+ "lllllllllllo: jjjjjjjjjjjjjjjjj\n"));
+
+ _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL;
+ assert_se(table_to_json(t, &a) >= 0);
+
+ assert_se(json_build(&b, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("pfft_aa", JSON_BUILD_STRING("foo")),
+ JSON_BUILD_PAIR("dimpfelmoser", JSON_BUILD_UNSIGNED(1024)),
+ JSON_BUILD_PAIR("lllllllllllo", JSON_BUILD_STRING("jjjjjjjjjjjjjjjjj")))) >= 0);
+
+ assert_se(json_variant_equal(a, b));
+}
+
static int intro(void) {
assert_se(setenv("SYSTEMD_COLORS", "0", 1) >= 0);
assert_se(setenv("COLUMNS", "40", 1) >= 0);
assert_se(path_equal(path_startswith(result, p), "usr"));
result = mfree(result);
+ /* Test CHASE_PROHIBIT_SYMLINKS */
+
+ assert_se(chase_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+ assert_se(chase_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+ assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+ assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+ assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+ assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+
cleanup:
assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
}
for (Architecture a = 0; a < _ARCHITECTURE_MAX; a++)
FOREACH_STRING(suffix, "", "-verity", "-verity-sig") {
_cleanup_free_ char *joined = NULL;
- sd_id128_t id;
+ GptPartitionType type;
joined = strjoin(prefix, architecture_to_string(a), suffix);
if (!joined)
return (void) log_oom();
- r = gpt_partition_type_uuid_from_string(joined, &id);
+ r = gpt_partition_type_from_string(joined, &type);
if (r < 0) {
printf("%s %s\n", RED_CROSS_MARK(), joined);
continue;
printf("%s %s\n", GREEN_CHECK_MARK(), joined);
if (streq(prefix, "root-") && streq(suffix, ""))
- assert_se(gpt_partition_type_is_root(id));
+ assert_se(type.designator == PARTITION_ROOT);
if (streq(prefix, "root-") && streq(suffix, "-verity"))
- assert_se(gpt_partition_type_is_root_verity(id));
+ assert_se(type.designator == PARTITION_ROOT_VERITY);
if (streq(prefix, "usr-") && streq(suffix, ""))
- assert_se(gpt_partition_type_is_usr(id));
+ assert_se(type.designator == PARTITION_USR);
if (streq(prefix, "usr-") && streq(suffix, "-verity"))
- assert_se(gpt_partition_type_is_usr_verity(id));
+ assert_se(type.designator == PARTITION_USR_VERITY);
- assert_se(gpt_partition_type_uuid_to_arch(id) == a);
+ assert_se(type.arch == a);
}
}
m = hashmap_new(&string_hash_ops);
- NULSTR_FOREACH(key, key_table)
- hashmap_put(m, key, (void*) (const char*) "my dummy val");
+ NULSTR_FOREACH(k, key_table)
+ hashmap_put(m, k, (void*) (const char*) "my dummy val");
HASHMAP_FOREACH_KEY(s, key, m) {
assert_se(s);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "nulstr-util.h"
+#include "strv.h"
+#include "tests.h"
+
+TEST(strv_split_nulstr) {
+ _cleanup_strv_free_ char **l = NULL;
+ const char nulstr[] = "str0\0str1\0str2\0str3\0";
+
+ l = strv_split_nulstr(nulstr);
+ assert_se(l);
+
+ assert_se(streq(l[0], "str0"));
+ assert_se(streq(l[1], "str1"));
+ assert_se(streq(l[2], "str2"));
+ assert_se(streq(l[3], "str3"));
+}
+
+TEST(strv_parse_nulstr) {
+ _cleanup_strv_free_ char **l = NULL;
+ const char nulstr[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx";
+
+ l = strv_parse_nulstr(nulstr, sizeof(nulstr)-1);
+ assert_se(l);
+ puts("Parse nulstr:");
+ strv_print(l);
+
+ assert_se(streq(l[0], "hoge"));
+ assert_se(streq(l[1], "hoge2"));
+ assert_se(streq(l[2], "hoge3"));
+ assert_se(streq(l[3], ""));
+ assert_se(streq(l[4], "hoge5"));
+ assert_se(streq(l[5], ""));
+ assert_se(streq(l[6], "xxx"));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[0]) {}, 0);
+ assert_se(l);
+ assert_se(strv_isempty(l));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[1]) { 0 }, 1);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[1]) { 'x' }, 1);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("x")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[2]) { 0, 0 }, 2);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("", "")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[2]) { 'x', 0 }, 2);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("x")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 0, 0, 0 }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("", "", "")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 'x', 0, 0 }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("x", "")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 0, 'x', 0 }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("", "x")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 0, 0, 'x' }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("", "", "x")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 'x', 'x', 0 }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("xx")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 0, 'x', 'x' }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("", "xx")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 'x', 0, 'x' }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("x", "x")));
+ strv_free(l);
+
+ l = strv_parse_nulstr((const char[3]) { 'x', 'x', 'x' }, 3);
+ assert_se(l);
+ assert_se(strv_equal(l, STRV_MAKE("xxx")));
+}
+
+static void test_strv_make_nulstr_one(char **l) {
+ _cleanup_free_ char *b = NULL, *c = NULL;
+ _cleanup_strv_free_ char **q = NULL;
+ size_t n, m;
+ unsigned i = 0;
+
+ log_info("/* %s */", __func__);
+
+ assert_se(strv_make_nulstr(l, &b, &n) >= 0);
+ assert_se(q = strv_parse_nulstr(b, n));
+ assert_se(strv_equal(l, q));
+
+ assert_se(strv_make_nulstr(q, &c, &m) >= 0);
+ assert_se(memcmp_nn(b, n, c, m) == 0);
+
+ NULSTR_FOREACH(s, b)
+ assert_se(streq(s, l[i++]));
+ assert_se(i == strv_length(l));
+}
+
+TEST(strv_make_nulstr) {
+ test_strv_make_nulstr_one(NULL);
+ test_strv_make_nulstr_one(STRV_MAKE(NULL));
+ test_strv_make_nulstr_one(STRV_MAKE("foo"));
+ test_strv_make_nulstr_one(STRV_MAKE("foo", "bar"));
+ test_strv_make_nulstr_one(STRV_MAKE("foo", "bar", "quuux"));
+}
+
+static void test_strv_make_nulstr_binary_one(char **l, const char *b, size_t n) {
+ _cleanup_strv_free_ char **z = NULL;
+ _cleanup_free_ char *a = NULL;
+ size_t m;
+
+ assert_se(strv_make_nulstr(l, &a, &m) >= 0);
+ assert_se(memcmp_nn(a, m, b, n) == 0);
+ assert_se(z = strv_parse_nulstr(a, m));
+ assert_se(strv_equal(l, z));
+}
+
+TEST(strv_make_nulstr_binary) {
+ test_strv_make_nulstr_binary_one(NULL, (const char[0]) {}, 0);
+ test_strv_make_nulstr_binary_one(STRV_MAKE(NULL), (const char[0]) {}, 0);
+ test_strv_make_nulstr_binary_one(STRV_MAKE(""), (const char[1]) { 0 }, 1);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("", ""), (const char[2]) { 0, 0 }, 2);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("x", ""), (const char[3]) { 'x', 0, 0 }, 3);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("", "x"), (const char[3]) { 0, 'x', 0 }, 3);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("", "", ""), (const char[3]) { 0, 0, 0 }, 3);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("x", "", ""), (const char[4]) { 'x', 0, 0, 0 }, 4);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("", "x", ""), (const char[4]) { 0, 'x', 0, 0 }, 4);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("", "", "x"), (const char[4]) { 0, 0, 'x', 0 }, 4);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("x", "x", ""), (const char[5]) { 'x', 0, 'x', 0, 0 }, 5);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("", "x", "x"), (const char[5]) { 0, 'x', 0, 'x', 0 }, 5);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("x", "", "x"), (const char[5]) { 'x', 0, 0, 'x', 0 }, 5);
+ test_strv_make_nulstr_binary_one(STRV_MAKE("x", "x", "x"), (const char[6]) { 'x', 0, 'x', 0, 'x', 0 }, 6);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
TEST(sd_hwdb_new_from_path) {
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
- const char *hwdb_bin_path = NULL;
int r;
assert_se(sd_hwdb_new_from_path(NULL, &hwdb) == -EINVAL);
}
TEST(architecture_table) {
- const char *n, *n2;
+ const char *n2;
NULSTR_FOREACH(n,
"native\0"
assert_se(streq(syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].name, "@known"));
for (size_t i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
- const char *k, *p = NULL;
+ const char *p = NULL;
/* Make sure each group has a description */
assert_se(!isempty(syscall_filter_sets[0].help));
#include <stdlib.h>
+#include "nulstr-util.h"
#include "strbuf.h"
#include "string-util.h"
#include "strv.h"
#include "alloc-util.h"
#include "escape.h"
-#include "nulstr-util.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
assert_se(strv_equal(l, (char**) input_table_retain_escape));
}
-TEST(strv_split_nulstr) {
- _cleanup_strv_free_ char **l = NULL;
- const char nulstr[] = "str0\0str1\0str2\0str3\0";
-
- l = strv_split_nulstr (nulstr);
- assert_se(l);
-
- assert_se(streq(l[0], "str0"));
- assert_se(streq(l[1], "str1"));
- assert_se(streq(l[2], "str2"));
- assert_se(streq(l[3], "str3"));
-}
-
-TEST(strv_parse_nulstr) {
- _cleanup_strv_free_ char **l = NULL;
- const char nulstr[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx";
-
- l = strv_parse_nulstr(nulstr, sizeof(nulstr)-1);
- assert_se(l);
- puts("Parse nulstr:");
- strv_print(l);
-
- assert_se(streq(l[0], "hoge"));
- assert_se(streq(l[1], "hoge2"));
- assert_se(streq(l[2], "hoge3"));
- assert_se(streq(l[3], ""));
- assert_se(streq(l[4], "hoge5"));
- assert_se(streq(l[5], ""));
- assert_se(streq(l[6], "xxx"));
-}
-
TEST(strv_overlap) {
const char * const input_table[] = {
"one",
assert_se(v[1] == NULL);
}
-static void test_strv_make_nulstr_one(char **l) {
- _cleanup_free_ char *b = NULL, *c = NULL;
- _cleanup_strv_free_ char **q = NULL;
- const char *s = NULL;
- size_t n, m;
- unsigned i = 0;
-
- log_info("/* %s */", __func__);
-
- assert_se(strv_make_nulstr(l, &b, &n) >= 0);
- assert_se(q = strv_parse_nulstr(b, n));
- assert_se(strv_equal(l, q));
-
- assert_se(strv_make_nulstr(q, &c, &m) >= 0);
- assert_se(m == n);
- assert_se(memcmp(b, c, m) == 0);
-
- NULSTR_FOREACH(s, b)
- assert_se(streq(s, l[i++]));
- assert_se(i == strv_length(l));
-}
-
-TEST(strv_make_nulstr) {
- test_strv_make_nulstr_one(NULL);
- test_strv_make_nulstr_one(STRV_MAKE(NULL));
- test_strv_make_nulstr_one(STRV_MAKE("foo"));
- test_strv_make_nulstr_one(STRV_MAKE("foo", "bar"));
- test_strv_make_nulstr_one(STRV_MAKE("foo", "bar", "quuux"));
-}
-
TEST(foreach_string) {
const char * const t[] = {
"foo",
assert_se(uid_range_covers(p, 0, UINT32_MAX));
}
- assert_se(fopen_temporary(NULL, &f, &fn) >= 0);
+ assert_se(fopen_temporary_child(NULL, &f, &fn) >= 0);
fputs("0 0 20\n"
"100 0 20\n", f);
assert_se(fflush_and_check(f) >= 0);
assert(i);
- table = table_new("key", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
assert_se(cell = table_get_cell(table, 0, 0));
(void) table_set_ellipsize_percent(table, cell, 100);
- (void) table_set_align_percent(table, cell, 100);
assert_se(cell = table_get_cell(table, 0, 1));
(void) table_set_ellipsize_percent(table, cell, 100);
n = have_time ? strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) : 0;
r = table_add_many(table,
- TABLE_STRING, "Local time:",
+ TABLE_FIELD, "Local time",
TABLE_STRING, n > 0 ? a : "n/a");
if (r < 0)
return table_log_add_error(r);
n = have_time ? strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) : 0;
r = table_add_many(table,
- TABLE_STRING, "Universal time:",
+ TABLE_FIELD, "Universal time",
TABLE_STRING, n > 0 ? a : "n/a");
if (r < 0)
return table_log_add_error(r);
} else
n = 0;
r = table_add_many(table,
- TABLE_STRING, "RTC time:",
+ TABLE_FIELD, "RTC time",
TABLE_STRING, n > 0 ? a : "n/a");
if (r < 0)
return table_log_add_error(r);
- r = table_add_cell(table, NULL, TABLE_STRING, "Time zone:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Time zone");
if (r < 0)
return table_log_add_error(r);
tzset();
r = table_add_many(table,
- TABLE_STRING, "System clock synchronized:",
+ TABLE_FIELD, "System clock synchronized",
TABLE_BOOLEAN, i->ntp_synced,
- TABLE_STRING, "NTP service:",
+ TABLE_FIELD, "NTP service",
TABLE_STRING, i->ntp_capable ? (i->ntp_active ? "active" : "inactive") : "n/a",
- TABLE_STRING, "RTC in local TZ:",
+ TABLE_FIELD, "RTC in local TZ",
TABLE_BOOLEAN, i->rtc_local);
if (r < 0)
return table_log_add_error(r);
assert(i);
- table = table_new("key", "value");
+ table = table_new_vertical();
if (!table)
return log_oom();
- table_set_header(table, false);
-
assert_se(cell = table_get_cell(table, 0, 0));
(void) table_set_ellipsize_percent(table, cell, 100);
- (void) table_set_align_percent(table, cell, 100);
assert_se(cell = table_get_cell(table, 0, 1));
(void) table_set_ellipsize_percent(table, cell, 100);
* d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2"
*/
- r = table_add_cell(table, NULL, TABLE_STRING, "Server:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Server");
if (r < 0)
return table_log_add_error(r);
if (r < 0)
return table_log_add_error(r);
- r = table_add_cell(table, NULL, TABLE_STRING, "Poll interval:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Poll interval");
if (r < 0)
return table_log_add_error(r);
if (i->packet_count == 0) {
r = table_add_many(table,
- TABLE_STRING, "Packet count:",
+ TABLE_FIELD, "Packet count",
TABLE_STRING, "0");
if (r < 0)
return table_log_add_error(r);
root_distance = i->root_delay / 2 + i->root_dispersion;
r = table_add_many(table,
- TABLE_STRING, "Leap:",
+ TABLE_FIELD, "Leap",
TABLE_STRING, ntp_leap_to_string(i->leap),
- TABLE_STRING, "Version:",
+ TABLE_FIELD, "Version",
TABLE_UINT32, i->version,
- TABLE_STRING, "Stratum:",
+ TABLE_FIELD, "Stratum",
TABLE_UINT32, i->stratum,
- TABLE_STRING, "Reference:");
+ TABLE_FIELD, "Reference");
if (r < 0)
return table_log_add_error(r);
if (r < 0)
return table_log_add_error(r);
- r = table_add_cell(table, NULL, TABLE_STRING, "Precision:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Precision");
if (r < 0)
return table_log_add_error(r);
if (r < 0)
return table_log_add_error(r);
- r = table_add_cell(table, NULL, TABLE_STRING, "Root distance:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Root distance");
if (r < 0)
return table_log_add_error(r);
if (r < 0)
return table_log_add_error(r);
- r = table_add_cell(table, NULL, TABLE_STRING, "Offset:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Offset");
if (r < 0)
return table_log_add_error(r);
return table_log_add_error(r);
r = table_add_many(table,
- TABLE_STRING, "Delay:",
+ TABLE_FIELD, "Delay",
TABLE_STRING, FORMAT_TIMESPAN(delay, 0),
- TABLE_STRING, "Jitter:",
+ TABLE_FIELD, "Jitter",
TABLE_STRING, FORMAT_TIMESPAN(i->jitter, 0),
- TABLE_STRING, "Packet count:",
+ TABLE_FIELD, "Packet count",
TABLE_UINT64, i->packet_count);
if (r < 0)
return table_log_add_error(r);
if (!i->spike) {
- r = table_add_cell(table, NULL, TABLE_STRING, "Frequency:");
+ r = table_add_cell(table, NULL, TABLE_FIELD, "Frequency");
if (r < 0)
return table_log_add_error(r);
#include "mkdir-label.h"
#include "mount-util.h"
#include "mountpoint-util.h"
+#include "nulstr-util.h"
#include "offline-passwd.h"
#include "pager.h"
#include "parse-argument.h"
return label_fix_full(fd, /* inode_path= */ NULL, /* label_path= */ path, 0);
}
-static int path_open_parent_safe(const char *path) {
+static int path_open_parent_safe(const char *path, bool allow_failure) {
_cleanup_free_ char *dn = NULL;
int r, fd;
if (!path_is_normalized(path))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to open parent of '%s': path not normalized.", path);
+ return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
+ SYNTHETIC_ERRNO(EINVAL),
+ "Failed to open parent of '%s': path not normalized%s.",
+ path,
+ allow_failure ? ", ignoring" : "");
r = path_extract_directory(path, &dn);
if (r < 0)
- return log_error_errno(r, "Unable to determine parent directory of '%s': %m", path);
+ return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
+ r,
+ "Unable to determine parent directory of '%s'%s: %m",
+ path,
+ allow_failure ? ", ignoring" : "");
- r = chase_symlinks(dn, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd);
+ r = chase_symlinks(dn, arg_root, allow_failure ? CHASE_SAFE : CHASE_SAFE|CHASE_WARN, NULL, &fd);
if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
return r;
if (r < 0)
- return log_error_errno(r, "Failed to open path '%s': %m", dn);
+ return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
+ r,
+ "Failed to open path '%s'%s: %m",
+ dn,
+ allow_failure ? ", ignoring" : "");
return fd;
}
/* Validate the path and keep the fd on the directory for opening the file so we're sure that it
* can't be changed behind our back. */
- dir_fd = path_open_parent_safe(path);
+ dir_fd = path_open_parent_safe(path, i->allow_failure);
if (dir_fd < 0)
return dir_fd;
/* Validate the path and keep the fd on the directory for opening the file so we're sure that it
* can't be changed behind our back. */
- dir_fd = path_open_parent_safe(path);
+ dir_fd = path_open_parent_safe(path, i->allow_failure);
if (dir_fd < 0)
return dir_fd;
/* Validate the path and keep the fd on the directory for opening the file so we're sure that it
* can't be changed behind our back. */
- dir_fd = path_open_parent_safe(path);
+ dir_fd = path_open_parent_safe(path, i->allow_failure);
if (dir_fd < 0)
return dir_fd;
/* Validate the path and use the returned directory fd for copying the target so we're sure that the
* path can't be changed behind our back. */
- dfd = path_open_parent_safe(i->path);
+ dfd = path_open_parent_safe(i->path, i->allow_failure);
if (dfd < 0)
return dfd;
dfd, bn,
i->uid_set ? i->uid : UID_INVALID,
i->gid_set ? i->gid : GID_INVALID,
- COPY_REFLINK | COPY_MERGE_EMPTY | COPY_MAC_CREATE | COPY_HARDLINKS);
+ COPY_REFLINK | COPY_MERGE_EMPTY | COPY_MAC_CREATE | COPY_HARDLINKS,
+ NULL);
fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
if (fd < 0) {
const char *path,
mode_t mode,
bool subvol,
+ bool allow_failure,
struct stat *ret_st,
CreationMode *ret_creation) {
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
- pfd = path_open_parent_safe(path);
+ pfd = path_open_parent_safe(path, allow_failure);
if (pfd < 0)
return pfd;
/* Then look at the original error */
if (r < 0)
- return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", path);
+ return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
+ r,
+ "Failed to create directory or subvolume \"%s\"%s: %m",
+ path,
+ allow_failure ? ", ignoring" : "");
return log_error_errno(errno, "Failed to open directory/subvolume we just created '%s': %m", path);
}
assert(i);
assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY));
- fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, &st, &creation);
+ fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, i->allow_failure, &st, &creation);
if (fd == -EEXIST)
return 0;
if (fd < 0)
assert(i);
assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA));
- fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, &st, &creation);
+ fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, i->allow_failure, &st, &creation);
if (fd == -EEXIST)
return 0;
if (fd < 0)
/* Validate the path and use the returned directory fd for copying the target so we're sure that the
* path can't be changed behind our back. */
- dfd = path_open_parent_safe(i->path);
+ dfd = path_open_parent_safe(i->path, i->allow_failure);
if (dfd < 0)
return dfd;
if (r == O_DIRECTORY)
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path);
- pfd = path_open_parent_safe(i->path);
+ pfd = path_open_parent_safe(i->path, i->allow_failure);
if (pfd < 0)
return pfd;
if (r == O_DIRECTORY)
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path);
- pfd = path_open_parent_safe(i->path);
+ pfd = path_open_parent_safe(i->path, i->allow_failure);
if (pfd < 0)
return pfd;
}
static bool token_match_string(UdevRuleToken *token, const char *str) {
- const char *i, *value;
+ const char *value;
bool match = false;
assert(token);
set -e
TEST_DESCRIPTION="test systemd-repart"
+IMAGE_NAME="repart"
+TEST_FORCE_NEWIMAGE=1
# shellcheck source=test/test-functions
. "$TEST_BASE_DIR/test-functions"
test_append_files() {
if ! get_bool "${TEST_NO_QEMU:=}"; then
install_dmevent
- if command -v openssl >/dev/null 2>&1; then
- inst_binary openssl
- fi
instmods dm_verity =md
generate_module_dependencies
+ image_install -o /sbin/mksquashfs
+ fi
+
+ inst_binary mcopy
+ if command -v openssl >/dev/null 2>&1; then
+ inst_binary openssl
fi
}
MemoryLow=
MemoryMax=
MemorySwapMax=
+MemoryZSwapMax=
MessageQueueMaxMessages=
MessageQueueMessageSize=
MountAPIVFS=
inst_simple "${path}/engines-3/capi.so" || true
inst_simple "${path}/engines-3/loader_attic.so" || true
inst_simple "${path}/engines-3/padlock.so" || true
+
+ # Binaries from mtools depend on the gconv modules to translate between codepages. Because there's no
+ # pkg-config file for these, we copy every gconv/ directory we can find in /usr/lib and /usr/lib64.
+ # shellcheck disable=SC2046
+ inst_recursive $(find /usr/lib* -name gconv 2>/dev/null)
}
cleanup_loopdev() {
root_size=$((4 * root_size))
data_size=$((2 * data_size))
fi
+ if [ "$IMAGE_NAME" = "repart" ]; then
+ root_size=$((root_size+=1000))
+ fi
echo "Setting up ${IMAGE_PUBLIC:?} (${root_size} MB)"
rm -f "${IMAGE_PRIVATE:?}" "$IMAGE_PUBLIC"
set -eux
set -o pipefail
+runas() {
+ declare userid=$1
+ shift
+ # shellcheck disable=SC2016
+ su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+}
+
if ! command -v systemd-repart &>/dev/null; then
echo "no systemd-repart" >/skipped
exit 0
export SYSTEMD_LOG_LEVEL=debug
export PAGER=cat
+# Disable use of special glyphs such as →
+export SYSTEMD_UTF8=0
+
seed=750b6cd5c4ae4012a15e7be3c29e6a47
if ! systemd-detect-virt --quiet --container; then
local defs imgs output
local loop volume
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
# 1. create an empty image
- systemd-repart --empty=create \
- --size=1G \
- --seed="$seed" \
- "$imgs/zzz"
+ runas testuser systemd-repart --empty=create \
+ --size=1G \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
PaddingMinBytes=92M
EOF
- systemd-repart --definitions="$defs" \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ --include-partitions=home,swap \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
+
+ runas testuser systemd-repart --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ --exclude-partitions=root \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
+
+ runas testuser systemd-repart --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
echo "Label=ignored_label" >>"$defs/home.conf"
echo "UUID=b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" >>"$defs/home.conf"
- systemd-repart --definitions="$defs" \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
# 4. Resizing to 2G
- systemd-repart --definitions="$defs" \
- --size=2G \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --size=2G \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
CopyBlocks=$imgs/block-copy
EOF
- systemd-repart --definitions="$defs" \
- --size=3G \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --size=3G \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
cmp --bytes=$((4096*10240)) --ignore-initial=0:$((512*4194264)) "$imgs/block-copy" "$imgs/zzz"
- if systemd-detect-virt --quiet --container; then
- echo "Skipping encrypt tests in container."
- return
- fi
-
# 6. Testing Format=/Encrypt=/CopyFiles=
cat >"$defs/extra3.conf" <<EOF
SizeMinBytes=48M
EOF
- systemd-repart --definitions="$defs" \
- --size=auto \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --size=auto \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
$imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=2A1D97E1-D0A3-46CC-A26E-ADC643926617, name=\"block-copy\"
$imgs/zzz7 : start= 6291416, size= 98304, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=7B93D1F2-595D-4CE3-B0B9-837FBD9E63B0, name=\"luks-format-copy\""
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping encrypt mount tests in container."
+ return
+ fi
+
loop="$(losetup -P --show --find "$imgs/zzz")"
udevadm wait --timeout 60 --settle "${loop:?}"
test_dropin() {
local defs imgs output
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
Label=label2
EOF
- output=$(systemd-repart --definitions="$defs" --empty=create --size=100M --json=pretty "$imgs/zzz")
+ output=$(runas testuser systemd-repart --definitions="$defs" \
+ --empty=create \
+ --size=100M \
+ --json=pretty \
+ "$imgs/zzz")
- diff <(echo "$output") - <<EOF
+ diff -u <(echo "$output") - <<EOF
[
{
"type" : "swap",
"offset" : 1048576,
"old_size" : 0,
"raw_size" : 33554432,
- "size" : "→ 32.0M",
+ "size" : "-> 32.0M",
"old_padding" : 0,
"raw_padding" : 0,
- "padding" : "→ 0B",
+ "padding" : "-> 0B",
"activity" : "create",
"drop-in_files" : [
"$defs/root.conf.d/override1.conf",
test_multiple_definitions() {
local defs imgs output
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
Label=label2
EOF
- output=$(systemd-repart --definitions="$defs/1" --definitions="$defs/2" --empty=create --size=100M --json=pretty "$imgs/zzz")
+ output=$(runas testuser systemd-repart --definitions="$defs/1" \
+ --definitions="$defs/2" \
+ --empty=create \
+ --size=100M \
+ --json=pretty \
+ "$imgs/zzz")
- diff <(echo "$output") - <<EOF
+ diff -u <(echo "$output") - <<EOF
[
{
"type" : "swap",
"offset" : 1048576,
"old_size" : 0,
"raw_size" : 33554432,
- "size" : "→ 32.0M",
+ "size" : "-> 32.0M",
"old_padding" : 0,
"raw_padding" : 0,
- "padding" : "→ 0B",
+ "padding" : "-> 0B",
"activity" : "create"
},
{
"offset" : 34603008,
"old_size" : 0,
"raw_size" : 33554432,
- "size" : "→ 32.0M",
+ "size" : "-> 32.0M",
"old_padding" : 0,
"raw_padding" : 0,
- "padding" : "→ 0B",
+ "padding" : "-> 0B",
"activity" : "create"
}
]
test_copy_blocks() {
local defs imgs output
- if systemd-detect-virt --quiet --container; then
- echo "Skipping copy blocks tests in container."
- return
- fi
-
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
MakeDirectories=/usr /efi
EOF
- systemd-repart --definitions="$defs" \
- --empty=create \
- --size=auto \
- --seed="$seed" \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --empty=create \
+ --size=auto \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 22528, size= 20480, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output"
assert_in "$imgs/zzz3 : start= 43008, size= 20480, type=${usr_guid}, uuid=${usr_uuid}, name=\"usr-${architecture}\", attrs=\"GUID:60\"" "$output"
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping second part of copy blocks tests in container."
+ return
+ fi
+
# Then, create another image with CopyBlocks=auto
cat >"$defs/esp.conf" <<EOF
CopyBlocks=auto
EOF
+ # --image needs root privileges so skip runas testuser here.
systemd-repart --definitions="$defs" \
--empty=create \
--size=auto \
test_unaligned_partition() {
local defs imgs output
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
Type=root-${architecture}
EOF
- truncate -s 10g "$imgs/unaligned"
+ runas testuser truncate -s 10g "$imgs/unaligned"
sfdisk "$imgs/unaligned" <<EOF
label: gpt
start=71092, size=3591848
EOF
- systemd-repart --definitions="$defs" \
- --seed="$seed" \
- --dry-run=no \
- "$imgs/unaligned"
+ runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/unaligned"
output=$(sfdisk --dump "$imgs/unaligned")
# testcase for #21817
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
Type=root
EOF
- truncate -s 100m "$imgs/21817.img"
+ runas testuser truncate -s 100m "$imgs/21817.img"
sfdisk "$imgs/21817.img" <<EOF
label: gpt
,
EOF
- systemd-repart --pretty=yes \
- --definitions "$imgs" \
- --seed="$seed" \
- --dry-run=no \
- "$imgs/21817.img"
+ runas testuser systemd-repart --pretty=yes \
+ --definitions "$imgs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/21817.img"
output=$(sfdisk --dump "$imgs/21817.img")
# testcase for #24553
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
EOF
# 1. Operate on a small image compared with SizeMinBytes=.
- truncate -s 8g "$imgs/zzz"
+ runas testuser truncate -s 8g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should fail, but not trigger assertions.
- assert_rc 1 systemd-repart --definitions="$defs" \
- --seed="$seed" \
- --dry-run=no \
- "$imgs/zzz"
+ assert_rc 1 runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 14848000, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
# 2. Operate on an larger image compared with SizeMinBytes=.
rm -f "$imgs/zzz"
- truncate -s 12g "$imgs/zzz"
+ runas testuser truncate -s 12g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should succeed.
- systemd-repart --definitions="$defs" \
- --seed="$seed" \
- --dry-run=no \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 24641456, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
EOF
rm -f "$imgs/zzz"
- truncate -s 8g "$imgs/zzz"
+ runas testuser truncate -s 8g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should also succeed, but root is not extended.
- systemd-repart --definitions="$defs" \
- --seed="$seed" \
- --dry-run=no \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 14848000, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
# 4. Multiple partitions with Priority= (large disk)
rm -f "$imgs/zzz"
- truncate -s 12g "$imgs/zzz"
+ runas testuser truncate -s 12g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should also succeed, and root is extended.
- systemd-repart --definitions="$defs" \
- --seed="$seed" \
- --dry-run=no \
- "$imgs/zzz"
+ runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 20971520, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
test_zero_uuid() {
local defs imgs output
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
UUID=null
EOF
- systemd-repart --definitions="$defs" \
- --seed="$seed" \
- --dry-run=no \
- --empty=create \
- --size=auto \
- "$imgs/zero"
+ runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=create \
+ --size=auto \
+ "$imgs/zero"
output=$(sfdisk --dump "$imgs/zero")
test_verity() {
local defs imgs output
- if systemd-detect-virt --quiet --container; then
- echo "Skipping verity test in container."
- return
- fi
-
- defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
- imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
emailAddress = test@email.com
EOF
- openssl req -config "$defs/verity.openssl.cnf" -new -x509 -newkey rsa:1024 -keyout "$defs/verity.key" -out "$defs/verity.crt" -days 365 -nodes
+ runas testuser openssl req -config "$defs/verity.openssl.cnf" \
+ -new -x509 \
+ -newkey rsa:1024 \
+ -keyout "$defs/verity.key" \
+ -out "$defs/verity.crt" \
+ -days 365 \
+ -nodes
mkdir -p /run/verity.d
ln -s "$defs/verity.crt" /run/verity.d/ok.crt
+ output=$(runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=create \
+ --size=auto \
+ --json=pretty \
+ --private-key="$defs/verity.key" \
+ --certificate="$defs/verity.crt" \
+ "$imgs/verity")
+
+ roothash=$(jq -r ".[] | select(.type == \"root-${architecture}-verity\") | .roothash" <<< "$output")
+
+ # Check that we can dissect, mount and unmount a repart verity image. (and that the image UUID is deterministic)
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping verity test dissect part in container."
+ return
+ fi
+
+ systemd-dissect "$imgs/verity" --root-hash "$roothash"
+ systemd-dissect "$imgs/verity" --root-hash "$roothash" --json=short | grep -q '"imageUuid":"1d2ce291-7cce-4f7d-bc83-fdb49ad74ebd"'
+ systemd-dissect "$imgs/verity" --root-hash "$roothash" -M "$imgs/mnt"
+ systemd-dissect -U "$imgs/mnt"
+}
+
+test_issue_24786() {
+ local defs imgs root output
+
+ defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ root="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs' '$root'" RETURN
+
+ touch "$root/abc"
+ mkdir "$root/usr"
+ touch "$root/usr/def"
+
+ cat >"$defs/00-root.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+CopyFiles=/
+EOF
+
+ cat >"$defs/10-usr.conf" <<EOF
+[Partition]
+Type=usr-${architecture}
+CopyFiles=/usr:/
+EOF
+
+ output=$(runas testuser systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=create \
+ --size=auto \
+ --json=pretty \
+ --root="$root" \
+ "$imgs/zzz")
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping issue 24786 test loop/mount parts in container."
+ return
+ fi
+
+ loop=$(losetup -P --show -f "$imgs/zzz")
+ udevadm wait --timeout 60 --settle "${loop:?}"
+
+ mkdir "$imgs/mnt"
+ mount -t ext4 "${loop}p1" "$imgs/mnt"
+ assert_rc 0 ls "$imgs/mnt/abc"
+ assert_rc 2 ls "$imgs/mnt/usr"
+ mkdir "$imgs/mnt/usr"
+ mount -t ext4 "${loop}p2" "$imgs/mnt/usr"
+ assert_rc 0 ls "$imgs/mnt/usr/def"
+
+ umount -R "$imgs/mnt"
+ losetup -d "$loop"
+}
+
+test_minimize() {
+ local defs imgs output
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping minimize test in container."
+ return
+ fi
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ for format in ext4 vfat; do
+ if ! command -v "mkfs.$format" >/dev/null; then
+ continue
+ fi
+
+ cat >"$defs/root-$format.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+Format=${format}
+CopyFiles=${defs}
+Minimize=yes
+EOF
+ done
+
+ if ! command -v mksquashfs >/dev/null; then
+ cat >"$defs/root-squashfs.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+Format=squashfs
+CopyFiles=${defs}
+Minimize=yes
+EOF
+ fi
+
output=$(systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
--empty=create \
--size=auto \
--json=pretty \
- --private-key="$defs/verity.key" \
- --certificate="$defs/verity.crt" \
- "$imgs/verity")
-
- roothash=$(jq -r ".[] | select(.type == \"root-${architecture}-verity\") | .roothash" <<< "$output")
+ "$imgs/zzz")
- # Check that we can dissect, mount and unmount a repart verity image.
+ # Check that we can dissect, mount and unmount a minimized image.
- systemd-dissect "$imgs/verity" --root-hash "$roothash"
- systemd-dissect "$imgs/verity" --root-hash "$roothash" -M "$imgs/mnt"
+ systemd-dissect "$imgs/zzz"
+ systemd-dissect "$imgs/zzz" -M "$imgs/mnt"
systemd-dissect -U "$imgs/mnt"
}
truncate -s 100m "$imgs/$sector.img"
loop=$(losetup -b "$sector" -P --show -f "$imgs/$sector.img" )
udevadm wait --timeout 60 --settle "${loop:?}"
+ # This operates on a loop device which we don't support doing without root privileges so we skip runas
+ # here.
systemd-repart --pretty=yes \
--definitions="$defs" \
--seed="$seed" \
test_issue_24553
test_zero_uuid
test_verity
+test_issue_24786
+test_minimize
# Valid block sizes on the Linux block layer are >= 512 and <= PAGE_SIZE, and
# must be powers of 2. Which leaves exactly four different ones to test on
echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -a hoge.foo.dhcp
assert_in '10.0.3.1 10.0.3.2' "$(resolvectl dns hoge)"
assert_in '10.0.3.3 10.0.3.4' "$(resolvectl dns hoge.foo)"
+
+# Tests for mDNS and LLMNR settings
+mkdir -p /run/systemd/resolved.conf.d
+{
+ echo "[Resolve]"
+ echo "MulticastDNS=yes"
+ echo "LLMNR=yes"
+} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+systemctl restart systemd-resolved.service
+systemctl service-log-level systemd-resolved.service debug
+# make sure networkd is not running.
+systemctl stop systemd-networkd.service
+# defaults to yes (both the global and per-link settings are yes)
+assert_in 'yes' "$(resolvectl mdns hoge)"
+assert_in 'yes' "$(resolvectl llmnr hoge)"
+# set per-link setting
+resolvectl mdns hoge yes
+resolvectl llmnr hoge yes
+assert_in 'yes' "$(resolvectl mdns hoge)"
+assert_in 'yes' "$(resolvectl llmnr hoge)"
+resolvectl mdns hoge resolve
+resolvectl llmnr hoge resolve
+assert_in 'resolve' "$(resolvectl mdns hoge)"
+assert_in 'resolve' "$(resolvectl llmnr hoge)"
+resolvectl mdns hoge no
+resolvectl llmnr hoge no
+assert_in 'no' "$(resolvectl mdns hoge)"
+assert_in 'no' "$(resolvectl llmnr hoge)"
+# downgrade global setting to resolve
+{
+ echo "[Resolve]"
+ echo "MulticastDNS=resolve"
+ echo "LLMNR=resolve"
+} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+systemctl restart systemd-resolved.service
+systemctl service-log-level systemd-resolved.service debug
+# set per-link setting
+resolvectl mdns hoge yes
+resolvectl llmnr hoge yes
+assert_in 'resolve' "$(resolvectl mdns hoge)"
+assert_in 'resolve' "$(resolvectl llmnr hoge)"
+resolvectl mdns hoge resolve
+resolvectl llmnr hoge resolve
+assert_in 'resolve' "$(resolvectl mdns hoge)"
+assert_in 'resolve' "$(resolvectl llmnr hoge)"
+resolvectl mdns hoge no
+resolvectl llmnr hoge no
+assert_in 'no' "$(resolvectl mdns hoge)"
+assert_in 'no' "$(resolvectl llmnr hoge)"
+# downgrade global setting to no
+{
+ echo "[Resolve]"
+ echo "MulticastDNS=no"
+ echo "LLMNR=no"
+} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+systemctl restart systemd-resolved.service
+systemctl service-log-level systemd-resolved.service debug
+# set per-link setting
+resolvectl mdns hoge yes
+resolvectl llmnr hoge yes
+assert_in 'no' "$(resolvectl mdns hoge)"
+assert_in 'no' "$(resolvectl llmnr hoge)"
+resolvectl mdns hoge resolve
+resolvectl llmnr hoge resolve
+assert_in 'no' "$(resolvectl mdns hoge)"
+assert_in 'no' "$(resolvectl llmnr hoge)"
+resolvectl mdns hoge no
+resolvectl llmnr hoge no
+assert_in 'no' "$(resolvectl mdns hoge)"
+assert_in 'no' "$(resolvectl llmnr hoge)"
+
+# Cleanup
+rm -f /run/systemd/resolved.conf.d/mdns-llmnr.conf
ip link del hoge
ip link del hoge.foo
DNS=10.0.0.1
EOF
+mkdir -p /run/systemd/resolved.conf.d
{
+ echo "[Resolve]"
echo "FallbackDNS="
echo "DNSSEC=allow-downgrade"
echo "DNSOverTLS=opportunistic"
-} >>/etc/systemd/resolved.conf
+} >/run/systemd/resolved.conf.d/test.conf
ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
# Override the default NTA list, which turns off DNSSEC validation for (among
# others) the test. domain
# Only run this if there is no system token defined yet, or …
ConditionPathExists=|!/sys/firmware/efi/efivars/LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
-# … if the boot loader didn't pass the OS a random seed (and thus probably was missing the random seed file)
-ConditionPathExists=|!/sys/firmware/efi/efivars/LoaderRandomSeed-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
-
[Service]
Type=oneshot
RemainAfterExit=yes