if ! AR="$AR" \
CC="$CC" CC_LD="$LD" CFLAGS="-Werror" \
CXX="$CXX" CXX_LD="$LD" CXXFLAGS="-Werror" \
- meson -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true --werror \
+ meson setup \
+ -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true --werror \
-Dnobody-group=nogroup -Dcryptolib="${CRYPTOLIB:?}" \
$args build; then
fi
if ! meson compile -C build -v; then
- fatal "'meson compile' failed with $args"
+ fatal "'meson compile' failed with '$args'"
fi
for loader in build/src/boot/efi/*.efi; do
git clean -dxf
- success "Build with $args passed in $SECONDS seconds"
+ success "Build with '$args' passed in $SECONDS seconds"
done
pull-requests: write
steps:
- uses: actions/labeler@e54e5b338fbd6e6cdb5d60f51c22335fc57c401e
- if: github.event_name == 'pull_request_target'
+ if: github.event_name == 'pull_request_target' && github.event.action != 'closed'
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: .github/labeler.yml
sync-labels: "" # This is a workaround for issue 18671
- uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0
- if: github.event_name == 'pull_request_target' && !github.event.pull_request.draft
+ if: github.event_name == 'pull_request_target' && github.event.action != 'closed' && !github.event.pull_request.draft
with:
script: |
response = await github.rest.issues.listLabelsOnIssue({
})
- uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0
- if: github.event_name == 'pull_request_target' && github.event.issue.pull_request && github.event.pull_request.merged == true
+ if: github.event_name == 'pull_request_target' && github.event.action == 'closed' && github.event.issue.pull_request
with:
script: |
for (const label of ["please-review",
release: rawhide
- distro: opensuse
release: tumbleweed
- - distro: centos_epel
+ - distro: centos
release: 9-stream
- - distro: centos_epel
+ - distro: centos
release: 8-stream
steps:
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
- - uses: systemd/mkosi@c9772ec920f1cd03181ba14e6fe2c3d35ccb4f92
-
- # FIXME: temporary workaround for a file conflict between systemd (C9S) and
- # systemd-boot (EPEL9). Drop this once systemd in C9S is updated to v252
- # (should be done by the end of 2022).
- - name: Fix C9S/EPEL9
- if: ${{ matrix.release == '9-stream' }}
- run: sudo sed -i '/add_packages/s/systemd-boot/systemd/g' /usr/local/lib/python3.10/dist-packages/mkosi/__init__.py
-
- - name: Install
- run: sudo apt-get update && sudo apt-get install --no-install-recommends python3-pexpect python3-jinja2
+ - uses: systemd/mkosi@ab2aff830096e681da1950a7d29f277caa89516c
- name: Configure
run: |
EOF
- name: Build ${{ matrix.distro }}
- run: sudo mkosi build
+ run: sudo mkosi --idmap no
- name: Show ${{ matrix.distro }} image summary
run: sudo mkosi summary
Changes in systemd-networkd and related tools:
+ * The [DHCPv4] section in .network file gained new SocketPriority=
+ setting that assigns the Linux socket priority used by the DHCPv4
+ raw socket. Can be used in conjunction with the EgressQOSMaps=setting
+ in [VLAN] section of .netdev file to send the desired ethernet 802.1Q
+ frame priority for DHCPv4 initial packets. This cannot be achieved
+ with netfilter mangle tables because of the raw socket bypass.
+
+ * The [DHCPv4] and [IPv6AcceptRA] sections in .network file gained new
+ QuickAck= boolean setting that enables the TCP quick ACK mode for the
+ routes configured by the acquired DHCPv4 lease or received router
+ advertisements (RAs).
+
* The RouteMetric= option (for DHCPv4, DHCPv6, and IPv6 advertised
routes) now accepts three values, for high, medium, and low preference
of the router (which can be set with the RouterPreference=) setting.
* systemd-networkd-wait-online now supports alternative interface names.
+ * The [DHCPv6] section in .network file gained new SendRelease=
+ setting which enables the DHCPv6 client to send release when
+ it stops. This is the analog of the [DHCPv4] SendRelease= setting.
+ It is enabled by default.
+
+ * If the Address= setting in [Network] or [Address] sections in .network
+ specified without its prefix length, then now systemd-networkd assumes
+ /32 for IPv4 or /128 for IPv6 addresses.
+
Changes in systemd-dissect:
* systemd-dissect gained a new option --list, to print the paths fo the
Features:
+* split out execute.c into new "systemd-executor" binary. Then make PID 1 fork
+ that off via vfork(), and then let that executor do the hard work. Ultimately
+ the executor then gets replaced by the real binary sooner or later. Reason:
+ currently the intermediary "stub" process is a CoW trap that doubles memory
+ usage of PID 1 on each service start. Also, strictly speaking we are not
+ allowed to do NSS from the stub process yet we do anyway. Next steps would
+ then be maybe use CLONE_INTO_CGROUP for the executor, given that we don't
+ need glibc anymore in the stub process then. Then, switch nspawn to just be a
+ frontend for this too, so that we have to ways into the executor: via unit
+ files/dbus/varlin through PID1 and via cmdline/OCI through nspawn.
+
* sd-stub: detect if we are running with uefi console output on serial, and if so
automatically add console= to kernel cmdline matching the same port.
and via the time window TPM logic invalidated if node doesn't keep itself
updated, or becomes corrupted in some way.
-* Always measure the LUKS rootfs volume key into PCR 15, and derive the machine
- ID from it securely. This would then allow us to bind secrets a specific
- system securely.
+* in the initrd, once the rootfs encryption key has been measured to PCR 15,
+ derive default machine ID to use from it, and pass it to host PID 1.
* tree-wide: convert as much as possible over to use sd_event_set_signal_exit(), instead
of manually hooking into SIGINT/SIGTERM
For the first time during the current boot an NTP synchronization has been
acquired and the local system clock adjustment has been initiated.
+-- 7db73c8af0d94eeb822ae04323fe6ab6
+Subject: Initial clock bump
+Defined-By: systemd
+Support: %SUPPORT_URL%
+
+The system clock has been advanced based on a timestamp file on disk, in order
+to ensure it remains roughly monotonic – even across reboots – if an RTC is not
+available or is unreliable.
+
-- 3f7d5ef3e54f4302b4f0b143bb270cab
Subject: TPM PCR Extended
Defined-By: systemd
Support: %SUPPORT_URL%
-The string '@MEASURING@' has been extended into Trusted Platform Module's (TPM)
-Platform Configuration Register (PCR) @PCR@, on banks @BANKS@.
+The Trusted Platform Module's (TPM) Platform Configuration Register (PCR)
+@PCR@, on banks @BANKS@, has been extended with the string '@MEASURING@'.
-Whenever the system transitions to a new runtime phase, a different string is
-extended into the specified PCR, to ensure that security policies for TPM-bound
-secrets and other resources are limited to specific phases of the runtime.
+Whenever the system transitions to a new runtime phase, the specified PCR is
+extended with a different string, to ensure that security policies for
+TPM-bound secrets and other resources are limited to specific phases of the
+runtime.
`_SD_ENUM_FORCE_S64()` macro in the enum definition, which forces the size of
the enum to be signed 64bit wide.
+- Empty lines to separate code blocks are a good thing, please add them
+ abundantly. However, please stick to one at a time, i.e. multiple empty lines
+ immediately following each other are not OK. Also, we try to keep function calls and their immediate error handling together. Hence:
+
+ ```c
+ /* → empty line here is good */
+ r = some_function(…);
+ /* → empty line here would be bad */
+ if (r < 0)
+ return log_error_errno(r, "Some function failed: %m");
+ /* → empty line here is good */
+ ```
+
## Code Organization and Semantics
- For our codebase we intend to use ISO C11 *with* GNU extensions (aka
the point where they can be initialized. Avoid huge variable declaration
lists at the top of the function.
- As an exception, `r` is typically used for a local state variable, but should
- almost always be declared as the last variable at the top of the function.
+ As an exception, `int r` is typically used for a local state variable, but
+ should almost always be declared as the last variable at the top of the
+ function.
```c
{
to receive a notification via VSOCK when a virtual machine has finished booting.
Note that in case the hypervisor does not support `SOCK_DGRAM` over `AF_VSOCK`,
`SOCK_SEQPACKET` will be tried instead. The credential payload should be in the
- form: `vsock:<CID>:<PORT>`, where `<CID>` is optional and if omitted will
- default to talking to the hypervisor (`0`). Also note that this requires
- support for VHOST to be built-in both the guest and the host kernels, and the
- kernel modules to be loaded.
+ form: `vsock:<CID>:<PORT>`. Also note that this requires support for VHOST to be
+ built-in both the guest and the host kernels, and the kernel modules to be loaded.
* [`systemd-sysusers(8)`](https://www.freedesktop.org/software/systemd/man/systemd-sysusers.html)
will look for the credentials `passwd.hashed-password.<username>`,
for example in `systemd-nspawn`, will be logged to the audit log, if the
kernel supports this.
+* `$SYSTEMD_ENABLE_LOG_CONTEXT` — if set, extra fields will always be logged to
+the journal instead of only when logging in debug mode.
+
`systemctl`:
* `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID 1's private D-Bus
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`, `systemd-cryptsetup`:
-* `$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.
+* `$SYSTEMD_FORCE_MEASURE=1` — If set, force measuring of resources (which are
+ marked for measurement) even if not booted on a kernel equipped with
+ systemd-stub. Normally, requested measurement of resources is conditionalized
+ on kernels that have booted with `systemd-stub`. With this environment
+ variable the test for that my be bypassed, for testing purposes.
KEYBOARD_KEY_88=wlan
KEYBOARD_KEY_65=direction # Screen Rotate
+# Dell G16 microphone mute
+evdev:name:Dell WMI hotkeys:dmi:bvn*:bvr*:bd*:svnDell*:pnDellG16*:*
# Dell Latitude microphone mute
evdev:name:Dell WMI hotkeys:dmi:bvn*:bvr*:bd*:svnDell*:pnLatitude*:*
# Dell Precision microphone mute
# Spectre x360 16 2022
evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pn*HP[sS][pP][eE][cC][tT][rR][eE]*x3602-in-1*:*
+# ENVY x360
+evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHPENVYx360Convertible*:*
KEYBOARD_KEY_08=unknown # Prevents random airplane mode activation
# HP Elite x2 1013 G3
KEYBOARD_KEY_f7=f21 # Touchpad toggle
KEYBOARD_KEY_f8=f21 # Touchpad toggle
+# Pangolin 12
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnSystem76*:pnPangolin*:pvrpang12*
+ KEYBOARD_KEY_76=f21 # Touchpad toggle
+
###########################################################
# T-bao
###########################################################
###########################################################
# Positivo
###########################################################
-# Positivo MASTER-N1110
+# Positivo MASTER-N1110
evdev:name:AT Translated Set 2 keyboard:dmi:bvn*:svnPositivoTecnologiaSA:pn*:pvr*:rvnPositivoTecnologiaSA:rnNP11G-E*
# Positivo DUO (k116)
evdev:name:AT Translated Set 2 keyboard:dmi:bvn*:svnPositivoTecnologiaSA:pn*:pvr*:rvnPositivoTecnologiaSA:rnK116*
sensor:modalias:acpi:MXC6655*:dmi:*:svnCHUWIInnovationAndTechnology*:pnHi10X:*
ACCEL_MOUNT_MATRIX=0, 1, 0; 1, 0, 0; 0, 0, 1
+# Chuwi Hi10 X (N4120 processor version)
+sensor:modalias:acpi:KIOX000A*:dmi:*:svnCHUWIInnovationAndTechnology*:pnHi10X:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
# Chuwi Hi10 Go
sensor:modalias:acpi:MXC6655*:dmi:*:svnCHUWIINNOVATIONLIMITED:pnHi10Go:*
ACCEL_MOUNT_MATRIX=-1, 0, 0; 0,-1, 0; 0, 0, 1
sensor:modalias:acpi:BOSC0200*:dmi:*:svnLINX*:pnLINX1010B:*
ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, -1
+# Linx 1020
+sensor:modalias:acpi:MIRAACC*:dmi:*:svnLINX*:pnLINX1020:*
+ ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, -1
+
# Linx 12X64, 12V64 and Vision 8
sensor:modalias:acpi:KIOX000A*:dmi:*:svnLINX*:pnLINX12*64:*
sensor:modalias:acpi:KIOX000A*:dmi:*:svnLINX:pnVISION004:*
<refsect1>
<title>Boot Loader Specification Commands</title>
- <para>These commands are available for all boot loaders that implement the <ulink
- url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader Specification</ulink> and/or the <ulink
- url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>, such as
+ <para>These commands are available for all boot loaders that
+ implement the <ulink
+ url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot
+ Loader Specification</ulink>, such as
<command>systemd-boot</command>.</para>
<variablelist>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>unlink</option> <replaceable>ID</replaceable></term>
+
+ <listitem><para>Removes a boot loader entry including the files it refers to. Takes a single boot
+ loader entry ID string or a glob pattern as argument. Referenced files such as kernel or initrd are
+ only removed if no other entry refers to them.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>cleanup</option></term>
+
+ <listitem><para>Removes files from the ESP and XBOOTLDR partitions that belong to the entry token but
+ are not referenced in any boot loader entries.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Boot Loader Interface Commands</title>
+
+ <para>These commands are available for all boot loaders that implement the <ulink
+ url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader Specification</ulink> and the <ulink
+ url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>, such as
+ <command>systemd-boot</command>.</para>
+
+ <variablelist>
<varlistentry>
<term><option>set-default</option> <replaceable>ID</replaceable></term>
<term><option>set-oneshot</option> <replaceable>ID</replaceable></term>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>Kernel Image Commands</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>kernel-identify</option> <replaceable>kernel</replaceable></term>
+
+ <listitem><para>Takes a kernel image as argument. Checks what kind of kernel the image is. Returns
+ one of uki, pe or unknown.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>kernel-inspect</option> <replaceable>kernel</replaceable></term>
+
+ <listitem><para>Takes a kernel image as argument. Prints details about the kernel.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
the firmware's boot option menu.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--dry-run</option></term>
+ <listitem><para>Dry run for <option>--unlink</option> and <option>--cleanup</option>.</para>
+
+ <para>In dry run mode, the unlink and cleanup operations only print the files that would get deleted
+ without actually deleting them.</para></listitem>
+ </varlistentry>
+
<xi:include href="standard-options.xml" xpointer="no-pager"/>
<xi:include href="standard-options.xml" xpointer="json" />
<xi:include href="standard-options.xml" xpointer="help"/>
<programlisting>$ <command>bootctl status</command>
System:
Firmware: UEFI 2.40 (<replaceable>firmware-version</replaceable>) ← firmware vendor and version
- Secure Boot: disabled (setup) ← secure boot status
+ Secure Boot: disabled (setup) ← Secure Boot status
TPM2 Support: yes
Boot into FW: supported ← does the firmware support booting into itself
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+
+<!--
+ SPDX-License-Identifier: LGPL-2.1-or-later
+-->
+
+<refsect1>
+
+<para id="singular">This option cannot be bypassed by prefixing <literal>+</literal> to the executable path
+in the service unit, as it applies to the whole control group.</para>
+
+<para id="plural">These options cannot be bypassed by prefixing <literal>+</literal> to the executable path
+in the service unit, as it applies to the whole control group.</para>
+
+</refsect1>
order).</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>tpm2-measure-pcr=</option></term>
+
+ <listitem><para>Controls whether to measure the volume key of the encrypted volume to a TPM2 PCR. If
+ set to "no" (which is the default) no PCR extension is done. If set to "yes" the volume key is
+ measured into PCR 15. If set to a decimal integer in the range 0…23 the volume key is measured into
+ the specified PCR. The volume key is measured along with the activated volume name and its UUID. This
+ functionality is particularly useful for the encrypted volume backing the root file system, as it
+ then allows later TPM objects to be securely bound to the root file system and hence the specific
+ installation.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>tpm2-measure-bank=</option></term>
+
+ <listitem><para>Selects one or more TPM2 PCR banks to measure the volume key into, as configured with
+ <option>tpm2-measure-pcr=</option> above. Multiple banks may be specified, separated by a colon
+ character. If not specified automatically determines available and used banks. Expects a message
+ digest name (e.g. <literal>sha1</literal>, <literal>sha256</literal>, …) as argument, to identify the
+ bank.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>token-timeout=</option></term>
<refnamediv>
<refname>journalctl</refname>
- <refpurpose>Print log entries from the the systemd journal</refpurpose>
+ <refpurpose>Print log entries from the systemd journal</refpurpose>
</refnamediv>
<refsynopsisdiv>
<refsect1>
<title>Forward Secure Sealing (FSS) Options</title>
- <para>The following options make be used together with the <option>--setup-keys</option> command, see below.</para>
+ <para>The following options may be used together with the <option>--setup-keys</option> command described
+ below:</para>
<variablelist>
<varlistentry>
<varlistentry>
<term><varname>Audit=</varname></term>
- <listitem><para>Takes a boolean value. If enabled <command>systemd-journal</command> will turn on
+ <listitem><para>Takes a boolean value. If enabled <command>systemd-journald</command> will turn on
kernel auditing on start-up. If disabled it will turn it off. If unset it will neither enable nor
- disable it, leaving the previous state unchanged. Note that this option does not control whether
- <command>systemd-journald</command> collects generated audit records, it just controls whether it
- tells the kernel to generate them. This means if another tool turns on auditing even if
- <command>systemd-journald</command> left it off, it will still collect the generated
- messages. Defaults to on.</para></listitem>
+ disable it, leaving the previous state unchanged. This means if another tool turns on auditing even
+ if <command>systemd-journald</command> left it off, it will still collect the generated
+ messages. Defaults to on.</para>
+
+ <para>Note that this option does not control whether <command>systemd-journald</command> collects
+ generated audit records, it just controls whether it tells the kernel to generate them. If you need
+ to prevent <command>systemd-journald</command> from collecting the generated messages, the socket
+ unit <literal>systemd-journald-audit.socket</literal> can be disabled and in this case this setting
+ is without effect.</para>
+ </listitem>
</varlistentry>
<varlistentry>
<para><varname>$KERNEL_INSTALL_MACHINE_ID</varname> is set for the plugins to the desired machine-id to
use. It's always a 128-bit ID. Normally it's read from <filename>/etc/machine-id</filename>, but it can
- also be overridden via <varname>$MACHINE_ID</varname> (see below). If not specified via these methods a
- fallback value will generated by <command>kernel-install</command>, and used only for a single
+ also be overridden via <varname>$MACHINE_ID</varname> (see below). If not specified via these methods,
+ a fallback value will generated by <command>kernel-install</command> and used only for a single
invocation.</para>
<para><varname>$KERNEL_INSTALL_ENTRY_TOKEN</varname> is set for the plugins to the desired entry
equivalent replacement with a more modern API.</para>
<para>All functions require a libudev context to operate. This
- context can be create via
+ context can be created via
<citerefentry><refentrytitle>udev_new</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
It is used to track library state and link objects together. No
global state is used by libudev, everything is always linked to
<listitem><para>Danger: this feature might soft-brick your device if used improperly.</para>
<para>Takes one of <literal>off</literal>, <literal>manual</literal> or <literal>force</literal>.
- Controls the enrollment of secure boot keys. If set to <literal>off</literal>, no action whatsoever
+ Controls the enrollment of Secure Boot keys. If set to <literal>off</literal>, no action whatsoever
is taken. If set to <literal>manual</literal> (the default) and the UEFI firmware is in setup-mode
then entries to manually enroll Secure Boot variables are created in the boot menu. If set to
<literal>force</literal>, in addition, if a directory named <filename>/loader/keys/auto/</filename>
exists on the ESP then the keys in that directory are enrolled automatically.</para>
- <para>The different sets of variables can be set up under <filename>/loader/keys/<replaceable>NAME</replaceable></filename>
- where <replaceable>NAME</replaceable> is the name that is going to be used as the name of the entry.
- This allows one to ship multiple sets of Secure Boot variables and choose which one to enroll at runtime.</para>
+ <para>The different sets of variables can be set up under
+ <filename>/loader/keys/<replaceable>NAME</replaceable></filename> where
+ <replaceable>NAME</replaceable> is the name that is going to be used as the name of the entry. This
+ allows one to ship multiple sets of Secure Boot variables and choose which one to enroll at runtime.
+ </para>
- <para>Supported secure boot variables are one database for authorized images, one key exchange key (KEK)
- and one platform key (PK). For more information, refer to the <ulink url="https://uefi.org/specifications">UEFI specification</ulink>,
- under Secure Boot and Driver Signing. Another resource that describe the interplay of the different variables is the
+ <para>Supported Secure Boot variables are one database for authorized images, one key exchange key
+ (KEK) and one platform key (PK). For more information, refer to the <ulink
+ url="https://uefi.org/specifications">UEFI specification</ulink>, under Secure Boot and Driver
+ Signing. Another resource that describe the interplay of the different variables is the
<ulink url="https://edk2-docs.gitbook.io/understanding-the-uefi-secure-boot-chain/secure_boot_chain_in_uefi/uefi_secure_boot">
EDK2 documentation</ulink>.</para>
<para>Work around BitLocker requiring a recovery key when the boot loader was
updated (disabled by default).</para>
- <para>Try to detect BitLocker encrypted drives along with an active TPM. If both are found
- and Windows Boot Manager is selected in the boot menu, set the <literal>BootNext</literal>
- EFI variable and restart the system. The firmware will then start Windows Boot Manager
- directly, leaving the TPM PCRs in expected states so that Windows can unseal the encryption
- key. This allows systemd-boot to be updated without having to provide the recovery key for
- BitLocker drive unlocking.</para>
+ <para>Try to detect BitLocker encrypted drives along with an active TPM. If both are found and
+ Windows Boot Manager is selected in the boot menu, set the <literal>BootNext</literal> EFI variable
+ and restart the system. The firmware will then start Windows Boot Manager directly, leaving the TPM
+ PCRs in expected states so that Windows can unseal the encryption key. This allows
+ <citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> to
+ be updated without having to provide the recovery key for BitLocker drive unlocking.</para>
<para>Note that the PCRs that Windows uses can be configured with the
<literal>Configure TPM platform validation profile for native UEFI firmware configurations</literal>
group policy under <literal>Computer Configuration\Administrative Templates\Windows Components\BitLocker Drive Encryption</literal>.
- When secure boot is enabled, changing this to PCRs <literal>0,2,7,11</literal> should be safe.
+ When Secure Boot is enabled, changing this to PCRs <literal>0,2,7,11</literal> should be safe.
The TPM key protector needs to be removed and then added back for the PCRs on an already
encrypted drive to change. If PCR 4 is not measured, this setting can be disabled to speed
up booting into Windows.</para></listitem>
netgroup: nis</programlisting>
- <para>To test, use <command>glibc</command>'s <command>getent</command> tool:</para>
+ <para>To test, use <command>glibc</command>'s
+ <citerefentry project='man-pages'><refentrytitle>getent</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ tool:</para>
<programlisting>$ getent ahosts `hostname`
::1 STREAM omega
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s FirmwareVendor = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s FirmwareDate = '...';
+ readonly t FirmwareDate = ...;
};
interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... };
out o unit);
GetUnitByControlGroup(in s cgroup,
out o unit);
+ GetUnitByPIDFD(in h pidfd,
+ out o unit,
+ out s unit_id,
+ out ay invocation_id);
LoadUnit(in s name,
out o unit);
StartUnit(in s name,
<variablelist class="dbus-method" generated="True" extra-ref="GetUnitByControlGroup()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="GetUnitByPIDFD()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="LoadUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="StartUnit()"/>
will fail.</para>
<para><function>GetUnitByPID()</function> may be used to get the unit object path of the unit a process
- ID belongs to. It takes a UNIX PID and returns the object path. The PID must refer to an existing system process.</para>
+ ID belongs to. It takes a UNIX PID and returns the object path. The PID must refer to an existing system process.
+ <function>GetUnitByPIDFD()</function> may be used to query with a Linux PIDFD (see:
+ <citerefentry><refentrytitle>pidfd_open</refentrytitle><manvolnum>2</manvolnum></citerefentry>) instead
+ of a PID, which is safer as UNIX PIDs can be recycled. The latter method returns the unit id and the
+ invocation id together with the unit object path.</para>
<para><function>LoadUnit()</function> is similar to <function>GetUnit()</function> but will load the
unit from disk if possible.</para>
<varlistentry>
<term><varname>PORTABLE_PREFIXES=</varname></term>
<listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
- <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> logic. This field
- serves two purposes: it is informational, identifying portable service images as such (and thus
- allowing them to be distinguished from other OS images, such as bootable system images). It is also
- used when a portable service image is attached: the specified or implied portable service prefix is
- checked against the list specified here, to enforce restrictions how images may be attached to a
- system.</para></listitem>
+ <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services Documentation</ulink> logic.
+ This field serves two purposes: it is informational, identifying portable service images as such
+ (and thus allowing them to be distinguished from other OS images, such as bootable system images).
+ It is also used when a portable service image is attached: the specified or implied portable
+ service prefix is checked against the list specified here, to enforce restrictions how images may
+ be attached to a system.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<refsect1>
<title>Module Types Provided</title>
- <para>The module implements all four PAM operations: <option>auth</option> (reason: to allow
- authentication using the encrypted data), <option>account</option> (reason: users with
+ <para>The module implements all four PAM operations: <option>auth</option> (to allow authentication using
+ the encrypted data), <option>account</option> (because users with
<filename>systemd-homed.service</filename> user accounts are described in a <ulink
url="https://systemd.io/USER_RECORD/">JSON user record</ulink> and may be configured in more detail than
- in the traditional Linux user database), <option>session</option> (user sessions must be tracked in order
- to implement automatic release when the last session of the user is gone), <option>password</option> (to
- change the encryption password — also used for user authentication — through PAM).</para>
+ in the traditional Linux user database), <option>session</option> (because user sessions must be tracked
+ in order to implement automatic release when the last session of the user is gone),
+ <option>password</option> (to change the encryption password — also used for user authentication —
+ through PAM).</para>
</refsect1>
<refsect1>
and transfer them as a whole between systems. When these images are attached the local system the contained units
may run in most ways like regular system-provided units, either with full privileges or inside strict sandboxing,
depending on the selected configuration. For more details, see
- <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink>.</para>
+ <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services Documentation</ulink>.</para>
<para>Specifically portable service images may be of the following kind:</para>
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Images can be block images, btrfs subvolumes or directories. For more information on portable
services with extensions, see the <literal>Extension Images</literal> paragraph on
- <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink>.
+ <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services Documentation</ulink>.
</para>
<para>Note that the same extensions have to be specified, in the same order, when attaching
<para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para>
- <para>When <command>systemd-repart</command> is invoked with the <option>--image=</option> or
- <option>--root=</option> command line switches the source paths specified are taken relative to the
- specified root directory or disk image root.</para></listitem>
+ <para>When
+ <citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ is invoked with the <option>--image=</option> or <option>--root=</option> command line switches the
+ source paths specified are taken relative to the specified root directory or disk image root.
+ </para></listitem>
</varlistentry>
<varlistentry>
to <literal>off</literal> or <literal>data</literal>, the partition is populated with content as
specified by <varname>CopyBlocks=</varname> or <varname>CopyFiles=</varname>. If set to
<literal>hash</literal>, the partition will be populated with verity hashes from the matching verity
- data partition. If set to <literal>signature</literal>, The partition will be populated with a JSON
+ data partition. If set to <literal>signature</literal>, the partition will be populated with a JSON
object containing a signature of the verity root hash of the matching verity hash partition.</para>
<para>A matching verity partition is a partition with the same verity match key (as configured with
'sd_pid_get_slice',
'sd_pid_get_unit',
'sd_pid_get_user_slice',
- 'sd_pid_get_user_unit'],
+ 'sd_pid_get_user_unit',
+ 'sd_pidfd_get_cgroup',
+ 'sd_pidfd_get_machine_name',
+ 'sd_pidfd_get_owner_uid',
+ 'sd_pidfd_get_session',
+ 'sd_pidfd_get_slice',
+ 'sd_pidfd_get_unit',
+ 'sd_pidfd_get_user_slice',
+ 'sd_pidfd_get_user_unit'],
'HAVE_PAM'],
['sd_seat_get_active',
'3',
['systemd-path', '1', [], ''],
['systemd-pcrphase.service',
'8',
- ['systemd-pcrphase',
+ ['systemd-pcrfs-root.service',
+ 'systemd-pcrfs@.service',
+ 'systemd-pcrmachine.service',
+ 'systemd-pcrphase',
'systemd-pcrphase-initrd.service',
'systemd-pcrphase-sysinit.service'],
'HAVE_GNU_EFI'],
<citerefentry><refentrytitle>sd_bus_process</refentrytitle><manvolnum>3</manvolnum></citerefentry> returns zero,
indicating that no work is pending on the connection. Internally, this call invokes <citerefentry
project='man-pages'><refentrytitle>ppoll</refentrytitle><manvolnum>2</manvolnum></citerefentry>, to wait for I/O on
- the bus connection. If the <parameter>timeout_sec</parameter> parameter is specified, the call will block at most
+ the bus connection. If the <parameter>timeout_usec</parameter> parameter is specified, the call will block at most
for the specified amount of time in µs. Pass <constant>UINT64_MAX</constant> to permit it to sleep
indefinitely.</para>
<para>A service could send the following after completing
initialization:</para>
- <programlisting>sd_notifyf(0, "READY=1\n"
- "STATUS=Processing requests…\n"
- "MAINPID=%lu",
- (unsigned long) getpid());</programlisting>
+ <programlisting>
+sd_notifyf(0, "READY=1\n"
+ "STATUS=Processing requests…\n"
+ "MAINPID=%lu",
+ (unsigned long) getpid());</programlisting>
</example>
<example>
<para>A service could send the following shortly before exiting, on failure:</para>
- <programlisting>sd_notifyf(0, "STATUS=Failed to start up: %s\n"
- "ERRNO=%i",
- strerror_r(errnum, (char[1024]){}, 1024),
- errnum);</programlisting>
+ <programlisting>
+sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+ "ERRNO=%i",
+ strerror_r(errnum, (char[1024]){}, 1024),
+ errnum);</programlisting>
</example>
<example>
to synchronize against reception of all notifications sent before
this call is made.</para>
- <programlisting>sd_notify(0, "READY=1");
- /* set timeout to 5 seconds */
- sd_notify_barrier(0, 5 * 1000000);
+ <programlisting>
+sd_notify(0, "READY=1");
+/* set timeout to 5 seconds */
+sd_notify_barrier(0, 5 * 1000000);
</programlisting>
</example>
</refsect1>
<constant>SD_PATH_CATALOG</constant>,
<constant>SD_PATH_SYSTEMD_SEARCH_NETWORK</constant>,
+
+ <constant>SD_PATH_SYSTEMD_SYSTEM_ENVIRONMENT_GENERATOR</constant>,
+ <constant>SD_PATH_SYSTEMD_USER_ENVIRONMENT_GENERATOR</constant>,
+ <constant>SD_PATH_SYSTEMD_SEARCH_SYSTEM_ENVIRONMENT_GENERATOR</constant>,
+ <constant>SD_PATH_SYSTEMD_SEARCH_USER_ENVIRONMENT_GENERATOR</constant>,
};</funcsynopsisinfo>
<funcprototype>
<refname>sd_pid_get_slice</refname>
<refname>sd_pid_get_user_slice</refname>
<refname>sd_pid_get_cgroup</refname>
+ <refname>sd_pidfd_get_owner_uid</refname>
+ <refname>sd_pidfd_get_session</refname>
+ <refname>sd_pidfd_get_user_unit</refname>
+ <refname>sd_pidfd_get_unit</refname>
+ <refname>sd_pidfd_get_machine_name</refname>
+ <refname>sd_pidfd_get_slice</refname>
+ <refname>sd_pidfd_get_user_slice</refname>
+ <refname>sd_pidfd_get_cgroup</refname>
<refname>sd_peer_get_owner_uid</refname>
<refname>sd_peer_get_session</refname>
<refname>sd_peer_get_user_unit</refname>
<paramdef>char **<parameter>cgroup</parameter></paramdef>
</funcprototype>
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_owner_uid</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>uid_t *<parameter>uid</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_session</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>char **<parameter>session</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_user_unit</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>char **<parameter>unit</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_unit</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>char **<parameter>unit</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_machine_name</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>char **<parameter>name</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_slice</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>char **<parameter>slice</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_user_slice</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>char **<parameter>slice</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_pidfd_get_cgroup</function></funcdef>
+ <paramdef>int <parameter>pidfd</parameter></paramdef>
+ <paramdef>char **<parameter>cgroup</parameter></paramdef>
+ </funcprototype>
+
<funcprototype>
<funcdef>int <function>sd_peer_get_owner_uid</function></funcdef>
<paramdef>int <parameter>fd</parameter></paramdef>
functions is passed as 0, the operation is executed for the
calling process.</para>
+ <para>The <function>sd_pidfd_get_owner_uid()</function>,
+ <function>sd_pidfd_get_session()</function>,
+ <function>sd_pidfd_get_user_unit()</function>,
+ <function>sd_pidfd_get_unit()</function>,
+ <function>sd_pidfd_get_machine_name()</function>,
+ <function>sd_pidfd_get_slice()</function>,
+ <function>sd_pidfd_get_user_slice()</function> and
+ <function>sd_pidfd_get_cgroup()</function> calls operate similarly to their PID counterparts, but accept a
+ <constant>PIDFD</constant> instead of a <constant>PID</constant>, which means they are not subject to recycle
+ race conditions as the process is pinned by the file descriptor during the whole duration of the invocation.
+ Note that these require a kernel that supports <constant>PIDFD</constant>. A suitable file descriptor may be
+ acquired via
+ <citerefentry project='man-pages'><refentrytitle>pidfd_open</refentrytitle><manvolnum>2</manvolnum></citerefentry>.</para>
+
<para>The <function>sd_peer_get_owner_uid()</function>,
<function>sd_peer_get_session()</function>,
<function>sd_peer_get_user_unit()</function>,
dependencies. If no units are specified,
<filename>default.target</filename> is implied.</para>
+ <para>The units that are shown are additionally filtered by <option>--type=</option> and
+ <option>--state=</option> if those options are specified. Note that we won't be able to
+ use a tree structure in this case, so <option>--plain</option> is implied.</para>
+
<para>By default, only target units are recursively
expanded. When <option>--all</option> is passed, all other
units are recursively expanded as well.</para>
<listitem>
<para>The argument is a comma-separated list of unit types such as <option>service</option> and
<option>socket</option>. When units are listed with <command>list-units</command>,
- <command>show</command>, or <command>status</command>, only units of the specified types will be
- shown. By default, units of all types are shown.</para>
+ <command>list-dependencies</command>, <command>show</command>, or <command>status</command>,
+ only units of the specified types will be shown. By default, units of all types are shown.</para>
<para>As a special case, if one of the arguments is <option>help</option>, a list of allowed values
will be printed and the program will exit.</para>
<listitem>
<para>The argument is a comma-separated list of unit LOAD, SUB, or ACTIVE states. When listing
- units with <command>list-units</command>, <command>show</command>, or <command>status</command>,
- show only those in the specified states. Use <option>--state=failed</option> or
- <option>--failed</option> to show only failed units.</para>
+ units with <command>list-units</command>, <command>list-dependencies</command>, <command>show</command>
+ or <command>status</command>, show only those in the specified states. Use <option>--state=failed</option>
+ or <option>--failed</option> to show only failed units.</para>
<para>As a special case, if one of the arguments is <option>help</option>, a list of allowed values
will be printed and the program will exit.</para>
<term><option>--no-warn</option></term>
<listitem>
- <para>Don't generate the warning shown by default when using
- <command>enable</command> or <command>disable</command> on units
- without install information (i.e. don't have or have an empty
- [Install] section).</para>
+ <para>Don't generate the warnings shown by default in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>when <command>systemctl</command> is invoked without procfs mounted on
+ <filename>/proc/</filename>,</para>
+ </listitem>
+ <listitem>
+ <para>when using <command>enable</command> or <command>disable</command> on units without
+ install information (i.e. don't have or have an empty [Install] section).</para>
+ </listitem>
+ </itemizedlist>
+ </para>
</listitem>
</varlistentry>
<refsect2>
<title><command>systemd-analyze plot</command></title>
- <para>This command prints an SVG graphic detailing which system services have been started at what
- time, highlighting the time they spent on initialization.</para>
+ <para>This command prints either an SVG graphic, detailing which system services have been started at what
+ time, highlighting the time they spent on initialization, or the raw time data in JSON or table format.</para>
<example>
<title><command>Plot a bootchart</command></title>
$ eog bootup.svg&
</programlisting>
</example>
+
+ <para>Note that this plot is based on the most recent per-unit timing data of loaded units. This means
+ that if a unit gets started, then stopped and then started again the information shown will cover the
+ most recent start cycle, not the first one. Thus it's recommended to consult this information only
+ shortly after boot, so that this distinction doesn't matter. Moreover, units that are not referenced by
+ any other unit through a dependency might be unloaded by the service manager once they terminate (and
+ did not fail). Such units will not show up in the plot.</para>
</refsect2>
<refsect2>
corresponds to a higher security threat. The JSON version of the table is printed to standard
output. The <replaceable>MODE</replaceable> passed to the option can be one of three:
<option>off</option> which is the default, <option>pretty</option> and <option>short</option>
- which respectively output a prettified or shorted JSON version of the security table.</para></listitem>
+ which respectively output a prettified or shorted JSON version of the security table.
+
+ With the <command>plot</command> command, generate a JSON formatted output of the raw time data.
+ The format is a JSON array with objects containing the following fields: <varname>name</varname>
+ which is the unit name, <varname>activated</varname> which is the time after startup the
+ service was activated, <varname>activating</varname> which is how long after startup the service
+ was initially started, <varname>time</varname> which is how long the service took to activate
+ from when it was initially started, <varname>deactivated</varname> which is the time after startup
+ that the service was deactivated, <varname>deactivating</varname> which is the time after startup
+ that the service was initially told to deactivate.
+ </para></listitem>
</varlistentry>
<varlistentry>
other paths.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--table</option></term>
+
+ <listitem><para>When used with the <command>plot</command> command, the raw time data is output in a table.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-legend</option></term>
+
+ <listitem><para>When used with the <command>plot</command> command in combination with either
+ <option>--table</option> or <option>--json=</option>, no legends or hints are included in the output.
+ </para></listitem>
+ </varlistentry>
+
<xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" />
refreshes the boot loader random seed stored in the EFI System Partition (ESP), from the Linux kernel
entropy pool. The boot loader random seed is primarily consumed and updated by
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> from the
- UEFI environemnt (or
+ UEFI environment (or
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> if the
former is not used, but the latter is), and passed as initial RNG seed to the OS. It is an effective way
to ensure the OS comes up with a random pool that is fully initialized.</para>
<listitem><para>A reboot into the UEFI firmware setup option, if supported by the firmware.</para></listitem>
- <listitem><para>Secure boot variables enrollement if the UEFI firmware is in setup-mode and files are provided
+ <listitem><para>Secure Boot variables enrollment if the UEFI firmware is in setup-mode and files are provided
on the ESP.</para></listitem>
</itemizedlist>
with a 'system token' stored in a persistent EFI variable and derives a random seed to use by the OS as
entropy pool initialization, providing a full entropy pool during early boot.</para></listitem>
- <listitem><para>The boot manager allows for secure boot variables to be enrolled if the UEFI firmware is
+ <listitem><para>The boot manager allows for Secure Boot variables to be enrolled if the UEFI firmware is
in setup-mode. Additionally, variables can be automatically enrolled if configured.</para></listitem>
</itemizedlist>
<listitem><para>Takes a path to a TPM2 PCR signature file as generated by the
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool and that may be used to allow the <command>decrypt</command> command to decrypt credentials that
- are bound to specific signed PCR values. If this this is not specified explicitly, and a credential
+ are bound to specific signed PCR values. If this is not specified explicitly, and a credential
with a signed PCR policy is attempted to be decrypted, a suitable signature file
<filename>tpm2-pcr-signature.json</filename> is searched for in <filename>/etc/systemd/</filename>,
<filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and
<row>
<entry>7</entry>
- <entry>Secure boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes. The shim project will measure most of its (non-MOK) certificates and SBAT data into this PCR.</entry>
+ <entry>Secure Boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes. The shim project will measure most of its (non-MOK) certificates and SBAT data into this PCR.</entry>
</row>
<!-- Grub measures all its commands and the kernel command line into PCR 8… -->
<row>
<entry>12</entry>
- <entry><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any specified kernel command line into this PCR. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if <command>systemd-boot</command> and <command>systemd-stub</command> are used in combination the command line might be measured twice!)</entry>
+ <entry><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the kernel command line into this PCR. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if <command>systemd-boot</command> and <command>systemd-stub</command> are used in combination the command line might be measured twice!)</entry>
</row>
<row>
<entry>14</entry>
<entry>The shim project measures its "MOK" certificates and hashes into this PCR.</entry>
</row>
+
+ <row>
+ <entry>15</entry>
+ <entry><citerefentry><refentrytitle>systemd-cryptsetup</refentrytitle><manvolnum>7</manvolnum></citerefentry> optionally measures the volume key of activated LUKS volumes into this PCR.</entry>
+ </row>
</tbody>
</tgroup>
</table>
<para>The <option>--tpm2-signature=</option> option takes a path to a TPM2 PCR signature file
as generated by the
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
- tool. If this this is not specified explicitly a suitable signature file
+ tool. If this is not specified explicitly a suitable signature file
<filename>tpm2-pcr-signature.json</filename> is searched for in <filename>/etc/systemd/</filename>,
<filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and
used. If a signature file is specified or found it is used to verify if the volume can be unlocked
<literal>no</literal>, causes the generator to ignore any devices configured in
<filename>/etc/crypttab</filename> (<varname>luks.uuid=</varname> will still work however).
<varname>rd.luks.crypttab=</varname> is honored only in initrd while
- <varname>luks.crypttab=</varname> is honored by both the main system and the initrd.
+ <varname>luks.crypttab=</varname> is honored by both the main system and in the initrd.
</para></listitem>
</varlistentry>
part of the boot process as if it was listed in <filename>/etc/crypttab</filename>. This option may
be specified more than once in order to set up multiple devices. <varname>rd.luks.uuid=</varname> is
honored only in the initrd, while <varname>luks.uuid=</varname> is honored by both the main system
- and the initrd.</para>
+ and in the initrd.</para>
<para>If <filename>/etc/crypttab</filename> contains entries with the same UUID, then the name,
keyfile and options specified there will be used. Otherwise, the device will have the name
<manvolnum>5</manvolnum></citerefentry> field <replaceable>volume-name</replaceable>.</para>
<para><varname>rd.luks.name=</varname> is honored only in the initrd, while
- <varname>luks.name=</varname> is honored by both the main system and the initrd.</para>
+ <varname>luks.name=</varname> is honored by both the main system and in the initrd.</para>
</listitem>
</varlistentry>
<para><varname>rd.luks.options=</varname> is honored only by initial
RAM disk (initrd) while <varname>luks.options=</varname> is
- honored by both the main system and the initrd.</para>
+ honored by both the main system and in the initrd.</para>
</listitem>
</varlistentry>
</variablelist>
systems, make sure to set the correct default subvolumes on them,
using <command>btrfs subvolume set-default</command>.</para>
+ <para>If the system was booted via
+ <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> and the
+ stub reported to userspace that the kernel image was measured to a TPM2 PCR, then any discovered root and
+ <filename>/var/</filename> volume identifiers (and volume encryption key in case it is encrypted) will be
+ automatically measured into PCR 15 on activation, via
+ <citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+
<para><filename>systemd-gpt-auto-generator</filename> implements
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</refsect1>
<citerefentry><refentrytitle>systemd.swap</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<refsect1>
<title>Description</title>
- <para><filename>systemd-integritysetup-generator</filename> is a generator that translates <filename>/etc/integritytab</filename> entries into
- native systemd units early at boot. This will create
+ <para><command>systemd-integritysetup-generator</command> is a generator that translates
+ <filename>/etc/integritytab</filename> entries into native systemd units early at boot. This will create
<citerefentry><refentrytitle>systemd-integritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
units as necessary.</para>
<listitem><para>Sockets and other file node paths that <command>systemd-journald</command> will
listen on and are visible in the file system. In addition to these,
<command>systemd-journald</command> can listen for audit events using <citerefentry
- project='man-pages'><refentrytitle>netlink</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para></listitem>
+ project='man-pages'><refentrytitle>netlink</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ depending on whether <literal>systemd-journald-audit.socket</literal> is enabled or
+ not.</para></listitem>
</varlistentry>
</variablelist>
seen in TPM2 PCR register 11 after boot-up of a unified kernel image. Then, cryptographically sign
the resulting values with the private/public key pair (RSA) configured via
<option>--private-key=</option> and <option>--public-key=</option>. This will write a JSON object to
- standard output that contains signatures for all specified PCR banks (see
- <option>--pcr-bank=</option>) below, which may be used to unlock encrypted credentials (see
+ standard output that contains signatures for all specified PCR banks (see the
+ <option>--pcr-bank=</option> option below), which may be used to unlock encrypted credentials (see
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>) or
LUKS volumes (see
- <citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>). This
- allows binding secrets to a set of kernels for which such PCR 11 signatures can be provided.</para>
+ <citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>).
+ This allows binding secrets to a set of kernels for which such PCR 11 signatures can be
+ provided.</para>
<para>Note that a TPM2 device must be available for this signing to take place, even though the
result is not tied to any TPM2 device or its state.</para></listitem>
<variablelist>
<varlistentry>
- <term><option>--linux=PATH</option></term>
- <term><option>--osrel=PATH</option></term>
- <term><option>--cmdline=PATH</option></term>
- <term><option>--initrd=PATH</option></term>
- <term><option>--splash=PATH</option></term>
- <term><option>--dtb=PATH</option></term>
- <term><option>--pcrpkey=PATH</option></term>
+ <term><option>--linux=<replaceable>PATH</replaceable></option></term>
+ <term><option>--osrel=<replaceable>PATH</replaceable></option></term>
+ <term><option>--cmdline=<replaceable>PATH</replaceable></option></term>
+ <term><option>--initrd=<replaceable>PATH</replaceable></option></term>
+ <term><option>--splash=<replaceable>PATH</replaceable></option></term>
+ <term><option>--dtb=<replaceable>PATH</replaceable></option></term>
+ <term><option>--pcrpkey=<replaceable>PATH</replaceable></option></term>
<listitem><para>When used with the <command>calculate</command> or <command>sign</command> verb,
configures the files to read the unified kernel image components from. Each option corresponds with
</varlistentry>
<varlistentry>
- <term><option>--bank=DIGEST</option></term>
+ <term><option>--bank=<replaceable>DIGEST</replaceable></option></term>
<listitem><para>Controls the PCR banks to pre-calculate the PCR values for – in case
<command>calculate</command> or <command>sign</command> is invoked –, or the banks to show in the
</varlistentry>
<varlistentry>
- <term><option>--private-key=PATH</option></term>
- <term><option>--public-key=PATH</option></term>
+ <term><option>--private-key=<replaceable>PATH</replaceable></option></term>
+ <term><option>--public-key=<replaceable>PATH</replaceable></option></term>
<listitem><para>These switches take paths to a pair of PEM encoded RSA key files, for use with
the <command>sign</command> command.</para>
<example>
<title>Generate a unified kernel image, and calculate the expected TPM PCR 11 value</title>
- <programlisting># objcopy \
- --add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
- --add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \
- --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
- --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 \
- /usr/lib/systemd/boot/efi/linuxx64.efi.stub \
- foo.efi
-# systemd-measure calculate \
- --linux=vmlinux \
- --osrel=os-release.txt \
- --cmdline=cmdline.txt \
- --initrd=initrd.cpio \
- --splash=splash.bmp \
- --dtb=devicetree.dtb
+ <programlisting># ukify --output foo.efi \
+ --os-release @os-release.txt \
+ --cmdline @cmdline.txt \
+ --splash splash.bmp \
+ --devicetree devicetree.dtb \
+ --measure \
+ vmlinux initrd.cpio
11:sha1=d775a7b4482450ac77e03ee19bda90bd792d6ec7
11:sha256=bc6170f9ce28eb051ab465cd62be8cf63985276766cf9faf527ffefb66f45651
11:sha384=1cf67dff4757e61e5a73d2a21a6694d668629bbc3761747d493f7f49ad720be02fd07263e1f93061243aec599d1ee4b4
--bank=sha256 \
--private-key=tpm2-pcr-private.pem \
--public-key=tpm2-pcr-public.pem > tpm2-pcr-signature.json
-# objcopy \
- --add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
- --add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \
- --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
- --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 .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>
+# ukify --output foo.efi \
+ --os-release @os-release.txt \
+ --cmdline @cmdline.txt \
+ --splash splash.bmp \
+ --devicetree devicetree.dtb \
+ --pcr-private-key tpm2-pcr-private.pem \
+ --pcr-public-key tpm2-pcr-public.pem \
+ --pcr-banks sha1,sha256 \
+ vmlinux initrd.cpio</programlisting>
<para>Later on, enroll the signed PCR policy on a LUKS volume:</para>
--public-key=tpm2-pcr-initrd-public.pem \
--phase=enter-initrd \
--append=tpm2-pcr-signature.json.tmp >tpm2-pcr-signature.json
-# objcopy \
- --add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
- --add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \
- --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
- --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 .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>
+# ukify --output foo.efi \
+ --os-release @os-release.txt \
+ --cmdline @cmdline.txt \
+ --splash splash.bmp \
+ --devicetree devicetree.dtb \
+ --pcr-private-key tpm2-pcr-initrd-private.pem \
+ --pcr-public-key tpm2-pcr-initrd-public.pem \
+ --section .pcrsig=@tpm2-pcr-signature.json \
+ --section .pcrpkey=@tpm2-pcr-public.pem \
+ vmlinux initrd.cpio</programlisting>
</example>
<para>Note that in this example the <literal>.pcrpkey</literal> PE section contains the key covering all
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
- <citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>ukify</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<itemizedlist>
<listitem><para>If <option>noidmap</option> is used, any user <option>z</option> in the range
<option>0 … y</option> seen from inside of the container is mapped to <option>x + z</option> in the
- <option>x … x + y</option> range on the host. All host users outside of that range are mapped to
+ <option>x … x + y</option> range on the host. Other host users are mapped to
<option>nobody</option> inside the container.</para></listitem>
<listitem><para>If <option>idmap</option> is used, any user <option>z</option> in the UID range
<option>0 … y</option> as seen from inside the container is mapped to the same <option>z</option>
</itemizedlist></para>
<para>Whichever ID mapping option is used, the same mapping will be used for users and groups IDs. If
- <option>rootidmap</option> is used, the group owning the bind mounted directory will have no effect</para>
+ <option>rootidmap</option> is used, the group owning the bind mounted directory will have no effect.</para>
<para>Note that when this option is used in combination with <option>--private-users</option>, the resulting
mount points will be owned by the <constant>nobody</constant> user. That's because the mount and its files and
<para>You can enable monitoring and actions on units by setting <varname>ManagedOOMSwap=</varname> and
<varname>ManagedOOMMemoryPressure=</varname> in the unit configuration, see
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
- <command>systemd-oomd</command> retrieves information about such units from <command>systemd</command>
+ <command>systemd-oomd</command> retrieves information about such units from
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
when it starts and watches for subsequent changes.</para>
<para>Cgroups of units with <varname>ManagedOOMSwap=</varname> or
<refname>systemd-pcrphase.service</refname>
<refname>systemd-pcrphase-sysinit.service</refname>
<refname>systemd-pcrphase-initrd.service</refname>
+ <refname>systemd-pcrmachine.service</refname>
+ <refname>systemd-pcrfs-root.service</refname>
+ <refname>systemd-pcrfs@.service</refname>
<refname>systemd-pcrphase</refname>
- <refpurpose>Measure boot phase into TPM2 PCR 11</refpurpose>
+ <refpurpose>Measure boot phase into TPM2 PCR 11, machine ID and file system identity into PCR 15</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-pcrphase.service</filename></para>
<para><filename>systemd-pcrphase-sysinit.service</filename></para>
<para><filename>systemd-pcrphase-initrd.service</filename></para>
- <para><filename>/usr/lib/systemd/system-pcrphase</filename> <replaceable>STRING</replaceable></para>
+ <para><filename>systemd-pcrmachine.service</filename></para>
+ <para><filename>systemd-pcrfs-root.service</filename></para>
+ <para><filename>systemd-pcrfs@.service</filename></para>
+ <para><filename>/usr/lib/systemd/system-pcrphase</filename> <optional><replaceable>STRING</replaceable></optional></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-pcrphase.service</filename>,
- <filename>systemd-pcrphase-sysinit.service</filename> and
+ <filename>systemd-pcrphase-sysinit.service</filename>, and
<filename>systemd-pcrphase-initrd.service</filename> are system services that measure specific strings
into TPM2 PCR 11 during boot at various milestones of the boot process.</para>
+ <para><filename>systemd-pcrmachine.service</filename> is a system service that measures the machine ID
+ (see <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>) into
+ PCR 15.</para>
+
+ <para><filename>systemd-pcrfs-root.service</filename> and <filename>systemd-pcrfs@.service</filename> are
+ services that measure file system identity information (i.e. mount point, file system type, label and
+ UUID, partition label and UUID) into PCR 15. <filename>systemd-pcrfs-root.service</filename> does so for
+ the root file system, <filename>systemd-pcrfs@.service</filename> is a template unit that measures the
+ file system indicated by its instance identifier instead.</para>
+
<para>These services require
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> to be
- used in a unified kernel image (UKI) setup. They execute no operation when invoked when the stub has not
- been used to invoke the kernel. The stub will measure the invoked kernel and associated vendor resources
- into PCR 11 before handing control to it; once userspace is invoked these services then will extend
- certain literal strings indicating various phases of the boot process into TPM2 PCR 11. During a regular
- boot process the following strings are extended into PCR 11.</para>
+ used in a unified kernel image (UKI). They execute no operation when the stub has not been used to invoke
+ the kernel. The stub will measure the invoked kernel and associated vendor resources into PCR 11 before
+ handing control to it; once userspace is invoked these services then will extend TPM2 PCR 11 with certain
+ literal strings indicating phases of the boot process. During a regular boot process PCR 11 is extended
+ with the following strings:</para>
<orderedlist>
- <listitem><para><literal>enter-initrd</literal> is extended into PCR 11 early when the initrd
- initializes, before activating system extension images for the initrd. It is supposed to act as barrier
- between the time where the kernel initializes, and where the initrd starts operating and enables
- system extension images, i.e. code shipped outside of the UKI. (This string is extended at start of
- <filename>systemd-pcrphase-initrd.service</filename>.)</para></listitem>
-
- <listitem><para><literal>leave-initrd</literal> is extended into PCR 11 when the initrd is about to
- transition into the host file system, i.e. when it achieved its purpose. It is supposed to act as
- barrier between kernel/initrd code and host OS code. (This string is extended at stop of
- <filename>systemd-pcrphase-initrd.service</filename>.)</para></listitem>
-
- <listitem><para><literal>sysinit</literal> is extended into PCR 11 when basic system initialization is
- complete (which includes local file systems have been mounted), and the system begins starting regular
- system services. (This string is extended at start of
- <filename>systemd-pcrphase-sysinit.service</filename>.)</para></listitem>
-
- <listitem><para><literal>ready</literal> is extended into PCR 11 during later boot-up, after remote
- file systems have been activated (i.e. after <filename>remote-fs.target</filename>), but before users
- are permitted to log in (i.e. before <filename>systemd-user-sessions.service</filename>). It is
- supposed to act as barrier between the time where unprivileged regular users are still prohibited to
- log in and where they are allowed to log in. (This string is extended at start of
- <filename>systemd-pcrphase.service</filename>.)</para></listitem>
-
- <listitem><para><literal>shutdown</literal> is extended into PCR 11 when system shutdown begins. It is
- supposed to act as barrier between the time the system is fully up and running and where it is about to
- shut down. (This string is extended at stop of
- <filename>systemd-pcrphase.service</filename>.)</para></listitem>
-
- <listitem><para><literal>final</literal> is extended into PCR 11 at the end of system shutdown. It is
- supposed to act as barrier between the time the service manager still runs and when it transitions into
- the final boot phase where service management is not available anymore. (This string is extended at
- stop of <filename>systemd-pcrphase-sysinit.service</filename>.)</para></listitem>
-
+ <listitem><para><literal>enter-initrd</literal> — early when the initrd initializes, before activating
+ system extension images for the initrd. It acts as a barrier between the time where the kernel
+ initializes and where the initrd starts operating and enables system extension images, i.e. code
+ shipped outside of the UKI. (This extension happens when
+ <filename>systemd-pcrphase-initrd.service</filename> is started.)</para></listitem>
+
+ <listitem><para><literal>leave-initrd</literal> — when the initrd is about to transition into the host
+ file system. It acts as barrier between initrd code and host OS code. (This extension happens when
+ <filename>systemd-pcrphase-initrd.service</filename> is stopped.)</para></listitem>
+
+ <listitem><para><literal>sysinit</literal> — when basic system initialization is complete (which
+ includes local file systems having been mounted), and the system begins starting regular system
+ services. (This extension happens when <filename>systemd-pcrphase-sysinit.service</filename> is
+ started.)</para></listitem>
+
+ <listitem><para><literal>ready</literal> — during later boot-up, after remote file systems have been
+ activated (i.e. after <filename>remote-fs.target</filename>), but before users are permitted to log in
+ (i.e. before <filename>systemd-user-sessions.service</filename>). It acts as barrier between the time
+ where unprivileged regular users are still prohibited to log in and where they are allowed to log in.
+ (This extension happens when <filename>systemd-pcrphase.service</filename> is started.)
+ </para></listitem>
+
+ <listitem><para><literal>shutdown</literal> — when the system shutdown begins. It acts as barrier
+ between the time the system is fully up and running and where it is about to shut down. (This extension
+ happens when <filename>systemd-pcrphase.service</filename> is stopped.)</para></listitem>
+
+ <listitem><para><literal>final</literal> — at the end of system shutdown. It acts as barrier between
+ the time the service manager still runs and when it transitions into the final shutdown phase where
+ service management is not available anymore. (This extension happens when
+ <filename>systemd-pcrphase-sysinit.service</filename> is stopped.)</para></listitem>
</orderedlist>
- <para>During a regular system lifecycle, the strings <literal>enter-initrd</literal> →
- <literal>leave-initrd</literal> → <literal>sysinit</literal> → <literal>ready</literal> →
- <literal>shutdown</literal> → <literal>final</literal> are extended into PCR 11, one after the
- other.</para>
+ <para>During a regular system lifecycle, PCR 11 is extended with the strings
+ <literal>enter-initrd</literal>, <literal>leave-initrd</literal>, <literal>sysinit</literal>,
+ <literal>ready</literal>, <literal>shutdown</literal>, and <literal>final</literal>.</para>
<para>Specific phases of the boot process may be referenced via the series of strings measured, separated
- by colons (the "boot path"). For example, the boot path for the regular system runtime is
+ by colons (the "phase path"). For example, the phase path for the regular system runtime is
<literal>enter-initrd:leave-initrd:sysinit:ready</literal>, while the one for the initrd is just
- <literal>enter-initrd</literal>. The boot path for the the boot phase before the initrd, is an empty
- string; because that's hard to pass around a single colon (<literal>:</literal>) may be used
- instead. Note that the aforementioned six strings are just the default strings and individual systems
- might measure other strings at other times, and thus implement different and more fine-grained boot
- phases to bind policy to.</para>
-
- <para>By binding policy of TPM2 objects to a specific boot path it is possible to restrict access to them
- to specific phases of the boot process, for example making it impossible to access the root file system's
- encryption key after the system transitioned from the initrd into the host root file system.</para>
+ <literal>enter-initrd</literal>. The phase path for the boot phase before the initrd is an empty string;
+ because that's hard to pass around a single colon (<literal>:</literal>) may be used instead. Note that
+ the aforementioned six strings are just the default strings and individual systems might measure other
+ strings at other times, and thus implement different and more fine-grained boot phases to bind policy
+ to.</para>
+
+ <para>By binding policy of TPM2 objects to a specific phase path it is possible to restrict access to
+ them to specific phases of the boot process, for example making it impossible to access the root file
+ system's encryption key after the system transitioned from the initrd into the host root file system.
+ </para>
<para>Use
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> to
- pre-calculate expected PCR 11 values for specific boot phases (via the <option>--phase=</option> switch).</para>
+ pre-calculate expected PCR 11 values for specific boot phases (via the <option>--phase=</option> switch).
+ </para>
+
+ <para><filename>systemd-pcrfs-root.service</filename> and <filename>systemd-pcrfs@.service</filename> are
+ automatically pulled into the initial transaction by
+ <citerefentry><refentrytitle>systemd-gpt-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ for the root and <filename>/var/</filename> file
+ systems. <citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ will do this for all mounts with the <option>x-systemd.pcrfs</option> mount option in
+ <filename>/etc/fstab</filename>.</para>
</refsect1>
<refsect1>
TPM2 device will cause the invocation to fail.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--machine-id</option></term>
+
+ <listitem><para>Instead of measuring a word specified on the command line into PCR 11, measure the
+ host's machine ID into PCR 15.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--file-system=</option></term>
+
+ <listitem><para>Instead of measuring a word specified on the command line into PCR 11, measure
+ identity information of the specified file system into PCR 15. The parameter must be the path to the
+ established mount point of the file system to measure.</para></listitem>
+ </varlistentry>
+
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
- <citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-gpt-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
<para>Most of <command>systemd-portabled</command>'s functionality is accessible through the
<citerefentry><refentrytitle>portablectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> command.</para>
- <para>See <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> for details about
- the concepts this service implements.</para>
+ <para>See the <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services Documentation</ulink>
+ for details about the concepts this service implements.</para>
</refsect1>
<refsect1>
<command>systemd-repart</command> was executed.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--sector-size=</option><arg>BYTES</arg></term>
+
+ <listitem><para>This option allows configuring the sector size of the image produced by
+ <command>systemd-repart</command>. It takes a value that is a power of <literal>2</literal> between
+ <literal>512</literal> and <literal>4096</literal>. This option is useful when building images for
+ disks that use a different sector size as the disk on which the image is produced.</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" />
<refsect1>
<title>Assembling Kernel Images</title>
- <para>In order to assemble an UEFI PE kernel image from various components as described above, use an
- <citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry> command line
- like this:</para>
-
- <programlisting>objcopy \
- --add-section .osrel=os-release --change-section-vma .osrel=0x20000 \
- --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
- --add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \
- --add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \
- --add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
- --add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \
- /usr/lib/systemd/boot/efi/linuxx64.efi.stub \
- foo-unsigned.efi</programlisting>
-
- <para>Note that these PE section offsets are example values and a properly assembled image must not
- contain any overlapping sections (this includes already existing sections inside the stub before
- assembly) or boot may fail.</para>
-
- <para>This generates one PE executable file <filename>foo-unsigned.efi</filename> from the six individual
- files for OS release information, kernel command line, boot splash image, kernel image, main initrd and
- UEFI boot stub.</para>
-
- <para>To then sign the resulting image for UEFI SecureBoot use an
- <citerefentry project='archlinux'><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry> command like
- the following:</para>
-
- <programlisting>sbsign \
- --key mykey.pem \
- --cert mykey.crt \
- --output foo.efi \
- foo-unsigned.efi</programlisting>
-
- <para>This expects a pair of X.509 private key and certificate as parameters and then signs the UEFI PE
- executable we generated above for UEFI SecureBoot and generates a signed UEFI PE executable as
- result.</para>
-
- <para>See
- <citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
- an example involving the <literal>.pcrsig</literal> and <literal>.pcrpkey</literal> sections.</para>
+ <para>In order to assemble a bootable Unified Kernel Image from various components as described above, use
+ <citerefentry><refentrytitle>ukify</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
</refsect1>
<refsect1>
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader Specification</ulink>,
<ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>,
- <citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
- <citerefentry project='archlinux'><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>ukify</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
suitable for shipping resources that are processed by subsystems running in earliest boot. Specifically,
OS extension images are not suitable for shipping system services or
<citerefentry><refentrytitle>systemd-sysusers</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- definitions. See <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> for a simple
- mechanism for shipping system services in disk images, in a similar fashion to OS extensions. Note the
- different isolation on these two mechanisms: while system extension directly extend the underlying OS
- image with additional files that appear in a way very similar to as if they were shipped in the OS image
- itself and thus imply no security isolation, portable services imply service level sandboxing in one way
- or another. The <filename>systemd-sysext.service</filename> service is guaranteed to finish start-up
- before <filename>basic.target</filename> is reached; i.e. at the time regular services initialize (those
- which do not use <varname>DefaultDependencies=no</varname>), the files and directories system extensions
- provide are available in <filename>/usr/</filename> and <filename>/opt/</filename> and may be
- accessed.</para>
+ definitions. See the <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services Documentation</ulink>
+ for a simple mechanism for shipping system services in disk images, in a similar fashion to OS
+ extensions. Note the different isolation on these two mechanisms: while system extension directly extend
+ the underlying OS image with additional files that appear in a way very similar to as if they were
+ shipped in the OS image itself and thus imply no security isolation, portable services imply service
+ level sandboxing in one way or another. The <filename>systemd-sysext.service</filename> service is
+ guaranteed to finish start-up before <filename>basic.target</filename> is reached; i.e. at the time
+ regular services initialize (those which do not use <varname>DefaultDependencies=no</varname>), the files
+ and directories system extensions provide are available in <filename>/usr/</filename> and
+ <filename>/opt/</filename> and may be accessed.</para>
<para>Note that there is no concept of enabling/disabling installed system extension images: all
installed extension images are automatically activated at boot. However, you can place an empty directory
<refsect1>
<title>Description</title>
- <para><filename>systemd-veritysetup-generator</filename> is a generator that translates kernel command line options
- configuring verity protected block devices into native systemd units early at boot and when
+ <para><command>systemd-veritysetup-generator</command> is a generator that translates kernel command line
+ options configuring verity protected block devices into native systemd units early at boot and when
configuration of the system manager is reloaded. This will create
<citerefentry><refentrytitle>systemd-veritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
units as necessary.</para>
<para>Currently, only two verity devices may be set up with this generator, backing the root and <filename>/usr</filename> file systems of the
OS.</para>
- <para><filename>systemd-veritysetup-generator</filename> implements
+ <para><command>systemd-veritysetup-generator</command> implements
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</refsect1>
<refsect1>
<title>Kernel Command Line</title>
- <para><filename>systemd-veritysetup-generator</filename>
+ <para><command>systemd-veritysetup-generator</command>
understands the following kernel command line parameters:</para>
<variablelist class='kernel-commandline-options'>
<term><varname>systemd.verity_usr_hash=</varname></term>
<term><varname>systemd.verity_usr_options=</varname></term>
- <listitem><para>Equivalent to their counterparts for the root file system as described above, but apply to the <filename>/usr/</filename> file system instead.</para></listitem>
+ <listitem><para>Equivalent to their counterparts for the root file system as described above, but
+ apply to the <filename>/usr/</filename> file system instead.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
stateless systems.</para>
<para>This service is only enabled if full volatile mode is selected, for example by specifying
- <literal>systemd.volatile=yes</literal> on the kernel command line. This service runs only in the initrdyes,
+ <literal>systemd.volatile=yes</literal> on the kernel command line. This service runs only in the initrd,
before the system transitions to the host's root directory. Note that this service is not used if
- <literal>systemd.volatile=state</literal> is used, as in that mode the root directory is
- non-volatile.</para>
+ <literal>systemd.volatile=state</literal> is used, as in that mode the root directory is non-volatile.
+ </para>
</refsect1>
<refsect1>
<para>Note that this setting only has an effect on the unit's processes themselves (or any processes
directly or indirectly forked off them). It has no effect on processes potentially invoked on request
of them through tools such as <citerefentry
- project='man-pages'><refentrytitle>at</refentrytitle><manvolnum>1p</manvolnum></citerefentry>,
+ project='man-pages'><refentrytitle>at</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry
- project='man-pages'><refentrytitle>crontab</refentrytitle><manvolnum>1p</manvolnum></citerefentry>,
+ project='man-pages'><refentrytitle>crontab</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>, or
arbitrary IPC services.</para></listitem>
</varlistentry>
<entry>LimitNOFILE=</entry>
<entry>ulimit -n</entry>
<entry>Number of File Descriptors</entry>
- <entry>Don't use. Be careful when raising the soft limit above 1024, since <function>select()</function> cannot function with file descriptors above 1023 on Linux. Nowadays, the hard limit defaults to 524288, a very high value compared to historical defaults. Typically applications should increase their soft limit to the hard limit on their own, if they are OK with working with file descriptors above 1023, i.e. do not use <function>select()</function>. Note that file descriptors are nowadays accounted like any other form of memory, thus there should not be any need to lower the hard limit. Use <varname>MemoryMax=</varname> to control overall service memory use, including file descriptor memory.</entry>
+ <entry>Don't use. Be careful when raising the soft limit above 1024, since <citerefentry project='man-pages'><refentrytitle>select</refentrytitle><manvolnum>2</manvolnum></citerefentry> cannot function with file descriptors above 1023 on Linux. Nowadays, the hard limit defaults to 524288, a very high value compared to historical defaults. Typically applications should increase their soft limit to the hard limit on their own, if they are OK with working with file descriptors above 1023, i.e. do not use <citerefentry project='man-pages'><refentrytitle>select</refentrytitle><manvolnum>2</manvolnum></citerefentry>. Note that file descriptors are nowadays accounted like any other form of memory, thus there should not be any need to lower the hard limit. Use <varname>MemoryMax=</varname> to control overall service memory use, including file descriptor memory.</entry>
</row>
<row>
<entry>LimitAS=</entry>
<listitem><para>Controls which kernel architecture <citerefentry
project='man-pages'><refentrytitle>uname</refentrytitle><manvolnum>2</manvolnum></citerefentry> shall report,
when invoked by unit processes. Takes one of the architecture identifiers <constant>x86</constant>,
- <constant>x86-64</constant>, <constant>ppc</constant>, <constant>ppc-le</constant>, <constant>ppc64</constant>,
- <constant>ppc64-le</constant>, <constant>s390</constant> or <constant>s390x</constant>. Which personality
- architectures are supported depends on the system architecture. Usually the 64bit versions of the various
+ <constant>x86-64</constant>, <constant>arm64</constant>, <constant>arm64-be</constant>, <constant>arm</constant>,
+ <constant>arm-be</constant>, <constant>alpha</constant>, <constant>arc</constant>, <constant>arc-be</constant>,
+ <constant>cris</constant>, <constant>ia64</constant>, <constant>loongarch64</constant>, <constant>m68k</constant>,
+ <constant>mips64-le</constant>, <constant>mips64</constant>, <constant>mips-le</constant>, <constant>mips</constant>,
+ <constant>nios2</constant>, <constant>parisc64</constant>, <constant>parisc</constant>, <constant>ppc64-le</constant>,
+ <constant>ppc64</constant>, <constant>ppc</constant>, <constant>ppc-le</constant>, <constant>riscv32</constant>,
+ <constant>riscv64</constant>, <constant>s390x</constant>, <constant>s390</constant>, <constant>sh64</constant>,
+ <constant>sh</constant>, <constant>sparc64</constant>, <constant>sparc</constant> or <constant>tilegx</constant>.
+ Which personality architectures are supported depends on the system architecture. Usually the 64bit versions of the various
system architectures support their immediate 32bit personality architecture counterpart, but no others. For
example, <constant>x86-64</constant> systems support the <constant>x86-64</constant> and
<constant>x86</constant> personalities but no others. The personality feature is useful when running 32-bit
<para>Note that this setting might not be supported on some systems (for example if the LSM eBPF hook is
not enabled in the underlying kernel or if not using the unified control group hierarchy). In that case this setting
- has no effect.</para></listitem>
+ has no effect.</para>
+
+ <xi:include href="cgroup-sandboxing.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
11) with a prefix of <literal>io.systemd.credential:</literal> or
<literal>io.systemd.credential.binary:</literal>. In both cases a key/value pair separated by
<literal>=</literal> is expected, in the latter case the right-hand side is Base64 decoded when
- parsed (thus permitting binary data to be passed in). Example qemu switch: <literal>-smbios
+ parsed (thus permitting binary data to be passed in). Example
+ <ulink url="https://www.qemu.org/docs/master/system/index.html">qemu</ulink>
+ switch: <literal>-smbios
type=11,value=io.systemd.credential:xx=yy</literal>, or <literal>-smbios
type=11,value=io.systemd.credential.binary:rick=TmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXA=</literal>. Alternatively,
use the <command>qemu</command> <literal>fw_cfg</literal> node
- <literal>opt/io.systemd.credentials/</literal>. Example qemu switch: <literal>-fw_cfg
+ <literal>opt/io.systemd.credentials/</literal>. Example <command>qemu</command> switch: <literal>-fw_cfg
name=opt/io.systemd.credentials/mycred,string=supersecret</literal>. They may also be specified on
the kernel command line using the <literal>systemd.set_credential=</literal> switch (see
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>) and from
links.</para>
<programlisting>[Link]
-NamePolicy=kernel database onboard slot path
+NamePolicy=kernel database on-board slot path
MACAddressPolicy=persistent</programlisting>
</example>
<varname>Options=</varname> setting in a unit file.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>x-systemd.pcrfs</option></term>
+
+ <listitem><para>Measures file system identity information (mount point, type, label, UUID, partition
+ label, partition UUID) into PCR 15 after the file system has been mounted. This ensures the
+ <citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ or <filename>systemd-pcrfs-root.service</filename> services are pulled in by the mount unit.</para>
+
+ <para>Note that this option can only be used in <filename>/etc/fstab</filename>, and will be ignored
+ when part of the <varname>Options=</varname> setting in a unit file. It is also implied for the root
+ and <filename>/usr/</filename> partitions discovered by
+ <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>x-systemd.rw-only</option></term>
<term><varname>Where=</varname></term>
<listitem><para>Takes an absolute path of a file or directory for the mount point; in particular, the
destination cannot be a symbolic link. If the mount point does not exist at the time of mounting, it
- is created as directory. This string must be reflected in the unit filename. (See above.) This option
+ is created as either a directory or a file. The former is the usual case; the latter is done only if this mount
+ is a bind mount and the source (<varname>What=</varname>) is not a directory.
+ This string must be reflected in the unit filename. (See above.) This option
is mandatory.</para></listitem>
</varlistentry>
may trigger the start of the DHCPv6 client if the relevant flags are set in the RA data, or
if no routers are found on the link. The default is to disable RA reception for bridge
devices or when IP forwarding is enabled, and to enable it otherwise. Cannot be enabled on
- bond devices and when link-local addressing is disabled.</para>
+ devices aggregated in a bond device or when link-local addressing is disabled.</para>
<para>Further settings for the IPv6 RA support may be configured in the [IPv6AcceptRA]
section, see below.</para>
<varlistentry>
<term><varname>QuickAck=</varname></term>
<listitem>
- <para>Takes a boolean. When true enables TCP quick ack mode for the route. When unset, the
- kernel's default will be used.</para>
+ <para>Takes a boolean. When true, the TCP quick ACK mode for the route is enabled. When unset,
+ the kernel's default will be used.</para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>SocketPriority=</varname></term>
+ <listitem>
+ <para>The Linux socket option <constant>SO_PRIORITY</constant> applied to the raw IP socket used for
+ initial DHCPv4 messages. Unset by default. Usual values range from 0 to 6.
+ More details about <constant>SO_PRIORITY</constant> socket option in
+ <citerefentry project='man-pages'><refentrytitle>socket</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+ Can be used in conjunction with [VLAN] section <varname>EgressQOSMaps=</varname> setting of .netdev
+ file to set the 802.1Q VLAN ethernet tagged header priority, see
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<!-- How to use the DHCP lease -->
<varlistentry>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>QuickAck=</varname></term>
+ <listitem>
+ <para>Takes a boolean. When true, the TCP quick ACK mode is enabled for the routes configured by
+ the acquired DHCPv4 lease. When unset, the kernel's default will be used.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>UseGateway=</varname></term>
<listitem>
<term><varname>UseHostname=</varname></term>
<term><varname>UseDomains=</varname></term>
<term><varname>NetLabel=</varname></term>
+ <term><varname>SendRelease=</varname></term>
<listitem>
<para>As in the [DHCPv4] section.</para>
</listitem>
<refsect1>
<title>[DHCPPrefixDelegation] Section Options</title>
<para>The [DHCPPrefixDelegation] section configures subnet prefixes of the delegated prefixes
- acquired by a DHCPv6 client, or by a DHCPv4 client through the 6RD option on another interface.
+ acquired by a DHCPv6 client or by a DHCPv4 client through the 6RD option on another interface.
The settings in this section are used only when the <varname>DHCPPrefixDelegation=</varname>
setting in the [Network] section is enabled.</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>QuickAck=</varname></term>
+ <listitem>
+ <para>Takes a boolean. When true, the TCP quick ACK mode is enabled for the routes configured by
+ the received RAs. When unset, the kernel's default will be used.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>UseMTU=</varname></term>
<listitem>
<title>See Also</title>
<para>Environment variables with details on the trigger will be set for triggered units. See the
<literal>Environment Variables Set on Triggered Units</literal> section in
- <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more details.</para>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
support is not enabled in the underlying kernel or container manager). These settings will have no effect in
that case. If compatibility with such systems is desired it is hence recommended to not exclusively rely on
them for IP security.</para>
+
+ <xi:include href="cgroup-sandboxing.xml" xpointer="singular"/>
</listitem>
</varlistentry>
SocketBindAllow=ipv4:udp:10000-65535
SocketBindDeny=any
…</programlisting></para>
+
+ <xi:include href="cgroup-sandboxing.xml" xpointer="singular"/>
</listitem>
</varlistentry>
RestrictNetworkInterfaces=~eth1</programlisting>
Programs in the unit will be only able to use the eth2 network interface.
</para>
+
+ <xi:include href="cgroup-sandboxing.xml" xpointer="singular"/>
</listitem>
</varlistentry>
DeviceAllow=/dev/loop-control
…</programlisting></para>
+ <xi:include href="cgroup-sandboxing.xml" xpointer="singular"/>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
</variablelist>
+
+ <xi:include href="cgroup-sandboxing.xml" xpointer="singular"/>
</listitem>
</varlistentry>
<varlistentry>
<term>systemd 252</term>
<listitem><para> Options for controlling the Legacy Control Group Hierarchy (<ulink
- url="https://docs.kernel.org/admin-guide/cgroup-v1/index.html">Control Groups version 1</ulink> are
- now fully deprecated: <varname>CPUShares=<replaceable>weight</replaceable></varname>,
+ url="https://docs.kernel.org/admin-guide/cgroup-v1/index.html">Control Groups version 1</ulink>)
+ are now fully deprecated:
+ <varname>CPUShares=<replaceable>weight</replaceable></varname>,
<varname>StartupCPUShares=<replaceable>weight</replaceable></varname>,
<varname>MemoryLimit=<replaceable>bytes</replaceable></varname>,
<varname>BlockIOAccounting=</varname>,
<replaceable>weight</replaceable></varname>,
<varname>BlockIOReadBandwidth=<replaceable>device</replaceable>
<replaceable>bytes</replaceable></varname>,
- <varname>BlockIOWriteBandwidth=<replaceable>device</replaceable>
- <replaceable>bytes</replaceable></varname>.
+ <varname>BlockIOWriteBandwidth=<replaceable>device</replaceable> <replaceable>bytes</replaceable></varname>.
Please switch to the unified cgroup hierarchy.</para></listitem>
</varlistentry>
</variablelist>
<row>
<entry><literal>+</literal></entry>
- <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines).</entry>
+ <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines). However, note that this will not bypass options that apply to the whole control group, such as <varname>DevicePolicy=</varname>, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the full list.</entry>
</row>
<row>
<programlisting>ExecReload=kill -HUP $MAINPID</programlisting>
- <para>Note however that reloading a daemon by enqueing a signal (as with the example line above) is
+ <para>Note however that reloading a daemon by enqueuing a signal (as with the example line above) is
usually not a good choice, because this is an asynchronous operation and hence not suitable when
ordering reloads of multiple services against each other. It is thus strongly recommended to either
use <varname>Type=</varname><option>notify-reload</option> in place of
Note that in case the hypervisor does not support <constant>SOCK_DGRAM</constant>
over <constant>AF_VSOCK</constant>, <constant>SOCK_SEQPACKET</constant> will be
tried instead. The credential payload for <constant>AF_VSOCK</constant> should be
- in the form: <literal>vsock:CID:PORT</literal>, where <literal>CID</literal> is
- optional and if omitted will default to talking to the hypervisor
- (<constant>0</constant>).</para>
+ in the form: <literal>vsock:CID:PORT</literal>.</para>
</listitem>
</varlistentry>
<title>See Also</title>
<para>Environment variables with details on the trigger will be set for triggered units. See the
<literal>Environment Variables Set on Triggered Units</literal> section in
- <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more details.</para>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
</para></listitem>
<listitem><para><literal>device-tree-compatible(<replaceable>value</replaceable>)</literal>
- matches systems with a device tree that is compatible with <literal>value</literal>.
+ matches systems with a device tree that are compatible with <literal>value</literal>.
</para></listitem>
<listitem><para><literal>smbios-field(<replaceable>field</replaceable>
<refsect1>
<title>[Transfer] Section Options</title>
- <para>This section defines general properties of this transfer:</para>
+ <para>This section defines general properties of this transfer.</para>
<variablelist>
<varlistentry>
<refsect1>
<title>[Source] Section Options</title>
- <para>This section defines properties of the transfer source:</para>
+ <para>This section defines properties of the transfer source.</para>
<variablelist>
<varlistentry>
<refsect1>
<title>[Target] Section Options</title>
- <para>This section defines properties of the transfer target:</para>
+ <para>This section defines properties of the transfer target.</para>
<variablelist>
<varlistentry>
<constant>subvolume</constant>. For details about the resource types, see above. This option is
mandatory.</para>
- <para>Note that only some combinations of source and target resource types are supported, see above.</para></listitem>
+ <para>Note that only certain combinations of source and target resource types are supported, see
+ above.</para></listitem>
</varlistentry>
<varlistentry>
<ulink url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable Partitions
Specification</ulink>, similar to the <varname>PartitionNoAuto=</varname> and
<varname>PartitionGrowFileSystem=</varname> flags described above. If the target type is
- <constant>regular-file</constant>, the writable bit is removed from the access mode. If the the
+ <constant>regular-file</constant>, the writable bit is removed from the access mode. If the
target type is <constant>subvolume</constant>, the subvolume will be marked read-only as a
whole. Finally, if the target <varname>Type=</varname> is selected as <constant>directory</constant>,
the "immutable" file attribute is set, see <citerefentry
<para>If the target <varname>Type=</varname> is selected as <constant>partition</constant>, the number
of concurrent versions to keep is additionally restricted by the number of partition slots of the
- right type in the partition table. i.e. if there are only 2 partition slots for the selected
+ right type in the partition table. I.e. if there are only 2 partition slots for the selected
partition type, setting this value larger than 2 is without effect, since no more than 2 concurrent
versions could be stored in the image anyway.</para></listitem>
</varlistentry>
<filename>/sys/</filename> or <filename>/proc/</filename>, as well as some other directories below
<filename>/var/</filename>).</para>
- <para><command>systemd-tmpfiles</command> uses this configuration to create volatile files and
- directories during boot and to do periodic cleanup afterwards. See
+ <para><citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ uses this configuration to create volatile files and directories during boot and to do periodic cleanup
+ afterwards. See
<citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
the description of <filename>systemd-tmpfiles-setup.service</filename>,
<filename>systemd-tmpfiles-clean.service</filename>, and associated units.</para>
<title>Return Value</title>
<para>On success, <function>udev_enumerate_new()</function> returns a
- pointer to the allocated udev monitor. On failure,
+ pointer to the allocated enumeration object. On failure,
<constant>NULL</constant> is returned.
<function>udev_enumerate_ref()</function> returns the argument
that it was passed, unmodified.
the "whole" block device in case a partition block device is specified. The devices will be sorted
by their device node major number as primary ordering key and the minor number as secondary
ordering key (i.e. they are shown in the order they'd be locked). Note that the number of lines
- printed here can be less than the the number of <option>--device=</option> and
+ printed here can be less than the number of <option>--device=</option> and
<option>--backing=</option> switches specified in case these resolve to the same "whole"
devices.</para></listitem>
</varlistentry>
############################################################
-python = find_program('python3')
-if run_command(python, '-c', 'import jinja2', check : false).returncode() != 0
- error('python3 jinja2 missing')
-endif
-
-python_310 = run_command(python, '-c',
- 'import sys; sys.exit(0 if sys.version_info >= (3,10) else 1)',
- check : false).returncode() == 0
-if get_option('ukify') == 'auto'
- want_ukify = python_310
-elif get_option('ukify') == 'true' and not python310
- error('ukify requires Python >= 3.10')
-else
- want_ukify = get_option('ukify') == 'true'
-endif
-
-############################################################
-
gperf = find_program('gperf')
gperf_test_format = '''
############################################################
+python = find_program('python3')
+if run_command(python, '-c', 'import jinja2', check : false).returncode() != 0
+ error('python3 jinja2 missing')
+endif
+
+python_39 = run_command(python, '-c',
+ 'import sys; sys.exit(0 if sys.version_info >= (3,9) else 1)',
+ check : false).returncode() == 0
+if get_option('ukify') == 'auto'
+ want_ukify = python_39 and conf.get('HAVE_GNU_EFI') == 1
+elif get_option('ukify') == 'true' and (not python_39 or conf.get('HAVE_GNU_EFI') != 1)
+ error('ukify requires Python >= 3.9 and GNU EFI')
+else
+ want_ukify = get_option('ukify') == 'true'
+endif
+
+############################################################
+
# binaries that have --help and are intended for use by humans,
# usually, but not always, installed in /bin.
public_programs = []
'src/boot/bootctl-status.h',
'src/boot/bootctl-systemd-efi-options.c',
'src/boot/bootctl-systemd-efi-options.h',
+ 'src/boot/bootctl-uki.c',
+ 'src/boot/bootctl-uki.h',
'src/boot/bootctl-util.c',
'src/boot/bootctl-util.h'],
include_directories : includes,
link_with : [libshared],
dependencies : [libopenssl,
tpm2,
+ libblkid,
versiondep],
install_rpath : rootpkglibdir,
install : true,
link_with : [libshared],
dependencies : [libcryptsetup,
libp11kit,
- versiondep],
+ versiondep,
+ libopenssl],
install_rpath : rootpkglibdir,
install : true,
install_dir : rootlibexecdir)
if want_tests != 'false' and want_kernel_install
test('test-kernel-install',
test_kernel_install_sh,
+ env : test_env,
args : [exe.full_path(), loaderentry_install])
endif
rootprefix=/${rootprefix#/}
fi
- meson "$BUILDDIR" \
+ meson setup "$BUILDDIR" \
-D "sysvinit-path=$sysvinit_path" \
-D "rootprefix=$rootprefix" \
-D man=false \
# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
[Distribution]
-Distribution=centos_epel
-
-[Output]
-Format=gpt_xfs
-HostonlyInitrd=no
+Distribution=centos
+Repositories=epel
[Content]
Packages=
enable systemd-userdbd.socket
enable systemd-pstore.service
enable systemd-boot-update.service
+enable systemd-journald-audit.socket
disable console-getty.service
disable debug-shell.service
local i verb comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
local -A OPTS=(
- [STANDALONE]='-h --help -p --print-esp-path -x --print-boot-path --version --no-variables --no-pager --graceful'
+ [STANDALONE]='-h --help -p --print-esp-path -x --print-boot-path --version --no-variables --no-pager --graceful --dry-run'
[ARG]='--esp-path --boot-path --make-machine-id-directory --root --image --install-source'
)
local -A VERBS=(
# systemd-efi-options takes an argument, but it is free-form, so we cannot complete it
- [STANDALONE]='help status install update remove is-installed random-seed systemd-efi-options list set-timeout set-timeout-oneshot'
- [BOOTENTRY]='set-default set-oneshot'
+ [STANDALONE]='help status install update remove is-installed random-seed systemd-efi-options list set-timeout set-timeout-oneshot cleanup'
+ [BOOTENTRY]='set-default set-oneshot unlink'
[BOOLEAN]='reboot-to-firmware'
)
[STANDALONE]='--all -a --reverse --after --before --defaults --force -f --full -l --global
--help -h --no-ask-password --no-block --legend=no --no-pager --no-reload --no-wall --now
--quiet -q --system --user --version --runtime --recursive -r --firmware-setup
- --show-types --plain --failed --value --fail --dry-run --wait'
+ --show-types --plain --failed --value --fail --dry-run --wait --no-warn'
[ARG]='--host -H --kill-whom --property -p --signal -s --type -t --state --job-mode --root
--preset-mode -n --lines -o --output -M --machine --message --timestamp --check-inhibitors'
)
)
local -A VERBS=(
- [STANDALONE]='time blame plot unit-paths exit-status calendar timestamp timespan'
+ [STANDALONE]='time blame unit-paths exit-status calendar timestamp timespan'
[CRITICAL_CHAIN]='critical-chain'
[DOT]='dot'
[DUMP]='dump'
[SECURITY]='security'
[CONDITION]='condition'
[INSPECT_ELF]='inspect-elf'
+ [PLOT]='plot'
)
local CONFIGS='systemd/bootchart.conf systemd/coredump.conf systemd/journald.conf
comps=$( compgen -A file -- "$cur" )
compopt -o filenames
fi
+
+ elif __contains_word "$verb" ${VERBS[PLOT]}; then
+ if [[ $cur = -* ]]; then
+ comps='--help --version --system --user --global --no-pager --json=off --json=pretty --json=short --table --no-legend'
+ fi
fi
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
_bootctl_comp_ids
}
+_bootctl_unlink() {
+ _bootctl_comp_ids
+}
+
_bootctl_reboot-to-firmware() {
local -a _completions
_completions=( yes no )
"set-oneshot:Set the default boot loader entry only for the next boot"
"set-timeout:Set the menu timeout"
"set-timeout-oneshot:Set the menu timeout for the next boot only"
+ "unlink:Remove boot loader entry"
+ "cleanup:Remove files in ESP not referenced in any boot entry"
)
if (( CURRENT == 1 )); then
_describe -t commands 'bootctl command' _bootctl_cmds || compadd "$@"
'--no-variables[Do not touch EFI variables]' \
'--no-pager[Do not pipe output into a pager]' \
'--graceful[Do not fail when locating ESP or writing fails]' \
+ '--dry-run[Dry run (unlink and cleanup)]' \
'--root=[Operate under the specified directory]:PATH' \
'--image=[Operate on the specified image]:PATH' \
'--install-source[Where to pick files when using --root=/--image=]:options:(image host auto)' \
'--show-types[When showing sockets, show socket type]' \
'--check-inhibitors[Specify if inhibitors should be checked]:mode:_systemctl_check_inhibitors' \
{-q,--quiet}'[Suppress output]' \
+ '--no-warn[Suppress several warnings shown by default]' \
'--no-block[Do not wait until operation finished]' \
'--legend=no[Do not print a legend, i.e. the column headers and the footer with hints]' \
'--no-pager[Do not pipe output into a pager]' \
_describe -t groups 'file system groups' _groups || compadd "$@"
}
+(( $+functions[_systemd-analyze_plot] )) ||
+ _systemd-analyze_plot() {
+ local -a _options
+ _options=( '--json=off' '--json=pretty' '--json=short' '--table' '--no-legend' )
+ _describe 'plot options' _options
+ }
+
(( $+functions[_systemd-analyze_commands] )) ||
_systemd-analyze_commands(){
local -a _systemd_analyze_cmds
'time:Print time spent in the kernel before reaching userspace'
'blame:Print list of running units ordered by time to init'
'critical-chain:Print a tree of the time critical chain of units'
- 'plot:Output SVG graphic showing service initialization'
+ 'plot:Output SVG graphic showing service initialization, or raw time data in
+JSON or table format'
'dot:Dump dependency graph (in dot(1) format)'
'dump:Dump server status'
'cat-config:Cat systemd config files'
'--offline=[Perform a security review of the specified unit files]:BOOL:(yes no)' \
'--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \
'--security-policy=[Use customized requirements to compare unit files against]: PATH' \
- '--json=[Generate a JSON output of the security analysis table]:MODE:(pretty short off)' \
+ "--json=[Generate a JSON output of the security analysis table or plot's raw time data]:MODE:(pretty short off)" \
+ "--table=[Generate a table of plot's raw time data]" \
'--profile=[Include the specified profile in the security review of units]: PATH' \
'--no-pager[Do not pipe output into a pager]' \
+ "--no-legend[Do not show the headers and footers for plot's raw time data formats]" \
'--man=[Do (not) check for existence of man pages]:BOOL:(yes no)' \
'--generators=[Do (not) run unit generators]:BOOL:(yes no)' \
'--order[When generating graph for dot, show only order]' \
#include "analyze-time-data.h"
#include "bus-error.h"
#include "bus-map-properties.h"
+#include "format-table.h"
#include "sort-util.h"
#include "version.h"
char *architecture;
} HostInfo;
-static HostInfo* free_host_info(HostInfo *hi) {
+static HostInfo *free_host_info(HostInfo *hi) {
if (!hi)
return NULL;
}
r = bus_map_all_properties(
- system_bus ?: bus,
+ system_bus ? : bus,
"org.freedesktop.hostname1",
"/org/freedesktop/hostname1",
hostname_map,
SCALE_Y * height);
}
}
-
static int plot_unit_times(UnitTimes *u, double width, int y) {
bool b;
if (!u->name)
return 0;
- svg_bar("activating", u->activating, u->activated, y);
- svg_bar("active", u->activated, u->deactivating, y);
+ svg_bar("activating", u->activating, u->activated, y);
+ svg_bar("active", u->activated, u->deactivating, y);
svg_bar("deactivating", u->deactivating, u->deactivated, y);
/* place the text on the left if we have passed the half of the svg width */
return 1;
}
-int verb_plot(int argc, char *argv[], void *userdata) {
- _cleanup_(free_host_infop) HostInfo *host = NULL;
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
- _cleanup_free_ char *pretty_times = NULL;
- bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM;
- BootTimes *boot;
+static void limit_times_to_boot(const BootTimes *boot, UnitTimes *u) {
+ if (u->deactivated > u->activating && u->deactivated <= boot->finish_time && u->activated == 0
+ && u->deactivating == 0)
+ u->activated = u->deactivating = u->deactivated;
+ if (u->activated < u->activating || u->activated > boot->finish_time)
+ u->activated = boot->finish_time;
+ if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
+ u->deactivating = boot->finish_time;
+ if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
+ u->deactivated = boot->finish_time;
+}
+
+static int produce_plot_as_svg(
+ UnitTimes *times,
+ const HostInfo *host,
+ const BootTimes *boot,
+ const char *pretty_times) {
+ int m = 1, y = 0;
UnitTimes *u;
- int n, m = 1, y = 0, r;
double width;
- r = acquire_bus(&bus, &use_full_bus);
- if (r < 0)
- return bus_log_connect_error(r, arg_transport);
-
- n = acquire_boot_times(bus, &boot);
- if (n < 0)
- return n;
-
- n = pretty_boot_time(bus, &pretty_times);
- if (n < 0)
- return n;
-
- if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) {
- n = acquire_host_info(bus, &host);
- if (n < 0)
- return n;
- }
-
- n = acquire_time_data(bus, ×);
- if (n <= 0)
- return n;
-
- typesafe_qsort(times, n, compare_unit_start);
-
width = SCALE_X * (boot->firmware_time + boot->finish_time);
if (width < 800.0)
width = 800.0;
if (text_width > text_start && text_width + text_start > width)
width = text_width + text_start;
- if (u->deactivated > u->activating &&
- u->deactivated <= boot->finish_time &&
- u->activated == 0 && u->deactivating == 0)
- u->activated = u->deactivating = u->deactivated;
- if (u->activated < u->activating || u->activated > boot->finish_time)
- u->activated = boot->finish_time;
- if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
- u->deactivating = boot->finish_time;
- if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
- u->deactivated = boot->finish_time;
+ limit_times_to_boot(boot, u);
+
m++;
}
svg("</svg>\n");
+ return 0;
+}
+
+static int show_table(Table *table, const char *word) {
+ int r;
+
+ assert(table);
+ assert(word);
+
+ if (table_get_rows(table) > 1) {
+ table_set_header(table, arg_legend);
+
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ r = table_print_json(table, NULL, arg_json_format_flags | JSON_FORMAT_COLOR_AUTO);
+ else
+ r = table_print(table, NULL);
+ if (r < 0)
+ return table_log_print_error(r);
+ }
+
+ if (arg_legend) {
+ if (table_get_rows(table) > 1)
+ printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
+ else
+ printf("No %s.\n", word);
+ }
+
+ return 0;
+}
+
+static int produce_plot_as_text(UnitTimes *times, const BootTimes *boot) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ int r;
+
+ table = table_new("name", "activated", "activating", "time", "deactivated", "deactivating");
+ if (!table)
+ return log_oom();
+
+ for (; times->has_data; times++) {
+ limit_times_to_boot(boot, times);
+
+ r = table_add_many(
+ table,
+ TABLE_STRING, times->name,
+ TABLE_TIMESPAN_MSEC, times->activated,
+ TABLE_TIMESPAN_MSEC, times->activating,
+ TABLE_TIMESPAN_MSEC, times->time,
+ TABLE_TIMESPAN_MSEC, times->deactivated,
+ TABLE_TIMESPAN_MSEC, times->deactivating);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ return show_table(table, "Units");
+}
+
+int verb_plot(int argc, char *argv[], void *userdata) {
+ _cleanup_(free_host_infop) HostInfo *host = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
+ _cleanup_free_ char *pretty_times = NULL;
+ bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM;
+ BootTimes *boot;
+ int n, r;
+
+ r = acquire_bus(&bus, &use_full_bus);
+ if (r < 0)
+ return bus_log_connect_error(r, arg_transport);
+
+ n = acquire_boot_times(bus, &boot);
+ if (n < 0)
+ return n;
+
+ n = pretty_boot_time(bus, &pretty_times);
+ if (n < 0)
+ return n;
+
+ if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) {
+ n = acquire_host_info(bus, &host);
+ if (n < 0)
+ return n;
+ }
+
+ n = acquire_time_data(bus, ×);
+ if (n <= 0)
+ return n;
+
+ typesafe_qsort(times, n, compare_unit_start);
+
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) || arg_table)
+ r = produce_plot_as_text(times, boot);
+ else
+ r = produce_plot_as_svg(times, host, boot, pretty_times);
+ if (r < 0)
+ return r;
+
return EXIT_SUCCESS;
}
JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
bool arg_quiet = false;
char *arg_profile = NULL;
+bool arg_legend = true;
+bool arg_table = false;
STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
" --security-policy=PATH Use custom JSON security policy instead\n"
" of built-in one\n"
" --json=pretty|short|off Generate JSON output of the security\n"
- " analysis table\n"
+ " analysis table, or plot's raw time data\n"
" --no-pager Do not pipe output into a pager\n"
+ " --no-legend Disable column headers and hints in plot\n"
+ " with either --table or --json=\n"
" --system Operate on system systemd instance\n"
" --user Operate on user systemd instance\n"
" --global Operate on global user configuration\n"
" specified time\n"
" --profile=name|PATH Include the specified profile in the\n"
" security review of the unit(s)\n"
+ " --table Output plot's raw time data as a table\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -q --quiet Do not emit hints\n"
ARG_SECURITY_POLICY,
ARG_JSON,
ARG_PROFILE,
+ ARG_TABLE,
+ ARG_NO_LEGEND,
};
static const struct option options[] = {
{ "unit", required_argument, NULL, 'U' },
{ "json", required_argument, NULL, ARG_JSON },
{ "profile", required_argument, NULL, ARG_PROFILE },
+ { "table", optional_argument, NULL, ARG_TABLE },
+ { "no-legend", optional_argument, NULL, ARG_NO_LEGEND },
{}
};
r = safe_atou(optarg, &arg_iterations);
if (r < 0)
return log_error_errno(r, "Failed to parse iterations: %s", optarg);
-
break;
case ARG_BASE_TIME:
r = parse_timestamp(optarg, &arg_base_time);
if (r < 0)
return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg);
-
break;
case ARG_PROFILE:
free_and_replace(arg_unit, mangled);
break;
}
+
+ case ARG_TABLE:
+ arg_table = true;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
case '?':
return -EINVAL;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --offline= is only supported for security right now.");
- if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf"))
+ if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Option --json= is only supported for security and inspect-elf right now.");
+ "Option --json= is only supported for security, inspect-elf, and plot right now.");
if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used.");
+ if ((!arg_legend && !streq_ptr(argv[optind], "plot")) ||
+ (streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --no-legend is only supported for plot with either --table or --json=.");
+
+ if (arg_table && !streq_ptr(argv[optind], "plot"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now.");
+
+ if (arg_table && !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive.");
+
return 1; /* work to do */
}
extern JsonFormatFlags arg_json_format_flags;
extern bool arg_quiet;
extern char *arg_profile;
+extern bool arg_legend;
+extern bool arg_table;
int acquire_bus(sd_bus **bus, bool *use_full_bus);
return 0;
}
+
+int chase_symlinks_and_unlink(
+ const char *path,
+ const char *root,
+ ChaseSymlinksFlags chase_flags,
+ int unlink_flags,
+ char **ret_path) {
+
+ _cleanup_free_ char *p = NULL, *rp = NULL, *dir = NULL, *fname = NULL;
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ assert(path);
+
+ r = path_extract_directory(path, &dir);
+ if (r < 0)
+ return r;
+ r = path_extract_filename(path, &fname);
+ if (r < 0)
+ return r;
+
+ fd = chase_symlinks_and_open(dir, root, chase_flags, O_PATH|O_DIRECTORY|O_CLOEXEC, ret_path ? &p : NULL);
+ if (fd < 0)
+ return fd;
+
+ if (p) {
+ rp = path_join(p, fname);
+ if (!rp)
+ return -ENOMEM;
+ }
+
+ if (unlinkat(fd, fname, unlink_flags) < 0)
+ return -errno;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(rp);
+
+ return 0;
+}
int chase_symlinks_and_stat(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, struct stat *ret_stat, int *ret_fd);
int chase_symlinks_and_access(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int access_mode, char **ret_path, int *ret_fd);
int chase_symlinks_and_fopen_unlocked(const char *path, const char *root, ChaseSymlinksFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_symlinks_and_unlink(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path);
int chase_symlinks_at(int dir_fd, const char *path, ChaseSymlinksFlags flags, char **ret_path, int *ret_fd);
int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) {
_cleanup_free_ char *status = NULL;
char *t, *f;
- size_t len;
int r;
assert(terminator);
t--;
}
- len = strcspn(t, terminator);
-
- f = strndup(t, len);
+ f = strdupcspn(t, terminator);
if (!f)
return -ENOMEM;
return r;
}
-int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) {
+int unhexmem_full(
+ const char *p,
+ size_t l,
+ bool secure,
+ void **ret,
+ size_t *ret_len) {
+
_cleanup_free_ uint8_t *buf = NULL;
size_t buf_size;
const char *x;
uint8_t *z;
- int r;
assert(p || l == 0);
if (!buf)
return -ENOMEM;
+ CLEANUP_ERASE_PTR(secure ? &buf : NULL, buf_size);
+
for (x = p, z = buf;;) {
int a, b;
a = unhex_next(&x, &l);
if (a == -EPIPE) /* End of string */
break;
- if (a < 0) {
- r = a;
- goto on_failure;
- }
+ if (a < 0)
+ return a;
b = unhex_next(&x, &l);
- if (b < 0) {
- r = b;
- goto on_failure;
- }
+ if (b < 0)
+ return b;
*(z++) = (uint8_t) a << 4 | (uint8_t) b;
}
*ret = TAKE_PTR(buf);
return 0;
-
-on_failure:
- if (secure)
- explicit_bzero_safe(buf, buf_size);
-
- return r;
}
/* https://tools.ietf.org/html/rfc4648#section-6
const void *p,
size_t l,
size_t line_break,
- char **out) {
+ char **ret) {
const uint8_t *x;
- char *r, *z;
+ char *b, *z;
size_t m;
assert(p || l == 0);
- assert(out);
assert(line_break > 0);
+ assert(ret);
/* three input bytes makes four output bytes, padding is added so we must round up */
m = 4 * (l + 2) / 3 + 1;
-
if (line_break != SIZE_MAX)
m += m / line_break;
- z = r = malloc(m);
- if (!r)
+ z = b = malloc(m);
+ if (!b)
return -ENOMEM;
for (x = p; x && x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
/* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */
}
switch (l % 3) {
case 2:
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = '=';
-
break;
+
case 1:
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = '=';
- maybe_line_break(&z, r, line_break);
+ maybe_line_break(&z, b, line_break);
*(z++) = '=';
-
break;
}
*z = 0;
- *out = r;
- assert(z >= r); /* Let static analyzers know that the answer is non-negative. */
- return z - r;
+ *ret = b;
+
+ assert(z >= b); /* Let static analyzers know that the answer is non-negative. */
+ return z - b;
}
static ssize_t base64_append_width(
return ret;
}
-int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_size) {
+int unbase64mem_full(
+ const char *p,
+ size_t l,
+ bool secure,
+ void **ret,
+ size_t *ret_size) {
+
_cleanup_free_ uint8_t *buf = NULL;
const char *x;
uint8_t *z;
size_t len;
- int r;
assert(p || l == 0);
if (!buf)
return -ENOMEM;
+ CLEANUP_ERASE_PTR(secure ? &buf : NULL, len);
+
for (x = p, z = buf;;) {
int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
a = unbase64_next(&x, &l);
if (a == -EPIPE) /* End of string */
break;
- if (a < 0) {
- r = a;
- goto on_failure;
- }
- if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */
- r = -EINVAL;
- goto on_failure;
- }
+ if (a < 0)
+ return a;
+ if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */
+ return -EINVAL;
b = unbase64_next(&x, &l);
- if (b < 0) {
- r = b;
- goto on_failure;
- }
- if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */
- r = -EINVAL;
- goto on_failure;
- }
+ if (b < 0)
+ return b;
+ if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */
+ return -EINVAL;
c = unbase64_next(&x, &l);
- if (c < 0) {
- r = c;
- goto on_failure;
- }
+ if (c < 0)
+ return c;
d = unbase64_next(&x, &l);
- if (d < 0) {
- r = d;
- goto on_failure;
- }
+ if (d < 0)
+ return d;
if (c == INT_MAX) { /* Padding at the third character */
- if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */
- r = -EINVAL;
- goto on_failure;
- }
+ if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
+ return -EINVAL;
/* b == 00YY0000 */
- if (b & 15) {
- r = -EINVAL;
- goto on_failure;
- }
+ if (b & 15)
+ return -EINVAL;
- if (l > 0) { /* Trailing rubbish? */
- r = -ENAMETOOLONG;
- goto on_failure;
- }
+ if (l > 0) /* Trailing rubbish? */
+ return -ENAMETOOLONG;
*(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
break;
if (d == INT_MAX) {
/* c == 00ZZZZ00 */
- if (c & 3) {
- r = -EINVAL;
- goto on_failure;
- }
+ if (c & 3)
+ return -EINVAL;
- if (l > 0) { /* Trailing rubbish? */
- r = -ENAMETOOLONG;
- goto on_failure;
- }
+ if (l > 0) /* Trailing rubbish? */
+ return -ENAMETOOLONG;
*(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
*(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
*z = 0;
+ assert((size_t) (z - buf) <= len);
+
if (ret_size)
*ret_size = (size_t) (z - buf);
if (ret)
*ret = TAKE_PTR(buf);
return 0;
-
-on_failure:
- if (secure)
- explicit_bzero_safe(buf, len);
-
- return r;
}
void hexdump(FILE *f, const void *p, size_t s) {
}
if (FLAGS_SET(flags, GET_HOSTNAME_SHORT))
- buf = strndup(s, strcspn(s, "."));
+ buf = strdupcspn(s, ".");
else
buf = strdup(s);
if (!buf)
break;
case PREFIXLEN_REFUSE:
return -ENOANO; /* To distinguish this error from others. */
- case PREFIXLEN_LEGACY:
- if (family == AF_INET) {
- r = in4_addr_default_prefixlen(&buffer.in, &k);
- if (r < 0)
- return r;
- } else
- k = 0;
- break;
default:
assert_not_reached();
}
typedef enum InAddrPrefixLenMode {
PREFIXLEN_FULL, /* Default to prefixlen of address size, 32 for IPv4 or 128 for IPv6, if not specified. */
PREFIXLEN_REFUSE, /* Fail with -ENOANO if prefixlen is not specified. */
- PREFIXLEN_LEGACY, /* Default to legacy default prefixlen calculation from address if not specified. */
} InAddrPrefixLenMode;
int in_addr_prefix_from_string_auto_internal(const char *p, InAddrPrefixLenMode mode, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
-#include "macro.h"
-
/* The head of the linked list. Use this in the structure that shall
* contain the head of the linked list */
#define LIST_HEAD(t,name) \
/* Prepend an item to the list */
#define LIST_PREPEND(name,head,item) \
- do { \
+ ({ \
typeof(*(head)) **_head = &(head), *_item = (item); \
assert(_item); \
if ((_item->name##_next = *_head)) \
_item->name##_next->name##_prev = _item; \
_item->name##_prev = NULL; \
*_head = _item; \
- } while (false)
+ _item; \
+ })
/* Append an item to the list */
#define LIST_APPEND(name,head,item) \
- do { \
+ ({ \
typeof(*(head)) **_hhead = &(head), *_tail; \
- LIST_FIND_TAIL(name, *_hhead, _tail); \
+ _tail = LIST_FIND_TAIL(name, *_hhead); \
LIST_INSERT_AFTER(name, *_hhead, _tail, item); \
- } while (false)
+ })
/* Remove an item from the list */
#define LIST_REMOVE(name,head,item) \
- do { \
+ ({ \
typeof(*(head)) **_head = &(head), *_item = (item); \
assert(_item); \
if (_item->name##_next) \
*_head = _item->name##_next; \
} \
_item->name##_next = _item->name##_prev = NULL; \
- } while (false)
+ _item; \
+ })
/* Find the head of the list */
-#define LIST_FIND_HEAD(name,item,head) \
- do { \
+#define LIST_FIND_HEAD(name,item) \
+ ({ \
typeof(*(item)) *_item = (item); \
- if (!_item) \
- (head) = NULL; \
- else { \
- while (_item->name##_prev) \
- _item = _item->name##_prev; \
- (head) = _item; \
- } \
- } while (false)
+ while (_item && _item->name##_prev) \
+ _item = _item->name##_prev; \
+ _item; \
+ })
/* Find the tail of the list */
-#define LIST_FIND_TAIL(name,item,tail) \
- do { \
+#define LIST_FIND_TAIL(name,item) \
+ ({ \
typeof(*(item)) *_item = (item); \
- if (!_item) \
- (tail) = NULL; \
- else { \
- while (_item->name##_next) \
- _item = _item->name##_next; \
- (tail) = _item; \
- } \
- } while (false)
+ while (_item && _item->name##_next) \
+ _item = _item->name##_next; \
+ _item; \
+ })
/* Insert an item after another one (a = where, b = what) */
#define LIST_INSERT_AFTER(name,head,a,b) \
- do { \
+ ({ \
typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
assert(_b); \
if (!_a) { \
_b->name##_prev = _a; \
_a->name##_next = _b; \
} \
- } while (false)
+ _b; \
+ })
/* Insert an item before another one (a = where, b = what) */
#define LIST_INSERT_BEFORE(name,head,a,b) \
- do { \
+ ({ \
typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
assert(_b); \
if (!_a) { \
_b->name##_next = _a; \
_a->name##_prev = _b; \
} \
- } while (false)
+ _b; \
+ })
#define LIST_JUST_US(name,item) \
(!(item)->name##_prev && !(item)->name##_next)
/* Join two lists tail to head: a->b, c->d to a->b->c->d and de-initialise second list */
#define LIST_JOIN(name,a,b) \
- do { \
+ ({ \
assert(b); \
if (!(a)) \
(a) = (b); \
else { \
typeof(*(a)) *_head = (b), *_tail; \
- LIST_FIND_TAIL(name, (a), _tail); \
+ _tail = LIST_FIND_TAIL(name, (a)); \
_tail->name##_next = _head; \
_head->name##_prev = _tail; \
} \
(b) = NULL; \
- } while (false)
+ a; \
+ })
#define LIST_POP(name, a) \
({ \
LIST_REMOVE(name, *_a, _p); \
_p; \
})
+
+/* Now include "macro.h", because we want our definition of assert() which the macros above use. We include
+ * it down here instead of up top, since macro.h pulls in log.h which in turn needs our own definitions. */
+#include "macro.h"
#include "alloc-util.h"
#include "argv-util.h"
+#include "env-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
+#include "strv.h"
#include "syslog-util.h"
#include "terminal-util.h"
#include "time-util.h"
#include "utf8.h"
#define SNDBUF_SIZE (8*1024*1024)
+#define IOVEC_MAX 128U
static log_syntax_callback_t log_syntax_callback = NULL;
static void *log_syntax_callback_userdata = NULL;
* use here. */
static char *log_abort_msg = NULL;
+typedef struct LogContext {
+ /* Depending on which destructor is used (log_context_free() or log_context_detach()) the memory
+ * referenced by this is freed or not */
+ char **fields;
+ bool owned;
+ LIST_FIELDS(struct LogContext, ll);
+} LogContext;
+
+static thread_local LIST_HEAD(LogContext, _log_context) = NULL;
+static thread_local size_t _log_context_num_fields = 0;
+
#if LOG_MESSAGE_VERIFICATION || defined(__COVERITY__)
bool _log_message_dummy = false; /* Always false */
#endif
return 0;
}
+static void log_do_context(struct iovec *iovec, size_t iovec_len, size_t *n) {
+ assert(iovec);
+ assert(n);
+
+ LIST_FOREACH(ll, c, _log_context)
+ STRV_FOREACH(s, c->fields) {
+ if (*n + 2 >= iovec_len)
+ return;
+
+ iovec[(*n)++] = IOVEC_MAKE_STRING(*s);
+ iovec[(*n)++] = IOVEC_MAKE_STRING("\n");
+ }
+}
+
static int write_to_journal(
int level,
int error,
const char *buffer) {
char header[LINE_MAX];
+ size_t n = 0, iovec_len;
+ struct iovec *iovec;
if (journal_fd < 0)
return 0;
+ iovec_len = MIN(4 + _log_context_num_fields * 2, IOVEC_MAX);
+ iovec = newa(struct iovec, iovec_len);
+
log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object, extra_field, extra);
- struct iovec iovec[4] = {
- IOVEC_MAKE_STRING(header),
- IOVEC_MAKE_STRING("MESSAGE="),
- IOVEC_MAKE_STRING(buffer),
- IOVEC_MAKE_STRING("\n"),
- };
+ iovec[n++] = IOVEC_MAKE_STRING(header);
+ iovec[n++] = IOVEC_MAKE_STRING("MESSAGE=");
+ iovec[n++] = IOVEC_MAKE_STRING(buffer);
+ iovec[n++] = IOVEC_MAKE_STRING("\n");
+
+ log_do_context(iovec, iovec_len, &n);
+
const struct msghdr msghdr = {
.msg_iov = iovec,
- .msg_iovlen = ELEMENTSOF(iovec),
+ .msg_iovlen = n,
};
if (sendmsg(journal_fd, &msghdr, MSG_NOSIGNAL) < 0)
if (journal_fd >= 0) {
char header[LINE_MAX];
- struct iovec iovec[17];
- size_t n = 0;
+ struct iovec *iovec;
+ size_t n = 0, m, iovec_len;
int r;
bool fallback = false;
+ iovec_len = MIN(17 + _log_context_num_fields * 2, IOVEC_MAX);
+ iovec = newa(struct iovec, iovec_len);
+
/* If the journal is available do structured logging.
* Do not report the errno if it is synthetic. */
log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL, NULL, NULL);
iovec[n++] = IOVEC_MAKE_STRING(header);
va_start(ap, format);
- r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, true, error, format, ap);
+ r = log_format_iovec(iovec, iovec_len, &n, true, error, format, ap);
+ m = n;
if (r < 0)
fallback = true;
else {
+ log_do_context(iovec, iovec_len, &n);
+
const struct msghdr msghdr = {
.msg_iov = iovec,
.msg_iovlen = n,
}
va_end(ap);
- for (size_t i = 1; i < n; i += 2)
+ for (size_t i = 1; i < m; i += 2)
free(iovec[i].iov_base);
if (!fallback) {
journal_fd >= 0) {
char header[LINE_MAX];
+ struct iovec *iovec;
+ size_t n = 0, iovec_len;
+
+ iovec_len = MIN(1 + n_input_iovec * 2 + _log_context_num_fields * 2, IOVEC_MAX);
+ iovec = newa(struct iovec, iovec_len);
+
log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL, NULL, NULL);
- struct iovec iovec[1 + n_input_iovec*2];
- iovec[0] = IOVEC_MAKE_STRING(header);
+ iovec[n++] = IOVEC_MAKE_STRING(header);
for (size_t i = 0; i < n_input_iovec; i++) {
- iovec[1+i*2] = input_iovec[i];
- iovec[1+i*2+1] = IOVEC_MAKE_STRING("\n");
+ iovec[n++] = input_iovec[i];
+ iovec[n++] = IOVEC_MAKE_STRING("\n");
}
+ log_do_context(iovec, iovec_len, &n);
+
const struct msghdr msghdr = {
.msg_iov = iovec,
- .msg_iovlen = 1 + n_input_iovec*2,
+ .msg_iovlen = n,
};
if (sendmsg(journal_fd, &msghdr, MSG_NOSIGNAL) >= 0)
if (log_on_console() && show_color < 0)
log_show_color(true);
}
+
+static int saved_log_context_enabled = -1;
+
+bool log_context_enabled(void) {
+ int r;
+
+ if (log_get_max_level() == LOG_DEBUG)
+ return true;
+
+ if (saved_log_context_enabled >= 0)
+ return saved_log_context_enabled;
+
+ r = getenv_bool_secure("SYSTEMD_ENABLE_LOG_CONTEXT");
+ if (r < 0 && r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_ENABLE_LOG_CONTEXT, ignoring: %m");
+
+ saved_log_context_enabled = r > 0;
+
+ return saved_log_context_enabled;
+}
+
+LogContext* log_context_attach(LogContext *c) {
+ assert(c);
+
+ _log_context_num_fields += strv_length(c->fields);
+
+ return LIST_PREPEND(ll, _log_context, c);
+}
+
+LogContext* log_context_detach(LogContext *c) {
+ if (!c)
+ return NULL;
+
+ assert(_log_context_num_fields >= strv_length(c->fields));
+ _log_context_num_fields -= strv_length(c->fields);
+
+ LIST_REMOVE(ll, _log_context, c);
+ return NULL;
+}
+
+LogContext* log_context_new(char **fields, bool owned) {
+ LogContext *c = new(LogContext, 1);
+ if (!c)
+ return NULL;
+
+ *c = (LogContext) {
+ .fields = fields,
+ .owned = owned,
+ };
+
+ return log_context_attach(c);
+}
+
+LogContext* log_context_free(LogContext *c) {
+ if (!c)
+ return NULL;
+
+ log_context_detach(c);
+
+ if (c->owned)
+ strv_free(c->fields);
+
+ return mfree(c);
+}
+
+LogContext* log_context_new_consume(char **fields) {
+ LogContext *c = log_context_new(fields, /*owned=*/ true);
+ if (!c)
+ strv_free(fields);
+
+ return c;
+}
+
+size_t log_context_num_contexts(void) {
+ size_t n = 0;
+
+ LIST_FOREACH(ll, c, _log_context)
+ n++;
+
+ return n;
+}
+
+size_t log_context_num_fields(void) {
+ return _log_context_num_fields;
+}
#include <string.h>
#include <syslog.h>
+#include "list.h"
#include "macro.h"
#include "ratelimit.h"
+#include "stdio-util.h"
/* Some structures we reference but don't want to pull in headers for */
struct iovec;
#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__)
+
+/*
+ * The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep
+ * track of a thread local log context onto which we can push extra metadata fields that should be logged.
+ *
+ * LOG_CONTEXT_PUSH() will add the provided field to the log context and will remove it again when the
+ * current block ends. LOG_CONTEXT_PUSH_STRV() will do the same but for all fields in the given strv.
+ * LOG_CONTEXT_PUSHF() is like LOG_CONTEXT_PUSH() but takes a format string and arguments.
+ *
+ * Using the macros is as simple as putting them anywhere inside a block to add a field to all following log
+ * messages logged from inside that block.
+ *
+ * void myfunction(...) {
+ * ...
+ *
+ * LOG_CONTEXT_PUSHF("MYMETADATA=%s", "abc");
+ *
+ * // Every journal message logged will now have the MYMETADATA=abc
+ * // field included.
+ * }
+ *
+ * One special case to note is async code, where we use callbacks that are invoked to continue processing
+ * when some event occurs. For async code, there's usually an associated "userdata" struct containing all the
+ * information associated with the async operation. In this "userdata" struct, we can store a log context
+ * allocated with log_context_new() and freed with log_context_free(). We can then add and remove fields to
+ * the `fields` member of the log context object and all those fields will be logged along with each log
+ * message.
+ */
+
+typedef struct LogContext LogContext;
+
+bool log_context_enabled(void);
+
+LogContext* log_context_attach(LogContext *c);
+LogContext* log_context_detach(LogContext *c);
+
+LogContext* log_context_new(char **fields, bool owned);
+LogContext* log_context_free(LogContext *c);
+
+/* Same as log_context_new(), but frees the given fields strv on failure. */
+LogContext* log_context_new_consume(char **fields);
+
+/* Returns the number of attached log context objects. */
+size_t log_context_num_contexts(void);
+/* Returns the number of fields in all attached log contexts. */
+size_t log_context_num_fields(void);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_detach);
+DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_free);
+
+#define LOG_CONTEXT_PUSH(...) \
+ LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__))
+
+#define LOG_CONTEXT_PUSHF(...) \
+ LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__))
+
+#define _LOG_CONTEXT_PUSH_STRV(strv, c) \
+ _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new(strv, /*owned=*/ false);
+
+#define LOG_CONTEXT_PUSH_STRV(strv) \
+ _LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ))
+
+/* LOG_CONTEXT_CONSUME_STR()/LOG_CONTEXT_CONSUME_STRV() are identical to
+ * LOG_CONTEXT_PUSH_STR()/LOG_CONTEXT_PUSH_STRV() except they take ownership of the given str/strv argument.
+ */
+
+#define _LOG_CONTEXT_CONSUME_STR(s, c, strv) \
+ _unused_ _cleanup_strv_free_ strv = strv_new(s); \
+ if (!strv) \
+ free(s); \
+ _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(TAKE_PTR(strv))
+
+#define LOG_CONTEXT_CONSUME_STR(s) \
+ _LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ))
+
+#define _LOG_CONTEXT_CONSUME_STRV(strv, c) \
+ _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(strv);
+
+#define LOG_CONTEXT_CONSUME_STRV(strv) \
+ _LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ))
#define assert_message_se(expr, message) \
do { \
if (_unlikely_(!(expr))) \
- log_assert_failed(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__); \
+ log_assert_failed(message, PROJECT_FILE, __LINE__, __func__); \
} while (false)
#define assert_log(expr, message) ((_likely_(expr)) \
? (true) \
- : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__), false))
+ : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __func__), false))
#endif /* __COVERITY__ */
#endif
#define assert_not_reached() \
- log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__)
+ log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __func__)
#define assert_return(expr, r) \
do { \
return 0;
}
+int parse_sector_size(const char *t, uint64_t *ret) {
+ int r;
+
+ assert(t);
+ assert(ret);
+
+ uint64_t ss;
+
+ r = safe_atou64(t, &ss);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse sector size parameter %s", t);
+ if (ss < 512 || ss > 4096) /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */
+ return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Sector size not between 512 and 4096: %s", t);
+ if (!ISPOWEROF2(ss))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size not power of 2: %s", t);
+
+ *ret = ss;
+ return 0;
+}
+
int parse_range(const char *t, unsigned *lower, unsigned *upper) {
_cleanup_free_ char *word = NULL;
unsigned l, u;
int parse_mtu(int family, const char *s, uint32_t *ret);
int parse_size(const char *t, uint64_t base, uint64_t *size);
+int parse_sector_size(const char *t, uint64_t *ret);
int parse_range(const char *t, unsigned *lower, unsigned *upper);
int parse_errno(const char *t);
return parse_pid(p, ret);
}
+int pidfd_verify_pid(int pidfd, pid_t pid) {
+ pid_t current_pid;
+ int r;
+
+ assert(pidfd >= 0);
+ assert(pid > 0);
+
+ r = pidfd_get_pid(pidfd, ¤t_pid);
+ if (r < 0)
+ return r;
+
+ return current_pid != pid ? -ESRCH : 0;
+}
+
static int rlimit_to_nice(rlim_t limit) {
if (limit <= 1)
return PRIO_MAX-1; /* i.e. 19 */
})
int pidfd_get_pid(int fd, pid_t *ret);
+int pidfd_verify_pid(int pidfd, pid_t pid);
int setpriority_closest(int priority);
#define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service"
#define SPECIAL_VOLATILE_ROOT_SERVICE "systemd-volatile-root.service"
#define SPECIAL_UDEVD_SERVICE "systemd-udevd.service"
+#define SPECIAL_GROWFS_SERVICE "systemd-growfs@.service"
+#define SPECIAL_GROWFS_ROOT_SERVICE "systemd-growfs-root.service"
+#define SPECIAL_PCRFS_SERVICE "systemd-pcrfs@.service"
+#define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service"
/* Services systemd relies on */
#define SPECIAL_DBUS_SERVICE "dbus.service"
#include <sys/types.h>
#include "macro.h"
-#include "memory-util.h"
-#define snprintf_ok(buf, len, fmt, ...) \
- ({ \
- char *_buf = (buf); \
- size_t _len = (len); \
- int _snpf = snprintf(_buf, _len, (fmt), ##__VA_ARGS__); \
- _snpf >= 0 && (size_t) _snpf < _len ? _buf : NULL; \
- })
+_printf_(3, 4)
+static inline char *snprintf_ok(char *buf, size_t len, const char *format, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, format);
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ r = vsnprintf(buf, len, format, ap);
+ REENABLE_WARNING;
+ va_end(ap);
+
+ return r >= 0 && (size_t) r < len ? buf : NULL;
+}
#define xsprintf(buf, fmt, ...) \
assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, ##__VA_ARGS__), "xsprintf: " #buf "[] must be big enough")
size_t _i, _k; \
/* See https://github.com/google/sanitizers/issues/992 */ \
if (HAS_FEATURE_MEMORY_SANITIZER) \
- zero(_argtypes); \
+ memset(_argtypes, 0, sizeof(_argtypes)); \
_k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \
assert(_k < ELEMENTSOF(_argtypes)); \
for (_i = 0; _i < _k; _i++) { \
return str;
}
+int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) {
+ char *b;
+
+ assert(s || n == 0);
+ assert(mode >= 0);
+ assert(mode < _MAKE_CSTRING_MODE_MAX);
+
+ /* Converts a sized character buffer into a NUL-terminated NUL string, refusing if there are embedded
+ * NUL bytes. Whether to expect a trailing NUL byte can be specified via 'mode' */
+
+ if (n == 0) {
+ if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL)
+ return -EINVAL;
+
+ if (!ret)
+ return 0;
+
+ b = new0(char, 1);
+ } else {
+ const char *nul;
+
+ nul = memchr(s, 0, n);
+ if (nul) {
+ if (nul < s + n - 1 || /* embedded NUL? */
+ mode == MAKE_CSTRING_REFUSE_TRAILING_NUL)
+ return -EINVAL;
+
+ n--;
+ } else if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL)
+ return -EINVAL;
+
+ if (!ret)
+ return 0;
+
+ b = memdup_suffix0(s, n);
+ }
+ if (!b)
+ return -ENOMEM;
+
+ *ret = b;
+ return 0;
+}
+
size_t strspn_from_end(const char *str, const char *accept) {
size_t n = 0;
return n;
}
+
+char *strdupspn(const char *a, const char *accept) {
+ if (isempty(a) || isempty(accept))
+ return strdup("");
+
+ return strndup(a, strspn(a, accept));
+}
+
+char *strdupcspn(const char *a, const char *reject) {
+ if (isempty(a))
+ return strdup("");
+ if (isempty(reject))
+ return strdup(a);
+
+ return strndup(a, strcspn(a, reject));
+}
char *string_replace_char(char *str, char old_char, char new_char);
+typedef enum MakeCStringMode {
+ MAKE_CSTRING_REFUSE_TRAILING_NUL,
+ MAKE_CSTRING_ALLOW_TRAILING_NUL,
+ MAKE_CSTRING_REQUIRE_TRAILING_NUL,
+ _MAKE_CSTRING_MODE_MAX,
+ _MAKE_CSTRING_MODE_INVALID = -1,
+} MakeCStringMode;
+
+int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret);
+
size_t strspn_from_end(const char *str, const char *accept);
+
+char *strdupspn(const char *a, const char *accept);
+char *strdupcspn(const char *a, const char *reject);
return -ENOMEM;
}
+int strv_extend_assignment(char ***l, const char *lhs, const char *rhs) {
+ char *j;
+
+ assert(l);
+ assert(lhs);
+
+ if (!rhs) /* value is optional, in which case we suppress the field */
+ return 0;
+
+ j = strjoin(lhs, "=", rhs);
+ if (!j)
+ return -ENOMEM;
+
+ return strv_consume(l, j);
+}
+
int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) {
bool b = false;
int r;
int strv_extend_n(char ***l, const char *value, size_t n);
+int strv_extend_assignment(char ***l, const char *lhs, const char *rhs);
+
int fputstrv(FILE *f, char * const *l, const char *separator, bool *space);
#define strv_free_and_replace(a, b) \
};
struct tm tm;
+ bool utc, us;
time_t sec;
size_t n;
- bool utc = false, us = false;
- int r;
assert(buf);
+ assert(style >= 0);
+ assert(style < _TIMESTAMP_STYLE_MAX);
- switch (style) {
- case TIMESTAMP_PRETTY:
- case TIMESTAMP_UNIX:
- break;
- case TIMESTAMP_US:
- us = true;
- break;
- case TIMESTAMP_UTC:
- utc = true;
- break;
- case TIMESTAMP_US_UTC:
- us = true;
- utc = true;
- break;
- default:
- return NULL;
- }
-
- if (l < (size_t) (3 + /* week day */
- 1 + 10 + /* space and date */
- 1 + 8 + /* space and time */
- (us ? 1 + 6 : 0) + /* "." and microsecond part */
- 1 + 1 + /* space and shortest possible zone */
- 1))
- return NULL; /* Not enough space even for the shortest form. */
if (!timestamp_is_set(t))
return NULL; /* Timestamp is unset */
if (style == TIMESTAMP_UNIX) {
- r = snprintf(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down µs → s */
- if (r < 0 || (size_t) r >= l)
- return NULL; /* Doesn't fit */
+ if (l < (size_t) (1 + 1 + 1))
+ return NULL; /* not enough space for even the shortest of forms */
- return buf;
+ return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down µs → s */
}
+ utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE);
+ us = IN_SET(style, TIMESTAMP_US, TIMESTAMP_US_UTC);
+
+ if (l < (size_t) (3 + /* week day */
+ 1 + 10 + /* space and date */
+ style == TIMESTAMP_DATE ? 0 :
+ (1 + 8 + /* space and time */
+ (us ? 1 + 6 : 0) + /* "." and microsecond part */
+ 1 + (utc ? 3 : 1)) + /* space and shortest possible zone */
+ 1))
+ return NULL; /* Not enough space even for the shortest form. */
+
/* Let's not format times with years > 9999 */
if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
- assert(l >= STRLEN("--- XXXX-XX-XX XX:XX:XX") + 1);
- strcpy(buf, "--- XXXX-XX-XX XX:XX:XX");
- return buf;
+ static const char* const xxx[_TIMESTAMP_STYLE_MAX] = {
+ [TIMESTAMP_PRETTY] = "--- XXXX-XX-XX XX:XX:XX",
+ [TIMESTAMP_US] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX",
+ [TIMESTAMP_UTC] = "--- XXXX-XX-XX XX:XX:XX UTC",
+ [TIMESTAMP_US_UTC] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC",
+ [TIMESTAMP_DATE] = "--- XXXX-XX-XX",
+ };
+
+ assert(l >= strlen(xxx[style]) + 1);
+ return strcpy(buf, xxx[style]);
}
sec = (time_t) (t / USEC_PER_SEC); /* Round down */
assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
memcpy(buf, weekdays[tm.tm_wday], 4);
+ if (style == TIMESTAMP_DATE) {
+ /* Special format string if only date should be shown. */
+ if (strftime(buf + 3, l - 3, " %Y-%m-%d", &tm) <= 0)
+ return NULL; /* Doesn't fit */
+
+ return buf;
+ }
+
/* Add the main components */
if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
return NULL; /* Doesn't fit */
TIMESTAMP_UTC,
TIMESTAMP_US_UTC,
TIMESTAMP_UNIX,
+ TIMESTAMP_DATE,
_TIMESTAMP_STYLE_MAX,
_TIMESTAMP_STYLE_INVALID = -EINVAL,
} TimestampStyle;
assert(rule[0]);
_cleanup_free_ char *rulename = NULL;
- const char *e;
int r;
- e = strchrnul(rule + 1, rule[0]);
- rulename = strndup(rule + 1, e - rule - 1);
+ rulename = strdupcspn(rule + 1, CHAR_TO_STR(rule[0]));
if (!rulename)
return log_oom();
#include "find-esp.h"
#include "path-util.h"
#include "pretty-print.h"
+#include "recurse-dir.h"
#include "terminal-util.h"
#include "tpm2-util.h"
return r;
}
+static int ref_file(Hashmap *known_files, const char *fn, int increment) {
+ char *k = NULL;
+ int n, r;
+
+ assert(known_files);
+
+ /* just gracefully ignore this. This way the caller doesn't
+ have to verify whether the bootloader entry is relevant */
+ if (!fn)
+ return 0;
+
+ n = PTR_TO_INT(hashmap_get2(known_files, fn, (void**)&k));
+ n += increment;
+
+ assert(n >= 0);
+
+ if (n == 0) {
+ (void) hashmap_remove(known_files, fn);
+ free(k);
+ } else if (!k) {
+ _cleanup_free_ char *t = NULL;
+
+ t = strdup(fn);
+ if (!t)
+ return -ENOMEM;
+ r = hashmap_put(known_files, t, INT_TO_PTR(n));
+ if (r < 0)
+ return r;
+ TAKE_PTR(t);
+ } else {
+ r = hashmap_update(known_files, fn, INT_TO_PTR(n));
+ if (r < 0)
+ return r;
+ }
+
+ return n;
+}
+
+static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *root) {
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ /* just gracefully ignore this. This way the caller doesn't
+ have to verify whether the bootloader entry is relevant */
+ if (!fn || !root)
+ return;
+
+ r = ref_file(known_files, fn, -1);
+ if (r < 0)
+ return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn);
+ if (r > 0)
+ return;
+
+ if (arg_dry_run) {
+ r = chase_symlinks_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path, NULL);
+ if (r < 0)
+ log_info("Unable to determine whether \"%s\" exists, ignoring: %m", fn);
+ else
+ log_info("Would remove %s", path);
+ return;
+ }
+
+ r = chase_symlinks_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, 0, &path);
+ if (r >= 0)
+ log_info("Removed %s", path);
+ else if (r != -ENOENT)
+ return (void) log_warning_errno(r, "Failed to remove \"%s\", ignoring: %m", path ?: fn);
+
+ _cleanup_free_ char *d = NULL;
+ if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) {
+ r = chase_symlinks_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, AT_REMOVEDIR, NULL);
+ if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT))
+ log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d);
+ }
+}
+
+static int count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files) {
+ _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
+ int r = 0;
+
+ assert(config);
+ assert(ret_known_files);
+
+ known_files = hashmap_new(&path_hash_ops);
+ if (!known_files)
+ return -ENOMEM;
+
+ for (size_t i = 0; i < config->n_entries; i++) {
+ const BootEntry *e = config->entries + i;
+
+ if (!path_equal(e->root, root))
+ continue;
+
+ r = ref_file(known_files, e->kernel, +1);
+ if (r < 0)
+ return r;
+ r = ref_file(known_files, e->efi, +1);
+ if (r < 0)
+ return r;
+ STRV_FOREACH(s, e->initrd) {
+ r = ref_file(known_files, *s, +1);
+ if (r < 0)
+ return r;
+ }
+ r = ref_file(known_files, e->device_tree, +1);
+ if (r < 0)
+ return r;
+ STRV_FOREACH(s, e->device_tree_overlay) {
+ r = ref_file(known_files, *s, +1);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ *ret_known_files = TAKE_PTR(known_files);
+
+ return 0;
+}
+
+static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) {
+ assert(config);
+
+ if (!root || !id)
+ return -1;
+
+ for (size_t i = 0; i < config->n_entries; i++)
+ if (path_equal(config->entries[i].root, root)
+ && fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
+ return i;
+
+ return -1;
+}
+
+static int unlink_entry(const BootConfig *config, const char *root, const char *id) {
+ _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
+ const BootEntry *e = NULL;
+ int r;
+
+ assert(config);
+
+ r = count_known_files(config, root, &known_files);
+ if (r < 0)
+ return log_error_errno(r, "Failed to count files in %s: %m", root);
+
+ r = boot_config_find_in(config, root, id);
+ if (r < 0)
+ return -ENOENT;
+
+ if (r == config->default_entry)
+ log_warning("%s is the default boot entry", id);
+ if (r == config->selected_entry)
+ log_warning("%s is the selected boot entry", id);
+
+ e = &config->entries[r];
+
+ deref_unlink_file(known_files, e->kernel, e->root);
+ deref_unlink_file(known_files, e->efi, e->root);
+ STRV_FOREACH(s, e->initrd)
+ deref_unlink_file(known_files, *s, e->root);
+ deref_unlink_file(known_files, e->device_tree, e->root);
+ STRV_FOREACH(s, e->device_tree_overlay)
+ deref_unlink_file(known_files, *s, e->root);
+
+ if (arg_dry_run)
+ log_info("Would remove %s", e->path);
+ else {
+ r = chase_symlinks_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS, 0, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to remove \"%s\": %m", e->path);
+
+ log_info("Removed %s", e->path);
+ }
+
+ return 0;
+}
+
+static int list_remove_orphaned_file(
+ RecurseDirEvent event,
+ const char *path,
+ int dir_fd,
+ int inode_fd,
+ const struct dirent *de,
+ const struct statx *sx,
+ void *userdata) {
+ Hashmap *known_files = userdata;
+
+ assert(path);
+ assert(known_files);
+
+ if (event != RECURSE_DIR_ENTRY)
+ return RECURSE_DIR_CONTINUE;
+
+ if (hashmap_get(known_files, path))
+ return RECURSE_DIR_CONTINUE; /* keep! */
+
+ if (arg_dry_run)
+ log_info("Would remove %s", path);
+ else if (unlinkat(dir_fd, de->d_name, 0) < 0)
+ log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", path);
+ else
+ log_info("Removed %s", path);
+
+ return RECURSE_DIR_CONTINUE;
+}
+
+static int cleanup_orphaned_files(
+ const BootConfig *config,
+ const char *root) {
+ _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
+ _cleanup_free_ char *full = NULL, *p = NULL;
+ _cleanup_close_ int dir_fd = -1;
+ int r = -1;
+
+ assert(config);
+ assert(root);
+
+ log_info("Cleaning %s", root);
+
+ r = settle_entry_token();
+ if (r < 0)
+ return r;
+
+ r = count_known_files(config, root, &known_files);
+ if (r < 0)
+ return log_error_errno(r, "Failed to count files in %s: %m", root);
+
+ dir_fd = chase_symlinks_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
+ O_DIRECTORY|O_CLOEXEC, &full);
+ if (dir_fd == -ENOENT)
+ return 0;
+ if (dir_fd < 0)
+ return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, arg_entry_token);
+
+ p = path_join("/", arg_entry_token);
+ if (!p)
+ return log_oom();
+
+ r = recurse_dir(dir_fd, p, 0, UINT_MAX, RECURSE_DIR_SORT, list_remove_orphaned_file, known_files);
+ if (r < 0)
+ return log_error_errno(r, "Failed to cleanup %s: %m", full);
+
+ return r;
+}
+
int verb_list(int argc, char *argv[], void *userdata) {
_cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
dev_t esp_devid = 0, xbootldr_devid = 0;
return 0;
}
- pager_open(arg_pager_flags);
- return show_boot_entries(&config, arg_json_format_flags);
+ if (streq(argv[0], "list")) {
+ pager_open(arg_pager_flags);
+ return show_boot_entries(&config, arg_json_format_flags);
+ } else if (streq(argv[0], "cleanup")) {
+ if (arg_xbootldr_path && xbootldr_devid != esp_devid)
+ cleanup_orphaned_files(&config, arg_xbootldr_path);
+ return cleanup_orphaned_files(&config, arg_esp_path);
+ } else {
+ assert(streq(argv[0], "unlink"));
+ if (arg_xbootldr_path && xbootldr_devid != esp_devid) {
+ r = unlink_entry(&config, arg_xbootldr_path, argv[1]);
+ if (r == 0 || r != -ENOENT)
+ return r;
+ }
+ return unlink_entry(&config, arg_esp_path, argv[1]);
+ }
+}
+
+int verb_unlink(int argc, char *argv[], void *userdata) {
+ return verb_list(argc, argv, userdata);
}
int verb_status(int argc, char *argv[], void *userdata);
int verb_list(int argc, char *argv[], void *userdata);
+int verb_unlink(int argc, char *argv[], void *userdata);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bootctl.h"
+#include "bootctl-uki.h"
+#include "fd-util.h"
+#include "parse-util.h"
+#include "pe-header.h"
+
+#define MAX_SECTIONS 96
+
+static const uint8_t dos_file_magic[2] = "MZ";
+static const uint8_t pe_file_magic[4] = "PE\0\0";
+
+static const uint8_t name_osrel[8] = ".osrel";
+static const uint8_t name_linux[8] = ".linux";
+static const uint8_t name_initrd[8] = ".initrd";
+static const uint8_t name_cmdline[8] = ".cmdline";
+static const uint8_t name_uname[8] = ".uname";
+
+static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) {
+ _cleanup_free_ struct PeSectionHeader *sections = NULL;
+ struct DosFileHeader dos;
+ struct PeHeader pe;
+ size_t scount;
+ uint64_t soff, items;
+ int rc;
+
+ items = fread(&dos, 1, sizeof(dos), uki);
+ if (items != sizeof(dos))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "DOS header read error");
+ if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0)
+ goto no_sections;
+
+ rc = fseek(uki, le32toh(dos.ExeHeader), SEEK_SET);
+ if (rc < 0)
+ return log_error_errno(errno, "seek to PE header");
+ items = fread(&pe, 1, sizeof(pe), uki);
+ if (items != sizeof(pe))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE header read error");
+ if (memcmp(pe.Magic, pe_file_magic, sizeof(pe_file_magic)) != 0)
+ goto no_sections;
+
+ soff = le32toh(dos.ExeHeader) + sizeof(pe) + le16toh(pe.FileHeader.SizeOfOptionalHeader);
+ rc = fseek(uki, soff, SEEK_SET);
+ if (rc < 0)
+ return log_error_errno(errno, "seek to PE section headers");
+
+ scount = le16toh(pe.FileHeader.NumberOfSections);
+ if (scount > MAX_SECTIONS)
+ goto no_sections;
+ sections = new(struct PeSectionHeader, scount);
+ if (!sections)
+ return log_oom();
+ items = fread(sections, sizeof(*sections), scount, uki);
+ if (items != scount)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE section header read error");
+
+ *ret = TAKE_PTR(sections);
+ *ret_n = scount;
+ return 0;
+
+no_sections:
+ *ret = NULL;
+ *ret_n = 0;
+ return 0;
+}
+
+static int find_pe_section(struct PeSectionHeader *sections, size_t scount,
+ const uint8_t *name, size_t namelen, size_t *ret) {
+ for (size_t s = 0; s < scount; s++) {
+ if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name),
+ name, namelen) == 0) {
+ if (ret)
+ *ret = s;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static bool is_uki(struct PeSectionHeader *sections, size_t scount) {
+ return (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) &&
+ find_pe_section(sections, scount, name_linux, sizeof(name_linux), NULL) &&
+ find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL));
+}
+
+int verb_kernel_identify(int argc, char *argv[], void *userdata) {
+ _cleanup_fclose_ FILE *uki = NULL;
+ _cleanup_free_ struct PeSectionHeader *sections = NULL;
+ size_t scount;
+ int rc;
+
+ uki = fopen(argv[1], "re");
+ if (!uki)
+ return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]);
+
+ rc = pe_sections(uki, §ions, &scount);
+ if (rc < 0)
+ return EXIT_FAILURE;
+
+ if (sections) {
+ if (is_uki(sections, scount)) {
+ puts("uki");
+ return EXIT_SUCCESS;
+ }
+ puts("pe");
+ return EXIT_SUCCESS;
+ }
+
+ puts("unknown");
+ return EXIT_SUCCESS;
+}
+
+static int read_pe_section(FILE *uki, const struct PeSectionHeader *section,
+ void **ret, size_t *ret_n) {
+ _cleanup_free_ void *data = NULL;
+ uint32_t size, bytes;
+ uint64_t soff;
+ int rc;
+
+ soff = le32toh(section->PointerToRawData);
+ size = le32toh(section->VirtualSize);
+
+ if (size > 16 * 1024)
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "PE section too big");
+
+ rc = fseek(uki, soff, SEEK_SET);
+ if (rc < 0)
+ return log_error_errno(errno, "seek to PE section");
+
+ data = malloc(size+1);
+ if (!data)
+ return log_oom();
+ ((uint8_t*) data)[size] = 0; /* safety NUL byte */
+
+ bytes = fread(data, 1, size, uki);
+ if (bytes != size)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE section read error");
+
+ *ret = TAKE_PTR(data);
+ if (ret_n)
+ *ret_n = size;
+ return 0;
+}
+
+static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scount) {
+ _cleanup_free_ char *cmdline = NULL;
+ _cleanup_free_ char *uname = NULL;
+ size_t idx;
+
+ if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx))
+ read_pe_section(uki, sections + idx, (void**)&cmdline, NULL);
+
+ if (find_pe_section(sections, scount, name_uname, sizeof(name_uname), &idx))
+ read_pe_section(uki, sections + idx, (void**)&uname, NULL);
+
+ if (cmdline)
+ printf(" Cmdline: %s\n", cmdline);
+ if (uname)
+ printf(" Version: %s\n", uname);
+}
+
+int verb_kernel_inspect(int argc, char *argv[], void *userdata) {
+ _cleanup_fclose_ FILE *uki = NULL;
+ _cleanup_free_ struct PeSectionHeader *sections = NULL;
+ size_t scount;
+ int rc;
+
+ uki = fopen(argv[1], "re");
+ if (!uki)
+ return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]);
+
+ rc = pe_sections(uki, §ions, &scount);
+ if (rc < 0)
+ return EXIT_FAILURE;
+
+ if (sections) {
+ if (is_uki(sections, scount)) {
+ puts("Kernel Type: uki");
+ inspect_uki(uki, sections, scount);
+ return EXIT_SUCCESS;
+ }
+ puts("Kernel Type: pe");
+ return EXIT_SUCCESS;
+ }
+
+ puts("Kernel Type: unknown");
+ return EXIT_SUCCESS;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int verb_kernel_identify(int argc, char *argv[], void *userdata);
+int verb_kernel_inspect(int argc, char *argv[], void *userdata);
#include "bootctl-set-efivar.h"
#include "bootctl-status.h"
#include "bootctl-systemd-efi-options.h"
+#include "bootctl-uki.h"
#include "build.h"
#include "dissect-image.h"
#include "escape.h"
char *arg_image = NULL;
InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO;
char *arg_efi_boot_option_description = NULL;
+bool arg_dry_run = false;
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
if (r < 0)
return log_oom();
- printf("%1$s [OPTIONS...] COMMAND ...\n"
+ printf("%1$s [OPTIONS...] COMMAND ...\n"
"\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n"
"\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n"
- " status Show status of installed boot loader and EFI variables\n"
+ " status Show status of installed boot loader and EFI variables\n"
" reboot-to-firmware [BOOL]\n"
- " Query or set reboot-to-firmware EFI flag\n"
+ " Query or set reboot-to-firmware EFI flag\n"
" systemd-efi-options [STRING]\n"
- " Query or set system options string in EFI variable\n"
+ " Query or set system options string in EFI variable\n"
"\n%3$sBoot Loader Specification Commands:%4$s\n"
- " list List boot loader entries\n"
- " set-default ID Set default boot loader entry\n"
- " set-oneshot ID Set default boot loader entry, for next boot only\n"
- " set-timeout SECONDS Set the menu timeout\n"
+ " list List boot loader entries\n"
+ " unlink ID Remove boot loader entry\n"
+ " cleanup Remove files in ESP not referenced in any boot entry\n"
+ "\n%3$sBoot Loader Interface Commands:%4$s\n"
+ " set-default ID Set default boot loader entry\n"
+ " set-oneshot ID Set default boot loader entry, for next boot only\n"
+ " set-timeout SECONDS Set the menu timeout\n"
" set-timeout-oneshot SECONDS\n"
- " Set the menu timeout for the next boot only\n"
+ " Set the menu timeout for the next boot only\n"
"\n%3$ssystemd-boot Commands:%4$s\n"
- " install Install systemd-boot to the ESP and EFI variables\n"
- " update Update systemd-boot in the ESP and EFI variables\n"
- " remove Remove systemd-boot from the ESP and EFI variables\n"
- " is-installed Test whether systemd-boot is installed in the ESP\n"
- " random-seed Initialize random seed in ESP and EFI variables\n"
+ " install Install systemd-boot to the ESP and EFI variables\n"
+ " update Update systemd-boot in the ESP and EFI variables\n"
+ " remove Remove systemd-boot from the ESP and EFI variables\n"
+ " is-installed Test whether systemd-boot is installed in the ESP\n"
+ " random-seed Initialize random seed in ESP and EFI variables\n"
+ "\n%3$sKernel Image Commands:%4$s\n"
+ " kernel-identify Identify kernel image type\n"
+ " kernel-inspect Prints details about the kernel image\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
" Install all supported EFI architectures\n"
" --efi-boot-option-description=DESCRIPTION\n"
" Description of the entry in the boot option list\n"
+ " --dry-run Dry run (unlink and cleanup)\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ARG_JSON,
ARG_ARCH_ALL,
ARG_EFI_BOOT_OPTION_DESCRIPTION,
+ ARG_DRY_RUN,
};
static const struct option options[] = {
{ "json", required_argument, NULL, ARG_JSON },
{ "all-architectures", no_argument, NULL, ARG_ARCH_ALL },
{ "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION },
+ { "dry-run", no_argument, NULL, ARG_DRY_RUN },
{}
};
return r;
break;
+ case ARG_DRY_RUN:
+ arg_dry_run = true;
+ break;
+
case '?':
return -EINVAL;
}
if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list",
- "install", "update", "remove", "is-installed", "random-seed"))
+ "install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Options --root= and --image= are not supported with verb %s.",
argv[optind]);
if (arg_install_source != ARG_INSTALL_SOURCE_AUTO && !arg_root && !arg_image)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=.");
+ if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry is only supported with --unlink or --cleanup");
+
return 1;
}
{ "update", VERB_ANY, 1, 0, verb_install },
{ "remove", VERB_ANY, 1, 0, verb_remove },
{ "is-installed", VERB_ANY, 1, 0, verb_is_installed },
+ { "kernel-identify", 2, 2, 0, verb_kernel_identify },
+ { "kernel-inspect", 2, 2, 0, verb_kernel_inspect },
{ "list", VERB_ANY, 1, 0, verb_list },
+ { "unlink", 2, 2, 0, verb_unlink },
+ { "cleanup", VERB_ANY, 1, 0, verb_list },
{ "set-default", 2, 2, 0, verb_set_efivar },
{ "set-oneshot", 2, 2, 0, verb_set_efivar },
{ "set-timeout", 2, 2, 0, verb_set_efivar },
extern char *arg_image;
extern InstallSource arg_install_source;
extern char *arg_efi_boot_option_description;
+extern bool arg_dry_run;
static inline const char *arg_dollar_boot_path(void) {
/* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include <efi.h>
-#include <efilib.h>
-
-#include "util.h"
-
-void efi_assert(const char *expr, const char *file, unsigned line, const char *function) {
- log_error_stall(L"systemd-boot assertion '%a' failed at %a:%u, function %a(). Halting.", expr, file, line, function);
- for (;;)
- BS->Stall(60 * 1000 * 1000);
-}
#include <efi.h>
#include <efigpt.h>
#include <efilib.h>
+#include <inttypes.h>
#include "bcd.h"
#include "bootspec-fundamental.h"
#ifndef GNU_EFI_USE_MS_ABI
/* We do not use uefi_call_wrapper() in systemd-boot. As such, we rely on the
* compiler to do the calling convention conversion for us. This is check is
- * to make sure the -DGNU_EFI_USE_MS_ABI was passed to the comiler. */
+ * to make sure the -DGNU_EFI_USE_MS_ABI was passed to the compiler. */
#error systemd-boot requires compilation with GNU_EFI_USE_MS_ABI defined.
#endif
case TIMEOUT_MENU_HIDDEN:
return xstrdup16(u"Menu disabled. Hold down key at bootup to show menu.");
default:
- return xpool_print(L"Menu timeout set to %u s.", *t);
+ return xasprintf("Menu timeout set to %u s.", *t);
}
}
return cache;
}
-static void ps_string(const char16_t *fmt, const void *value) {
- assert(fmt);
- if (value)
- Print(fmt, value);
-}
-
-static void ps_bool(const char16_t *fmt, bool value) {
- assert(fmt);
- Print(fmt, yes_no(value));
-}
-
static bool ps_continue(void) {
- if (unicode_supported())
- Print(L"\n─── Press any key to continue, ESC or q to quit. ───\n\n");
- else
- Print(L"\n--- Press any key to continue, ESC or q to quit. ---\n\n");
+ const char16_t *sep = unicode_supported() ? u"───" : u"---";
+ printf("\n%ls Press any key to continue, ESC or q to quit. %ls\n\n", sep, sep);
uint64_t key;
return console_key_read(&key, UINT64_MAX) == EFI_SUCCESS &&
secure = secure_boot_mode();
(void) efivar_get(LOADER_GUID, L"LoaderDevicePartUUID", &device_part_uuid);
- /* We employ some unusual indentation here for readability. */
-
- ps_string(L" systemd-boot version: %a\n", GIT_VERSION);
- ps_string(L" loaded image: %s\n", loaded_image_path);
- ps_string(L" loader partition UUID: %s\n", device_part_uuid);
- ps_string(L" architecture: %a\n", EFI_MACHINE_TYPE_NAME);
- Print(L" UEFI specification: %u.%02u\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
- ps_string(L" firmware vendor: %s\n", ST->FirmwareVendor);
- Print(L" firmware version: %u.%02u\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
- Print(L" OS indications: %lu\n", get_os_indications_supported());
- Print(L" secure boot: %s (%s)\n", yes_no(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)), secure_boot_mode_to_string(secure));
- ps_bool(L" shim: %s\n", shim_loaded());
- ps_bool(L" TPM: %s\n", tpm_present());
- Print(L" console mode: %d/%ld (%" PRIuN L"x%" PRIuN L" @%ux%u)\n", ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1), x_max, y_max, screen_width, screen_height);
+ printf(" systemd-boot version: " GIT_VERSION "\n");
+ if (loaded_image_path)
+ printf(" loaded image: %ls\n", loaded_image_path);
+ if (device_part_uuid)
+ printf(" loader partition UUID: %ls\n", device_part_uuid);
+ printf(" architecture: " EFI_MACHINE_TYPE_NAME "\n");
+ printf(" UEFI specification: %u.%02u\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
+ printf(" firmware vendor: %ls\n", ST->FirmwareVendor);
+ printf(" firmware version: %u.%02u\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
+ printf(" OS indications: %#" PRIx64 "\n", get_os_indications_supported());
+ printf(" secure boot: %ls (%ls)\n",
+ yes_no(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)),
+ secure_boot_mode_to_string(secure));
+ printf(" shim: %ls\n", yes_no(shim_loaded()));
+ printf(" TPM: %ls\n", yes_no(tpm_present()));
+ printf(" console mode: %i/%" PRIi64 " (%zux%zu @%ux%u)\n",
+ ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1),
+ x_max, y_max, screen_width, screen_height);
if (!ps_continue())
return;
switch (config->timeout_sec_config) {
case TIMEOUT_UNSET:
- break;
+ break;
case TIMEOUT_MENU_FORCE:
- Print(L" timeout (config): menu-force\n"); break;
+ printf(" timeout (config): menu-force\n");
+ break;
case TIMEOUT_MENU_HIDDEN:
- Print(L" timeout (config): menu-hidden\n"); break;
+ printf(" timeout (config): menu-hidden\n");
+ break;
default:
- Print(L" timeout (config): %u s\n", config->timeout_sec_config);
+ printf(" timeout (config): %u s\n", config->timeout_sec_config);
}
switch (config->timeout_sec_efivar) {
case TIMEOUT_UNSET:
- break;
+ break;
case TIMEOUT_MENU_FORCE:
- Print(L" timeout (EFI var): menu-force\n"); break;
+ printf(" timeout (EFI var): menu-force\n");
+ break;
case TIMEOUT_MENU_HIDDEN:
- Print(L" timeout (EFI var): menu-hidden\n"); break;
+ printf(" timeout (EFI var): menu-hidden\n");
+ break;
default:
- Print(L" timeout (EFI var): %u s\n", config->timeout_sec_efivar);
+ printf(" timeout (EFI var): %u s\n", config->timeout_sec_efivar);
}
- ps_string(L" default (config): %s\n", config->entry_default_config);
- ps_string(L" default (EFI var): %s\n", config->entry_default_efivar);
- ps_string(L" default (one-shot): %s\n", config->entry_oneshot);
- ps_string(L" saved entry: %s\n", config->entry_saved);
- ps_bool(L" editor: %s\n", config->editor);
- ps_bool(L" auto-entries: %s\n", config->auto_entries);
- ps_bool(L" auto-firmware: %s\n", config->auto_firmware);
- ps_bool(L" beep: %s\n", config->beep);
- ps_bool(L" reboot-for-bitlocker: %s\n", config->reboot_for_bitlocker);
+ if (config->entry_default_config)
+ printf(" default (config): %ls\n", config->entry_default_config);
+ if (config->entry_default_efivar)
+ printf(" default (EFI var): %ls\n", config->entry_default_efivar);
+ if (config->entry_oneshot)
+ printf(" default (one-shot): %ls\n", config->entry_oneshot);
+ if (config->entry_saved)
+ printf(" saved entry: %ls\n", config->entry_saved);
+ printf(" editor: %ls\n", yes_no(config->editor));
+ printf(" auto-entries: %ls\n", yes_no(config->auto_entries));
+ printf(" auto-firmware: %ls\n", yes_no(config->auto_firmware));
+ printf(" beep: %ls\n", yes_no(config->beep));
+ printf(" reboot-for-bitlocker: %ls\n", yes_no(config->reboot_for_bitlocker));
switch (config->secure_boot_enroll) {
case ENROLL_OFF:
- Print(L" secure-boot-enroll: off\n"); break;
+ printf(" secure-boot-enroll: off\n");
+ break;
case ENROLL_MANUAL:
- Print(L" secure-boot-enroll: manual\n"); break;
+ printf(" secure-boot-enroll: manual\n");
+ break;
case ENROLL_FORCE:
- Print(L" secure-boot-enroll: force\n"); break;
+ printf(" secure-boot-enroll: force\n");
+ break;
default:
assert_not_reached();
}
switch (config->console_mode) {
case CONSOLE_MODE_AUTO:
- Print(L" console-mode (config): %s\n", L"auto"); break;
+ printf(" console-mode (config): auto\n");
+ break;
case CONSOLE_MODE_KEEP:
- Print(L" console-mode (config): %s\n", L"keep"); break;
+ printf(" console-mode (config): keep\n");
+ break;
case CONSOLE_MODE_FIRMWARE_MAX:
- Print(L" console-mode (config): %s\n", L"max"); break;
+ printf(" console-mode (config): max\n");
+ break;
default:
- Print(L" console-mode (config): %ld\n", config->console_mode); break;
+ printf(" console-mode (config): %" PRIi64 "\n", config->console_mode);
+ break;
}
/* EFI var console mode is always a concrete value or unset. */
if (config->console_mode_efivar != CONSOLE_MODE_KEEP)
- Print(L"console-mode (EFI var): %ld\n", config->console_mode_efivar);
+ printf("console-mode (EFI var): %" PRIi64 "\n", config->console_mode_efivar);
if (!ps_continue())
return;
for (UINTN i = 0; i < config->entry_count; i++) {
ConfigEntry *entry = config->entries[i];
-
- _cleanup_free_ char16_t *dp = NULL;
- if (entry->device)
- (void) device_path_to_str(DevicePathFromHandle(entry->device), &dp);
-
- Print(L" config entry: %" PRIuN L"/%" PRIuN L"\n", i + 1, config->entry_count);
- ps_string(L" id: %s\n", entry->id);
- ps_string(L" title: %s\n", entry->title);
- ps_string(L" title show: %s\n", streq16(entry->title, entry->title_show) ? NULL : entry->title_show);
- ps_string(L" sort key: %s\n", entry->sort_key);
- ps_string(L" version: %s\n", entry->version);
- ps_string(L" machine-id: %s\n", entry->machine_id);
- ps_string(L" device: %s\n", dp);
- ps_string(L" loader: %s\n", entry->loader);
+ EFI_DEVICE_PATH *dp = NULL;
+ _cleanup_free_ char16_t *dp_str = NULL;
+
+ if (entry->device &&
+ BS->HandleProtocol(entry->device, &(EFI_GUID) EFI_DEVICE_PATH_PROTOCOL_GUID, (void **) &dp) ==
+ EFI_SUCCESS)
+ (void) device_path_to_str(dp, &dp_str);
+
+ printf(" config entry: %zu/%zu\n", i + 1, config->entry_count);
+ printf(" id: %ls\n", entry->id);
+ if (entry->title)
+ printf(" title: %ls\n", entry->title);
+ if (entry->title_show && !streq16(entry->title, entry->title_show))
+ printf(" title show: %ls\n", entry->title_show);
+ if (entry->sort_key)
+ printf(" sort key: %ls\n", entry->sort_key);
+ if (entry->version)
+ printf(" version: %ls\n", entry->version);
+ if (entry->machine_id)
+ printf(" machine-id: %ls\n", entry->machine_id);
+ if (dp_str)
+ printf(" device: %ls\n", dp_str);
+ if (entry->loader)
+ printf(" loader: %ls\n", entry->loader);
STRV_FOREACH(initrd, entry->initrd)
- Print(L" initrd: %s\n", *initrd);
- ps_string(L" devicetree: %s\n", entry->devicetree);
- ps_string(L" options: %s\n", entry->options);
- ps_bool(L" internal call: %s\n", !!entry->call);
-
- ps_bool(L"counting boots: %s\n", entry->tries_left >= 0);
+ printf(" initrd: %ls\n", *initrd);
+ if (entry->devicetree)
+ printf(" devicetree: %ls\n", entry->devicetree);
+ if (entry->options)
+ printf(" options: %ls\n", entry->options);
+ printf(" internal call: %ls\n", yes_no(!!entry->call));
+
+ printf("counting boots: %ls\n", yes_no(entry->tries_left >= 0));
if (entry->tries_left >= 0) {
- Print(L" tries: %u left, %u done\n", entry->tries_left, entry->tries_done);
- Print(L" current path: %s\\%s\n", entry->path, entry->current_name);
- Print(L" next path: %s\\%s\n", entry->path, entry->next_name);
+ printf(" tries: %i left, %i done\n", entry->tries_left, entry->tries_done);
+ printf(" current path: %ls\\%ls\n", entry->path, entry->current_name);
+ printf(" next path: %ls\\%ls\n", entry->path, entry->next_name);
}
if (!ps_continue())
EFI_STATUS err;
if (!FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI))
- return log_error_status_stall(EFI_UNSUPPORTED, L"Reboot to firmware interface not supported.");
+ return log_error_status(EFI_UNSUPPORTED, "Reboot to firmware interface not supported.");
(void) efivar_get_uint64_le(EFI_GLOBAL_GUID, L"OsIndications", &osind);
osind |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
err = efivar_set_uint64_le(EFI_GLOBAL_GUID, L"OsIndications", osind, EFI_VARIABLE_NON_VOLATILE);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error setting OsIndications: %r", err);
+ return log_error_status(err, "Error setting OsIndications: %m");
RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
assert_not_reached();
ST->ConOut->EnableCursor(ST->ConOut, false);
/* draw a single character to make ClearScreen work on some firmware */
- Print(L" ");
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) u" ");
err = console_set_mode(config->console_mode_efivar != CONSOLE_MODE_KEEP ?
config->console_mode_efivar : config->console_mode);
if (err != EFI_SUCCESS) {
clear_screen(COLOR_NORMAL);
- log_error_stall(L"Error switching console mode: %r", err);
+ log_error_status(err, "Error switching console mode: %m");
}
UINTN line_width = 0, entry_padding = 3;
if (timeout_remain > 0) {
free(status);
- status = xpool_print(L"Boot in %u s.", timeout_remain);
+ status = xasprintf("Boot in %u s.", timeout_remain);
}
if (status) {
break;
case KEYPRESS(0, 0, 'v'):
- status = xpool_print(
- L"systemd-boot " GIT_VERSION L" (" EFI_MACHINE_TYPE_NAME L"), "
- L"UEFI Specification %u.%02u, Vendor %s %u.%02u",
+ status = xasprintf(
+ "systemd-boot " GIT_VERSION " (" EFI_MACHINE_TYPE_NAME "), "
+ "UEFI Specification %u.%02u, Vendor %ls %u.%02u",
ST->Hdr.Revision >> 16,
ST->Hdr.Revision & 0xffff,
ST->FirmwareVendor,
case KEYPRESS(0, 0, 'r'):
err = console_set_mode(CONSOLE_MODE_NEXT);
if (err != EFI_SUCCESS)
- status = xpool_print(L"Error changing console mode: %r", err);
+ status = xasprintf_status(err, "Error changing console mode: %m");
else {
config->console_mode_efivar = ST->ConOut->Mode->Mode;
- status = xpool_print(L"Console mode changed to %ld.", config->console_mode_efivar);
+ status = xasprintf(
+ "Console mode changed to %" PRIi64 ".",
+ config->console_mode_efivar);
}
new_mode = true;
break;
err = console_set_mode(config->console_mode == CONSOLE_MODE_KEEP ?
console_mode_initial : config->console_mode);
if (err != EFI_SUCCESS)
- status = xpool_print(L"Error resetting console mode: %r", err);
+ status = xasprintf_status(err, "Error resetting console mode: %m");
else
- status = xpool_print(L"Console mode reset to %s default.",
- config->console_mode == CONSOLE_MODE_KEEP ? L"firmware" : L"configuration file");
+ status = xasprintf(
+ "Console mode reset to %s default.",
+ config->console_mode == CONSOLE_MODE_KEEP ?
+ "firmware" :
+ "configuration file");
new_mode = true;
break;
if (FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
firmware_setup = true;
/* Let's make sure the user really wants to do this. */
- status = xpool_print(L"Press Enter to reboot into firmware interface.");
+ status = xstrdup16(u"Press Enter to reboot into firmware interface.");
} else
- status = xpool_print(L"Reboot into firmware interface not supported.");
+ status = xstrdup16(u"Reboot into firmware interface not supported.");
break;
default:
else {
uint64_t u;
if (!parse_number8(value, &u, NULL) || u > TIMEOUT_TYPE_MAX) {
- log_error_stall(L"Error parsing 'timeout' config option: %a", value);
+ log_error("Error parsing 'timeout' config option: %s", value);
continue;
}
config->timeout_sec_config = u;
if (streq8(key, "default")) {
if (value[0] == '@' && !strcaseeq8(value, "@saved")) {
- log_error_stall(L"Unsupported special entry identifier: %a", value);
+ log_error("Unsupported special entry identifier: %s", value);
continue;
}
free(config->entry_default_config);
if (streq8(key, "editor")) {
err = parse_boolean(value, &config->editor);
if (err != EFI_SUCCESS)
- log_error_stall(L"Error parsing 'editor' config option: %a", value);
+ log_error("Error parsing 'editor' config option: %s", value);
continue;
}
if (streq8(key, "auto-entries")) {
err = parse_boolean(value, &config->auto_entries);
if (err != EFI_SUCCESS)
- log_error_stall(L"Error parsing 'auto-entries' config option: %a", value);
+ log_error("Error parsing 'auto-entries' config option: %s", value);
continue;
}
if (streq8(key, "auto-firmware")) {
err = parse_boolean(value, &config->auto_firmware);
if (err != EFI_SUCCESS)
- log_error_stall(L"Error parsing 'auto-firmware' config option: %a", value);
+ log_error("Error parsing 'auto-firmware' config option: %s", value);
continue;
}
if (streq8(key, "beep")) {
err = parse_boolean(value, &config->beep);
if (err != EFI_SUCCESS)
- log_error_stall(L"Error parsing 'beep' config option: %a", value);
+ log_error("Error parsing 'beep' config option: %s", value);
continue;
}
if (streq8(key, "reboot-for-bitlocker")) {
err = parse_boolean(value, &config->reboot_for_bitlocker);
if (err != EFI_SUCCESS)
- log_error_stall(L"Error parsing 'reboot-for-bitlocker' config option: %a", value);
+ log_error("Error parsing 'reboot-for-bitlocker' config option: %s", value);
}
if (streq8(key, "secure-boot-enroll")) {
else if (streq8(value, "off"))
config->secure_boot_enroll = ENROLL_OFF;
else
- log_error_stall(L"Error parsing 'secure-boot-enroll' config option: %a", value);
+ log_error("Error parsing 'secure-boot-enroll' config option: %s", value);
continue;
}
else {
uint64_t u;
if (!parse_number8(value, &u, NULL) || u > CONSOLE_MODE_RANGE_MAX) {
- log_error_stall(L"Error parsing 'console-mode' config option: %a", value);
+ log_error("Error parsing 'console-mode' config option: %s", value);
continue;
}
config->console_mode = u;
entry->tries_done = tries_done;
entry->path = xstrdup16(path);
entry->current_name = xstrdup16(file);
- entry->next_name = xpool_print(
- L"%.*s%u-%u%s",
- prefix_len,
+ entry->next_name = xasprintf(
+ "%.*ls%" PRIu64 "-%" PRIu64 "%ls",
+ (int) prefix_len,
file,
LESS_BY(tries_left, 1u),
MIN(tries_done + 1, (uint64_t) INT_MAX),
if (!entry->path || !entry->current_name || !entry->next_name)
return;
- old_path = xpool_print(L"%s\\%s", entry->path, entry->current_name);
+ old_path = xasprintf("%ls\\%ls", entry->path, entry->current_name);
err = root_dir->Open(root_dir, &handle, old_path, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL);
if (err != EFI_SUCCESS)
strcpy16(file_info->FileName, entry->next_name);
err = handle->SetInfo(handle, &GenericFileInfo, file_info_size, file_info);
if (err != EFI_SUCCESS) {
- log_error_stall(L"Failed to rename '%s' to '%s', ignoring: %r", old_path, entry->next_name, err);
+ log_error_status(err, "Failed to rename '%ls' to '%ls', ignoring: %m", old_path, entry->next_name);
return;
}
/* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on
* success */
- new_path = xpool_print(L"%s\\%s", entry->path, entry->next_name);
+ new_path = xasprintf("%ls\\%ls", entry->path, entry->next_name);
efivar_set(LOADER_GUID, L"LoaderBootCountPath", new_path, 0);
/* If the file we just renamed is the loader path, then let's update that. */
new = xstr8_to_16(value);
if (entry->options) {
- char16_t *s = xpool_print(L"%s %s", entry->options, new);
+ char16_t *s = xasprintf("%ls %ls", entry->options, new);
free(entry->options);
entry->options = s;
} else
if (err == EFI_SUCCESS)
config->timeout_sec = config->timeout_sec_efivar;
else if (err != EFI_NOT_FOUND)
- log_error_stall(u"Error reading LoaderConfigTimeout EFI variable: %r", err);
+ log_error_status(err, "Error reading LoaderConfigTimeout EFI variable: %m");
err = efivar_get_timeout(u"LoaderConfigTimeoutOneShot", &config->timeout_sec);
if (err == EFI_SUCCESS) {
config->force_menu = true; /* force the menu when this is set */
} else if (err != EFI_NOT_FOUND)
- log_error_stall(u"Error reading LoaderConfigTimeoutOneShot EFI variable: %r", err);
+ log_error_status(err, "Error reading LoaderConfigTimeoutOneShot EFI variable: %m");
err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigConsoleMode", &value);
if (err == EFI_SUCCESS)
continue;
_cleanup_free_ char16_t *t = config->entries[i]->title_show;
- config->entries[i]->title_show = xpool_print(L"%s (%s)", t, config->entries[i]->version);
+ config->entries[i]->title_show = xasprintf("%ls (%ls)", t, config->entries[i]->version);
}
if (entries_unique(config->entries, unique, config->entry_count))
continue;
_cleanup_free_ char16_t *t = config->entries[i]->title_show;
- config->entries[i]->title_show = xpool_print(
- L"%s (%.*s)",
- t,
- strnlen16(config->entries[i]->machine_id, 8),
- config->entries[i]->machine_id);
+ config->entries[i]->title_show = xasprintf("%ls (%.8ls)", t, config->entries[i]->machine_id);
}
if (entries_unique(config->entries, unique, config->entry_count))
continue;
_cleanup_free_ char16_t *t = config->entries[i]->title_show;
- config->entries[i]->title_show = xpool_print(L"%s (%s)", t, config->entries[i]->id);
+ config->entries[i]->title_show = xasprintf("%ls (%ls)", t, config->entries[i]->id);
}
}
for (UINTN i = 0; i < boot_order_size / sizeof(uint16_t); i++) {
_cleanup_free_ char *buf = NULL;
- char16_t name[sizeof(L"Boot0000")];
UINTN buf_size;
- SPrint(name, sizeof(name), L"Boot%04x", (uint32_t) boot_order[i]);
+ _cleanup_free_ char16_t *name = xasprintf("Boot%04x", boot_order[i]);
err = efivar_get_raw(EFI_GLOBAL_GUID, name, &buf, &buf_size);
if (err != EFI_SUCCESS)
continue;
.title = xstrdup16(good_name),
.version = xstrdup16(good_version),
.device = device,
- .loader = xpool_print(L"\\EFI\\Linux\\%s", f->FileName),
+ .loader = xasprintf("\\EFI\\Linux\\%ls", f->FileName),
.sort_key = xstrdup16(good_sort_key),
.key = 'l',
.tries_done = -1,
STRV_FOREACH(i, entry->initrd) {
_cleanup_free_ char16_t *o = options;
if (o)
- options = xpool_print(L"%s initrd=%s", o, *i);
+ options = xasprintf("%ls initrd=%ls", o, *i);
else
- options = xpool_print(L"initrd=%s", *i);
+ options = xasprintf("initrd=%ls", *i);
_cleanup_(file_closep) EFI_FILE *handle = NULL;
err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, 0);
if (entry->options) {
_cleanup_free_ char16_t *o = options;
- options = xpool_print(L"%s %s", o, entry->options);
+ options = xasprintf("%ls %ls", o, entry->options);
}
*ret_options = TAKE_PTR(options);
_cleanup_(file_closep) EFI_FILE *image_root = NULL;
err = open_volume(entry->device, &image_root);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error opening root path: %r", err);
+ return log_error_status(err, "Error opening root path: %m");
err = make_file_device_path(entry->device, entry->loader, &path);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error making file device path: %r", err);
+ return log_error_status(err, "Error making file device path: %m");
UINTN initrd_size = 0;
_cleanup_free_ void *initrd = NULL;
_cleanup_free_ char16_t *options_initrd = NULL;
err = initrd_prepare(image_root, entry, &options_initrd, &initrd, &initrd_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error preparing initrd: %r", err);
+ return log_error_status(err, "Error preparing initrd: %m");
err = shim_load_image(parent_image, path, &image);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error loading %s: %r", entry->loader, err);
+ return log_error_status(err, "Error loading %ls: %m", entry->loader);
if (entry->devicetree) {
err = devicetree_install(&dtstate, image_root, entry->devicetree);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error loading %s: %r", entry->devicetree, err);
+ return log_error_status(err, "Error loading %ls: %m", entry->devicetree);
}
_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
err = initrd_register(initrd, initrd_size, &initrd_handle);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error registering initrd: %r", err);
+ return log_error_status(err, "Error registering initrd: %m");
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
err = BS->HandleProtocol(image, &LoadedImageProtocol, (void **) &loaded_image);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err);
+ return log_error_status(err, "Error getting LoadedImageProtocol handle: %m");
char16_t *options = options_initrd ?: entry->options;
if (options) {
err = pe_kernel_info(loaded_image->ImageBase, &compat_address);
if (err != EFI_SUCCESS) {
if (err != EFI_UNSUPPORTED)
- return log_error_status_stall(err, L"Error finding kernel compat entry address: %r", err);
+ return log_error_status(err, "Error finding kernel compat entry address: %m");
} else if (compat_address > 0) {
EFI_IMAGE_ENTRY_POINT kernel_entry =
(EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + compat_address);
err = EFI_UNSUPPORTED;
}
- return log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err);
+ return log_error_status(err, "Failed to execute %ls (%ls): %m", entry->title_show, entry->loader);
}
static void config_free(Config *config) {
entry = xnew(ConfigEntry, 1);
*entry = (ConfigEntry) {
- .id = xpool_print(L"secure-boot-keys-%s", dirent->FileName),
- .title = xpool_print(L"Enroll Secure Boot keys: %s", dirent->FileName),
- .path = xpool_print(L"\\loader\\keys\\%s", dirent->FileName),
+ .id = xasprintf("secure-boot-keys-%ls", dirent->FileName),
+ .title = xasprintf("Enroll Secure Boot keys: %ls", dirent->FileName),
+ .path = xasprintf("\\loader\\keys\\%ls", dirent->FileName),
.type = LOADER_SECURE_BOOT_KEYS,
.tries_done = -1,
.tries_left = -1,
efivar_set_time_usec(LOADER_GUID, L"LoaderTimeInitUSec", init_usec);
efivar_set(LOADER_GUID, L"LoaderInfo", L"systemd-boot " GIT_VERSION, 0);
- infostr = xpool_print(L"%s %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
+ infostr = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
efivar_set(LOADER_GUID, L"LoaderFirmwareInfo", infostr, 0);
- typestr = xpool_print(L"UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
+ typestr = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
efivar_set(LOADER_GUID, L"LoaderFirmwareType", typestr, 0);
(void) efivar_set_uint64_le(LOADER_GUID, L"LoaderFeatures", loader_features, 0);
return open_volume(loaded_image->DeviceHandle, ret_dir);
}
-EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
+static EFI_STATUS real_main(EFI_HANDLE image) {
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
_cleanup_(file_closep) EFI_FILE *root_dir = NULL;
_cleanup_(config_free) Config config = {};
uint64_t init_usec;
bool menu = false;
- InitializeLib(image, sys_table);
init_usec = time_usec();
- debug_hook(L"systemd-boot");
- /* Uncomment the next line if you need to wait for debugger. */
- // debug_break();
err = BS->OpenProtocol(image,
&LoadedImageProtocol,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err);
+ return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
(void) device_path_to_str(loaded_image->FilePath, &loaded_image_path);
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);
+ return log_error_status(err, "Unable to open root directory: %m");
(void) load_drivers(image, loaded_image, root_dir);
config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir);
if (config.entry_count == 0) {
- log_error_stall(L"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
+ log_error("No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
goto out;
}
BS->CloseProtocol(image, &LoadedImageProtocol, image, NULL);
return err;
}
+
+EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
+ InitializeLib(image, sys_table);
+
+ debug_hook("systemd-boot");
+ /* Uncomment the next line if you need to wait for debugger. */
+ // debug_break();
+
+ EFI_STATUS err = real_main(image);
+ log_wait();
+ return err;
+}
err = BS->CreateEvent(EVT_TIMER, 0, NULL, NULL, &timer);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error creating timer event: %r", err);
+ return log_error_status(err, "Error creating timer event: %m");
EFI_EVENT events[] = {
timer,
TimerRelative,
MIN(timeout_usec, watchdog_ping_usec) * 10);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error arming timer event: %r", err);
+ return log_error_status(err, "Error arming timer event: %m");
(void) BS->SetWatchdogTimer(watchdog_timeout_sec, 0x10000, 0, NULL);
err = BS->WaitForEvent(n_events, events, &index);
(void) BS->SetWatchdogTimer(watchdog_timeout_sec, 0x10000, 0, NULL);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error waiting for events: %r", err);
+ return log_error_status(err, "Error waiting for events: %m");
/* We have keyboard input, process it after this loop. */
if (timer != events[index])
mode = CLAMP(mode, CONSOLE_MODE_RANGE_MIN, CONSOLE_MODE_RANGE_MAX);
old_mode = MAX(CONSOLE_MODE_RANGE_MIN, ST->ConOut->Mode->Mode);
+ log_wait();
err = ST->ConOut->SetMode(ST->ConOut, mode);
if (err == EFI_SUCCESS)
return EFI_SUCCESS;
return NULL;
convert_efi_path(file_path_str);
- return xpool_print(u"%s.extra.d", file_path_str);
+ return xasprintf("%ls.extra.d", file_path_str);
}
EFI_STATUS pack_cpio(
* its file handles. */
goto nothing;
if (err != EFI_SUCCESS)
- return log_error_status_stall(
- err, L"Unable to open root directory: %r", err);
+ return log_error_status(err, "Unable to open root directory: %m");
if (!dropin_dir)
dropin_dir = rel_dropin_dir = get_dropin_dir(loaded_image->FilePath);
/* No extra subdir, that's totally OK */
goto nothing;
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to open extra directory of loaded image: %r", err);
+ return log_error_status(err, "Failed to open extra directory of loaded image: %m");
for (;;) {
_cleanup_free_ char16_t *d = NULL;
err = readdir_harder(extra_dir, &dirent, &dirent_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to read extra directory of loaded image: %r", err);
+ return log_error_status(err, "Failed to read extra directory of loaded image: %m");
if (!dirent) /* End of directory */
break;
* archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */
err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to pack cpio prefix: %r", err);
+ return log_error_status(err, "Failed to pack cpio prefix: %m");
for (UINTN i = 0; i < n_items; i++) {
_cleanup_free_ char *content = NULL;
err = file_read(extra_dir, items[i], 0, 0, &content, &contentsize);
if (err != EFI_SUCCESS) {
- log_error_status_stall(err, L"Failed to read %s, ignoring: %r", items[i], err);
+ log_error_status(err, "Failed to read %ls, ignoring: %m", items[i]);
continue;
}
&inode,
&buffer, &buffer_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to pack cpio file %s: %r", dirent->FileName, err);
+ return log_error_status(err, "Failed to pack cpio file %ls: %m", dirent->FileName);
}
err = pack_cpio_trailer(&buffer, &buffer_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to pack cpio trailer: %r");
+ return log_error_status(err, "Failed to pack cpio trailer: %m");
err = tpm_log_event(
tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured);
if (err != EFI_SUCCESS)
- return log_error_status_stall(
+ return log_error_status(
err,
- L"Unable to add cpio TPM measurement for PCR %u (%s), ignoring: %r",
+ "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m",
tpm_pcr,
- tpm_description,
- err);
+ tpm_description);
*ret_buffer = TAKE_PTR(buffer);
*ret_buffer_size = buffer_size;
err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to pack cpio prefix: %r", err);
+ return log_error_status(err, "Failed to pack cpio prefix: %m");
err = pack_cpio_one(
target_filename,
&inode,
&buffer, &buffer_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to pack cpio file %s: %r", target_filename, err);
+ return log_error_status(err, "Failed to pack cpio file %ls: %m", target_filename);
err = pack_cpio_trailer(&buffer, &buffer_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to pack cpio trailer: %r");
+ return log_error_status(err, "Failed to pack cpio trailer: %m");
err = tpm_log_event(
tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured);
if (err != EFI_SUCCESS)
- return log_error_status_stall(
+ return log_error_status(
err,
- L"Unable to add cpio TPM measurement for PCR %u (%s), ignoring: %r",
+ "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m",
tpm_pcr,
- tpm_description,
- err);
+ tpm_description);
*ret_buffer = TAKE_PTR(buffer);
*ret_buffer_size = buffer_size;
err = BS->LocateProtocol(&EfiDtFixupProtocol, NULL, (void **) &fixup);
if (err != EFI_SUCCESS)
- return log_error_status_stall(EFI_SUCCESS,
- L"Could not locate device tree fixup protocol, skipping.");
+ return log_error_status(EFI_SUCCESS, "Could not locate device tree fixup protocol, skipping.");
size = devicetree_allocated(state);
err = fixup->Fixup(fixup, PHYSICAL_ADDRESS_TO_POINTER(state->addr), &size,
assert(loaded_image);
assert(fname);
- spath = xpool_print(L"\\EFI\\systemd\\drivers\\%s", fname);
+ spath = xasprintf("\\EFI\\systemd\\drivers\\%ls", fname);
err = make_file_device_path(loaded_image->DeviceHandle, spath, &path);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error making file device path: %r", err);
+ return log_error_status(err, "Error making file device path: %m");
err = BS->LoadImage(false, parent_image, path, NULL, 0, &image);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to load image %s: %r", fname, err);
+ return log_error_status(err, "Failed to load image %ls: %m", fname);
err = BS->HandleProtocol(image, &LoadedImageProtocol, (void **)&loaded_image);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to find protocol in driver image %s: %r", fname, err);
+ return log_error_status(err, "Failed to find protocol in driver image %ls: %m", fname);
if (loaded_image->ImageCodeType != EfiBootServicesCode &&
loaded_image->ImageCodeType != EfiRuntimeServicesCode)
- return log_error_status_stall(EFI_INVALID_PARAMETER, L"Image %s is not a driver, refusing.", fname);
+ return log_error("Image %ls is not a driver, refusing.", fname);
err = BS->StartImage(image, NULL, NULL);
if (err != EFI_SUCCESS) {
/* EFI_ABORTED signals an initializing driver. It uses this error code on success
* so that it is unloaded after. */
if (err != EFI_ABORTED)
- log_error_stall(L"Failed to start image %s: %r", fname, err);
+ log_error_status(err, "Failed to start image %ls: %m", fname);
return 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);
+ return log_error_status(err, "Failed to get list of handles: %m");
for (size_t i = 0; i < n_handles; i++)
/* Some firmware gives us some bogus handles (or they might become bad due to
if (err == EFI_NOT_FOUND)
return EFI_SUCCESS;
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to open \\EFI\\systemd\\drivers: %r", err);
+ return log_error_status(err, "Failed to open \\EFI\\systemd\\drivers: %m");
for (;;) {
err = readdir_harder(drivers_dir, &dirent, &dirent_size);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed to read extra directory of loaded image: %r", err);
+ return log_error_status(err, "Failed to read extra directory of loaded image: %m");
if (!dirent) /* End of directory */
break;
#include <stdbool.h>
#include <stdint.h>
+#include <wchar.h>
#include "efi-string.h"
#if SD_BOOT
+# include "missing_efi.h"
# include "util.h"
#else
# include <stdlib.h>
DEFINE_PARSE_NUMBER(char, parse_number8);
DEFINE_PARSE_NUMBER(char16_t, parse_number16);
+static const char * const warn_table[] = {
+ [EFI_SUCCESS] = "Success",
+#if SD_BOOT
+ [EFI_WARN_UNKNOWN_GLYPH] = "Unknown glyph",
+ [EFI_WARN_DELETE_FAILURE] = "Delete failure",
+ [EFI_WARN_WRITE_FAILURE] = "Write failure",
+ [EFI_WARN_BUFFER_TOO_SMALL] = "Buffer too small",
+ [EFI_WARN_STALE_DATA] = "Stale data",
+ [EFI_WARN_FILE_SYSTEM] = "File system",
+ [EFI_WARN_RESET_REQUIRED] = "Reset required",
+#endif
+};
+
+/* Errors have MSB set, remove it to keep the table compact. */
+#define NOERR(err) ((err) & ~EFI_ERROR_MASK)
+
+static const char * const err_table[] = {
+ [NOERR(EFI_ERROR_MASK)] = "Error",
+ [NOERR(EFI_LOAD_ERROR)] = "Load error",
+#if SD_BOOT
+ [NOERR(EFI_INVALID_PARAMETER)] = "Invalid parameter",
+ [NOERR(EFI_UNSUPPORTED)] = "Unsupported",
+ [NOERR(EFI_BAD_BUFFER_SIZE)] = "Bad buffer size",
+ [NOERR(EFI_BUFFER_TOO_SMALL)] = "Buffer too small",
+ [NOERR(EFI_NOT_READY)] = "Not ready",
+ [NOERR(EFI_DEVICE_ERROR)] = "Device error",
+ [NOERR(EFI_WRITE_PROTECTED)] = "Write protected",
+ [NOERR(EFI_OUT_OF_RESOURCES)] = "Out of resources",
+ [NOERR(EFI_VOLUME_CORRUPTED)] = "Volume corrupt",
+ [NOERR(EFI_VOLUME_FULL)] = "Volume full",
+ [NOERR(EFI_NO_MEDIA)] = "No media",
+ [NOERR(EFI_MEDIA_CHANGED)] = "Media changed",
+ [NOERR(EFI_NOT_FOUND)] = "Not found",
+ [NOERR(EFI_ACCESS_DENIED)] = "Access denied",
+ [NOERR(EFI_NO_RESPONSE)] = "No response",
+ [NOERR(EFI_NO_MAPPING)] = "No mapping",
+ [NOERR(EFI_TIMEOUT)] = "Time out",
+ [NOERR(EFI_NOT_STARTED)] = "Not started",
+ [NOERR(EFI_ALREADY_STARTED)] = "Already started",
+ [NOERR(EFI_ABORTED)] = "Aborted",
+ [NOERR(EFI_ICMP_ERROR)] = "ICMP error",
+ [NOERR(EFI_TFTP_ERROR)] = "TFTP error",
+ [NOERR(EFI_PROTOCOL_ERROR)] = "Protocol error",
+ [NOERR(EFI_INCOMPATIBLE_VERSION)] = "Incompatible version",
+ [NOERR(EFI_SECURITY_VIOLATION)] = "Security violation",
+ [NOERR(EFI_CRC_ERROR)] = "CRC error",
+ [NOERR(EFI_END_OF_MEDIA)] = "End of media",
+ [29] = "Reserved (29)",
+ [30] = "Reserved (30)",
+ [NOERR(EFI_END_OF_FILE)] = "End of file",
+ [NOERR(EFI_INVALID_LANGUAGE)] = "Invalid language",
+ [NOERR(EFI_COMPROMISED_DATA)] = "Compromised data",
+ [NOERR(EFI_IP_ADDRESS_CONFLICT)] = "IP address conflict",
+ [NOERR(EFI_HTTP_ERROR)] = "HTTP error",
+#endif
+};
+
+static const char *status_to_string(EFI_STATUS status) {
+ if (status <= ELEMENTSOF(warn_table) - 1)
+ return warn_table[status];
+ if (status >= EFI_ERROR_MASK && status <= ((ELEMENTSOF(err_table) - 1) | EFI_ERROR_MASK))
+ return err_table[NOERR(status)];
+ return NULL;
+}
+
+typedef struct {
+ size_t padded_len; /* Field width in printf. */
+ size_t len; /* Precision in printf. */
+ bool pad_zero;
+ bool align_left;
+ bool alternative_form;
+ bool long_arg;
+ bool longlong_arg;
+ bool have_field_width;
+
+ const char *str;
+ const wchar_t *wstr;
+
+ /* For numbers. */
+ bool is_signed;
+ bool lowercase;
+ int8_t base;
+ char sign_pad; /* For + and (space) flags. */
+} SpecifierContext;
+
+typedef struct {
+ char16_t stack_buf[128]; /* We use stack_buf first to avoid allocations in most cases. */
+ char16_t *dyn_buf; /* Allocated buf or NULL if stack_buf is used. */
+ char16_t *buf; /* Points to the current active buf. */
+ size_t n_buf; /* Len of buf (in char16_t's, not bytes!). */
+ size_t n; /* Used len of buf (in char16_t's). This is always <n_buf. */
+
+ EFI_STATUS status;
+ const char *format;
+ va_list ap;
+} FormatContext;
+
+static void grow_buf(FormatContext *ctx, size_t need) {
+ assert(ctx);
+
+ assert_se(!__builtin_add_overflow(ctx->n, need, &need));
+
+ if (need < ctx->n_buf)
+ return;
+
+ /* Greedily allocate if we can. */
+ if (__builtin_mul_overflow(need, 2, &ctx->n_buf))
+ ctx->n_buf = need;
+
+ /* We cannot use realloc here as ctx->buf may be ctx->stack_buf, which we cannot free. */
+ char16_t *new_buf = xnew(char16_t, ctx->n_buf);
+ memcpy(new_buf, ctx->buf, ctx->n * sizeof(*ctx->buf));
+
+ free(ctx->dyn_buf);
+ ctx->buf = ctx->dyn_buf = new_buf;
+}
+
+static void push_padding(FormatContext *ctx, char pad, size_t len) {
+ assert(ctx);
+ while (len > 0) {
+ len--;
+ ctx->buf[ctx->n++] = pad;
+ }
+}
+
+static bool push_str(FormatContext *ctx, SpecifierContext *sp) {
+ assert(ctx);
+ assert(sp);
+
+ sp->padded_len = LESS_BY(sp->padded_len, sp->len);
+
+ grow_buf(ctx, sp->padded_len + sp->len);
+
+ if (!sp->align_left)
+ push_padding(ctx, ' ', sp->padded_len);
+
+ /* In userspace unit tests we cannot just memcpy() the wide string. */
+ if (sp->wstr && sizeof(wchar_t) == sizeof(char16_t)) {
+ memcpy(ctx->buf + ctx->n, sp->wstr, sp->len * sizeof(*sp->wstr));
+ ctx->n += sp->len;
+ } else
+ for (size_t i = 0; i < sp->len; i++)
+ ctx->buf[ctx->n++] = sp->str ? sp->str[i] : sp->wstr[i];
+
+ if (sp->align_left)
+ push_padding(ctx, ' ', sp->padded_len);
+
+ assert(ctx->n < ctx->n_buf);
+ return true;
+}
+
+static bool push_num(FormatContext *ctx, SpecifierContext *sp, uint64_t u) {
+ const char *digits = sp->lowercase ? "0123456789abcdef" : "0123456789ABCDEF";
+ char16_t tmp[32];
+ size_t n = 0;
+
+ assert(ctx);
+ assert(sp);
+ assert(IN_SET(sp->base, 10, 16));
+
+ /* "%.0u" prints nothing if value is 0. */
+ if (u == 0 && sp->len == 0)
+ return true;
+
+ if (sp->is_signed && (int64_t) u < 0) {
+ /* We cannot just do "u = -(int64_t)u" here because -INT64_MIN overflows. */
+
+ uint64_t rem = -((int64_t) u % sp->base);
+ u = (int64_t) u / -sp->base;
+ tmp[n++] = digits[rem];
+ sp->sign_pad = '-';
+ }
+
+ while (u > 0 || n == 0) {
+ uint64_t rem = u % sp->base;
+ u /= sp->base;
+ tmp[n++] = digits[rem];
+ }
+
+ /* Note that numbers never get truncated! */
+ size_t prefix = (sp->sign_pad != 0 ? 1 : 0) + (sp->alternative_form ? 2 : 0);
+ size_t number_len = prefix + MAX(n, sp->len);
+ grow_buf(ctx, MAX(sp->padded_len, number_len));
+
+ size_t padding = 0;
+ if (sp->pad_zero)
+ /* Leading zeroes go after the sign or 0x prefix. */
+ number_len = MAX(number_len, sp->padded_len);
+ else
+ padding = LESS_BY(sp->padded_len, number_len);
+
+ if (!sp->align_left)
+ push_padding(ctx, ' ', padding);
+
+ if (sp->sign_pad != 0)
+ ctx->buf[ctx->n++] = sp->sign_pad;
+ if (sp->alternative_form) {
+ ctx->buf[ctx->n++] = '0';
+ ctx->buf[ctx->n++] = sp->lowercase ? 'x' : 'X';
+ }
+
+ push_padding(ctx, '0', LESS_BY(number_len, n + prefix));
+
+ while (n > 0)
+ ctx->buf[ctx->n++] = tmp[--n];
+
+ if (sp->align_left)
+ push_padding(ctx, ' ', padding);
+
+ assert(ctx->n < ctx->n_buf);
+ return true;
+}
+
+/* This helps unit testing. */
+#if SD_BOOT
+# define NULLSTR "(null)"
+# define wcsnlen strnlen16
+#else
+# define NULLSTR "(nil)"
+#endif
+
+static bool handle_format_specifier(FormatContext *ctx, SpecifierContext *sp) {
+ /* Parses one item from the format specifier in ctx and put the info into sp. If we are done with
+ * this specifier returns true, otherwise this function should be called again. */
+
+ /* This implementation assumes 32bit ints. Also note that all types smaller than int are promoted to
+ * int in vararg functions, which is why we fetch only ints for any such types. The compiler would
+ * otherwise warn about fetching smaller types. */
+ assert_cc(sizeof(int) == 4);
+ assert_cc(sizeof(wchar_t) <= sizeof(int));
+ assert_cc(sizeof(intmax_t) <= sizeof(long long));
+
+ assert(ctx);
+ assert(sp);
+
+ switch (*ctx->format) {
+ case '#':
+ sp->alternative_form = true;
+ return false;
+ case '.':
+ sp->have_field_width = true;
+ return false;
+ case '-':
+ sp->align_left = true;
+ return false;
+ case '+':
+ case ' ':
+ sp->sign_pad = *ctx->format;
+ return false;
+
+ case '0':
+ if (!sp->have_field_width) {
+ sp->pad_zero = true;
+ return false;
+ }
+
+ /* If field width has already been provided then 0 is part of precision (%.0s). */
+ _fallthrough_;
+
+ case '*':
+ case '1' ... '9': {
+ int64_t i;
+
+ if (*ctx->format == '*')
+ i = va_arg(ctx->ap, int);
+ else {
+ uint64_t u;
+ if (!parse_number8(ctx->format, &u, &ctx->format) || u > INT_MAX)
+ assert_not_reached();
+ ctx->format--; /* Point it back to the last digit. */
+ i = u;
+ }
+
+ if (sp->have_field_width) {
+ /* Negative precision is ignored. */
+ if (i >= 0)
+ sp->len = (size_t) i;
+ } else {
+ /* Negative field width is treated as positive field width with '-' flag. */
+ if (i < 0) {
+ i *= -1;
+ sp->align_left = true;
+ }
+ sp->padded_len = i;
+ }
+
+ return false;
+ }
+
+ case 'h':
+ if (*(ctx->format + 1) == 'h')
+ ctx->format++;
+ /* char/short gets promoted to int, nothing to do here. */
+ return false;
+
+ case 'l':
+ if (*(ctx->format + 1) == 'l') {
+ ctx->format++;
+ sp->longlong_arg = true;
+ } else
+ sp->long_arg = true;
+ return false;
+
+ case 'z':
+ sp->long_arg = sizeof(size_t) == sizeof(long);
+ sp->longlong_arg = !sp->long_arg && sizeof(size_t) == sizeof(long long);
+ return false;
+
+ case 'j':
+ sp->long_arg = sizeof(intmax_t) == sizeof(long);
+ sp->longlong_arg = !sp->long_arg && sizeof(intmax_t) == sizeof(long long);
+ return false;
+
+ case 't':
+ sp->long_arg = sizeof(ptrdiff_t) == sizeof(long);
+ sp->longlong_arg = !sp->long_arg && sizeof(ptrdiff_t) == sizeof(long long);
+ return false;
+
+ case '%':
+ sp->str = "%";
+ sp->len = 1;
+ return push_str(ctx, sp);
+
+ case 'c':
+ sp->wstr = &(wchar_t){ va_arg(ctx->ap, int) };
+ sp->len = 1;
+ return push_str(ctx, sp);
+
+ case 's':
+ if (sp->long_arg) {
+ sp->wstr = va_arg(ctx->ap, const wchar_t *) ?: L"(null)";
+ sp->len = wcsnlen(sp->wstr, sp->len);
+ } else {
+ sp->str = va_arg(ctx->ap, const char *) ?: "(null)";
+ sp->len = strnlen8(sp->str, sp->len);
+ }
+ return push_str(ctx, sp);
+
+ case 'd':
+ case 'i':
+ case 'u':
+ case 'x':
+ case 'X':
+ sp->lowercase = *ctx->format == 'x';
+ sp->is_signed = IN_SET(*ctx->format, 'd', 'i');
+ sp->base = IN_SET(*ctx->format, 'x', 'X') ? 16 : 10;
+ if (sp->len == SIZE_MAX)
+ sp->len = 1;
+
+ uint64_t v;
+ if (sp->longlong_arg)
+ v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, long long) :
+ va_arg(ctx->ap, unsigned long long);
+ else if (sp->long_arg)
+ v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, long) : va_arg(ctx->ap, unsigned long);
+ else
+ v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, int) : va_arg(ctx->ap, unsigned);
+
+ return push_num(ctx, sp, v);
+
+ case 'p': {
+ const void *ptr = va_arg(ctx->ap, const void *);
+ if (!ptr) {
+ sp->str = NULLSTR;
+ sp->len = STRLEN(NULLSTR);
+ return push_str(ctx, sp);
+ }
+
+ sp->base = 16;
+ sp->lowercase = true;
+ sp->alternative_form = true;
+ sp->len = 0; /* Precision is ignored for %p. */
+ return push_num(ctx, sp, (uintptr_t) ptr);
+ }
+
+ case 'm': {
+ sp->str = status_to_string(ctx->status);
+ if (sp->str) {
+ sp->len = strlen8(sp->str);
+ return push_str(ctx, sp);
+ }
+
+ sp->base = 16;
+ sp->lowercase = true;
+ sp->alternative_form = true;
+ sp->len = 0;
+ return push_num(ctx, sp, ctx->status);
+ }
+
+ default:
+ assert_not_reached();
+ }
+}
+
+/* printf_internal is largely compatible to userspace vasprintf. Any features omitted should trigger asserts.
+ *
+ * Supported:
+ * - Flags: #, 0, +, -, space
+ * - Lengths: h, hh, l, ll, z, j, t
+ * - Specifiers: %, c, s, u, i, d, x, X, p, m
+ * - Precision and width (inline or as int arg using *)
+ *
+ * Notable differences:
+ * - Passing NULL to %s is permitted and will print "(null)"
+ * - %p will also use "(null)"
+ * - The provided EFI_STATUS is used for %m instead of errno
+ * - "\n" is translated to "\r\n" */
+_printf_(2, 0) static char16_t *printf_internal(EFI_STATUS status, const char *format, va_list ap, bool ret) {
+ assert(format);
+
+ FormatContext ctx = {
+ .buf = ctx.stack_buf,
+ .n_buf = ELEMENTSOF(ctx.stack_buf),
+ .format = format,
+ .status = status,
+ };
+
+ /* We cannot put this into the struct without making a copy. */
+ va_copy(ctx.ap, ap);
+
+ while (*ctx.format != '\0') {
+ SpecifierContext sp = { .len = SIZE_MAX };
+
+ switch (*ctx.format) {
+ case '%':
+ ctx.format++;
+ while (!handle_format_specifier(&ctx, &sp))
+ ctx.format++;
+ ctx.format++;
+ break;
+ case '\n':
+ ctx.format++;
+ sp.str = "\r\n";
+ sp.len = 2;
+ push_str(&ctx, &sp);
+ break;
+ default:
+ sp.str = ctx.format++;
+ while (!IN_SET(*ctx.format, '%', '\n', '\0'))
+ ctx.format++;
+ sp.len = ctx.format - sp.str;
+ push_str(&ctx, &sp);
+ }
+ }
+
+ va_end(ctx.ap);
+
+ assert(ctx.n < ctx.n_buf);
+ ctx.buf[ctx.n++] = '\0';
+
+ if (ret) {
+ if (ctx.dyn_buf)
+ return TAKE_PTR(ctx.dyn_buf);
+
+ char16_t *ret_buf = xnew(char16_t, ctx.n);
+ memcpy(ret_buf, ctx.buf, ctx.n * sizeof(*ctx.buf));
+ return ret_buf;
+ }
+
+#if SD_BOOT
+ ST->ConOut->OutputString(ST->ConOut, ctx.buf);
+#endif
+
+ return mfree(ctx.dyn_buf);
+}
+
+void printf_status(EFI_STATUS status, const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ printf_internal(status, format, ap, false);
+ va_end(ap);
+}
+
+void vprintf_status(EFI_STATUS status, const char *format, va_list ap) {
+ printf_internal(status, format, ap, false);
+}
+
+char16_t *xasprintf_status(EFI_STATUS status, const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ char16_t *ret = printf_internal(status, format, ap, true);
+ va_end(ap);
+ return ret;
+}
+
+char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap) {
+ return printf_internal(status, format, ap, true);
+}
+
#if SD_BOOT
/* To provide the actual implementation for these we need to remove the redirection to the builtins. */
# undef memcmp
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <uchar.h>
bool parse_number8(const char *s, uint64_t *ret_u, const char **ret_tail);
bool parse_number16(const char16_t *s, uint64_t *ret_u, const char16_t **ret_tail);
+typedef size_t EFI_STATUS;
+
+#if !SD_BOOT
+/* Provide these for unit testing. */
+enum {
+ EFI_ERROR_MASK = ((EFI_STATUS) 1 << (sizeof(EFI_STATUS) * CHAR_BIT - 1)),
+ EFI_SUCCESS = 0,
+ EFI_LOAD_ERROR = 1 | EFI_ERROR_MASK,
+};
+#endif
+
+#ifdef __clang__
+# define _gnu_printf_(a, b) _printf_(a, b)
+#else
+# define _gnu_printf_(a, b) __attribute__((format(gnu_printf, a, b)))
+#endif
+
+_gnu_printf_(2, 3) void printf_status(EFI_STATUS status, const char *format, ...);
+_gnu_printf_(2, 0) void vprintf_status(EFI_STATUS status, const char *format, va_list ap);
+_gnu_printf_(2, 3) _warn_unused_result_ char16_t *xasprintf_status(EFI_STATUS status, const char *format, ...);
+_gnu_printf_(2, 0) _warn_unused_result_ char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap);
+
#if SD_BOOT
+# define printf(...) printf_status(EFI_SUCCESS, __VA_ARGS__)
+# define xasprintf(...) xasprintf_status(EFI_SUCCESS, __VA_ARGS__)
+
/* The compiler normally has knowledge about standard functions such as memcmp, but this is not the case when
* compiling with -ffreestanding. By referring to builtins, the compiler can check arguments and do
* optimizations again. Note that we still need to provide implementations as the compiler is free to not
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "efi-string.h"
+#include "fuzz.h"
+#include "utf8.h"
+
+typedef struct {
+ EFI_STATUS status;
+ int16_t field_width;
+ int16_t precision;
+ const void *ptr;
+ char c;
+ unsigned char uchar;
+ signed char schar;
+ unsigned short ushort;
+ signed short sshort;
+ unsigned int uint;
+ signed int sint;
+ unsigned long ulong;
+ signed long slong;
+ unsigned long long ulonglong;
+ signed long long slonglong;
+ size_t size;
+ ssize_t ssize;
+ intmax_t intmax;
+ uintmax_t uintmax;
+ ptrdiff_t ptrdiff;
+ char str[];
+} Input;
+
+#define PRINTF_ONE(...) \
+ ({ \
+ _cleanup_free_ char16_t *_ret = xasprintf_status(__VA_ARGS__); \
+ DO_NOT_OPTIMIZE(_ret); \
+ })
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (outside_size_range(size, sizeof(Input), 1024 * 1024))
+ return 0;
+
+ const Input *i = (const Input *) data;
+ size_t len = size - offsetof(Input, str);
+
+ PRINTF_ONE(i->status, "%*.*s", i->field_width, (int) len, i->str);
+ PRINTF_ONE(i->status, "%*.*ls", i->field_width, (int) (len / sizeof(wchar_t)), (const wchar_t *) i->str);
+
+ PRINTF_ONE(i->status, "%% %*.*m", i->field_width, i->precision);
+ PRINTF_ONE(i->status, "%*p", i->field_width, i->ptr);
+ PRINTF_ONE(i->status, "%*c %12340c %56789c", i->field_width, i->c, i->c, i->c);
+
+ PRINTF_ONE(i->status, "%*.*hhu", i->field_width, i->precision, i->uchar);
+ PRINTF_ONE(i->status, "%*.*hhi", i->field_width, i->precision, i->schar);
+ PRINTF_ONE(i->status, "%*.*hu", i->field_width, i->precision, i->ushort);
+ PRINTF_ONE(i->status, "%*.*hi", i->field_width, i->precision, i->sshort);
+ PRINTF_ONE(i->status, "%*.*u", i->field_width, i->precision, i->uint);
+ PRINTF_ONE(i->status, "%*.*i", i->field_width, i->precision, i->sint);
+ PRINTF_ONE(i->status, "%*.*lu", i->field_width, i->precision, i->ulong);
+ PRINTF_ONE(i->status, "%*.*li", i->field_width, i->precision, i->slong);
+ PRINTF_ONE(i->status, "%*.*llu", i->field_width, i->precision, i->ulonglong);
+ PRINTF_ONE(i->status, "%*.*lli", i->field_width, i->precision, i->slonglong);
+
+ PRINTF_ONE(i->status, "%+*.*hhi", i->field_width, i->precision, i->schar);
+ PRINTF_ONE(i->status, "%-*.*hi", i->field_width, i->precision, i->sshort);
+ PRINTF_ONE(i->status, "% *.*i", i->field_width, i->precision, i->sint);
+ PRINTF_ONE(i->status, "%0*li", i->field_width, i->slong);
+ PRINTF_ONE(i->status, "%#*.*llx", i->field_width, i->precision, i->ulonglong);
+
+ PRINTF_ONE(i->status, "%-*.*zx", i->field_width, i->precision, i->size);
+ PRINTF_ONE(i->status, "% *.*zi", i->field_width, i->precision, i->ssize);
+ PRINTF_ONE(i->status, "%0*ji", i->field_width, i->intmax);
+ PRINTF_ONE(i->status, "%#0*jX", i->field_width, i->uintmax);
+ PRINTF_ONE(i->status, "%*.*ti", i->field_width, i->precision, i->ptrdiff);
+
+ return 0;
+}
return err == EFI_NOT_FOUND ? EFI_SUCCESS : err;
/* check current mode */
- err =ConsoleControl->GetMode(ConsoleControl, ¤t, &uga_exists, &stdin_locked);
+ err = ConsoleControl->GetMode(ConsoleControl, ¤t, &uga_exists, &stdin_locked);
if (err != EFI_SUCCESS)
return err;
/* do not touch the mode */
- new = on ? EfiConsoleControlScreenGraphics : EfiConsoleControlScreenText;
+ new = on ? EfiConsoleControlScreenGraphics : EfiConsoleControlScreenText;
if (new == current)
return EFI_SUCCESS;
- err =ConsoleControl->SetMode(ConsoleControl, new);
+ log_wait();
+ err = ConsoleControl->SetMode(ConsoleControl, new);
/* some firmware enables the cursor when switching modes */
ST->ConOut->EnableCursor(ST->ConOut, false);
initrd_length);
#endif
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, u"Bad kernel image: %r", err);
+ return log_error_status(err, "Bad kernel image: %m");
_cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL;
err = load_image(parent, linux_buffer, linux_length, &kernel_image);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, u"Error loading kernel image: %r", err);
+ return log_error_status(err, "Error loading kernel image: %m");
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
err = BS->HandleProtocol(kernel_image, &LoadedImageProtocol, (void **) &loaded_image);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, u"Error getting kernel loaded image protocol: %r", err);
+ return log_error_status(err, "Error getting kernel loaded image protocol: %m");
if (cmdline) {
loaded_image->LoadOptions = (void *) cmdline;
_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, u"Error registering initrd: %r", err);
+ return log_error_status(err, "Error registering initrd: %m");
+ log_wait();
err = BS->StartImage(kernel_image, NULL, NULL);
/* Try calling the kernel compat entry point if one exists. */
err = compat_entry(kernel_image, ST);
}
- return log_error_status_stall(err, u"Error starting kernel image: %r", err);
+ return log_error_status(err, "Error starting kernel image: %m");
}
const BootParams *image_params = (const BootParams *) linux_buffer;
if (image_params->hdr.header != SETUP_MAGIC || image_params->hdr.boot_flag != BOOT_FLAG_MAGIC)
- return log_error_status_stall(EFI_UNSUPPORTED, u"Unsupported kernel image.");
+ return log_error_status(EFI_UNSUPPORTED, "Unsupported kernel image.");
if (image_params->hdr.version < SETUP_VERSION_2_11)
- return log_error_status_stall(EFI_UNSUPPORTED, u"Kernel too old.");
+ return log_error_status(EFI_UNSUPPORTED, "Kernel too old.");
if (!image_params->hdr.relocatable_kernel)
- return log_error_status_stall(EFI_UNSUPPORTED, u"Kernel is not relocatable.");
+ return log_error_status(EFI_UNSUPPORTED, "Kernel is not relocatable.");
/* The xloadflags were added in version 2.12+ of the boot protocol but the handover support predates
* that, so we cannot safety-check this for 2.11. */
if (image_params->hdr.version >= SETUP_VERSION_2_12 &&
!FLAGS_SET(image_params->hdr.xloadflags, XLF_EFI_HANDOVER))
- return log_error_status_stall(EFI_UNSUPPORTED, u"Kernel does not support EFI handover protocol.");
+ return log_error_status(EFI_UNSUPPORTED, "Kernel does not support EFI handover protocol.");
bool can_4g = image_params->hdr.version >= SETUP_VERSION_2_12 &&
FLAGS_SET(image_params->hdr.xloadflags, XLF_CAN_BE_LOADED_ABOVE_4G);
if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX)
- return log_error_status_stall(
+ return log_error_status(
EFI_UNSUPPORTED,
- u"Unified kernel image was loaded above 4G, but kernel lacks support.");
+ "Unified kernel image was loaded above 4G, but kernel lacks support.");
if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) + initrd_length > UINT32_MAX)
- return log_error_status_stall(
- EFI_UNSUPPORTED, u"Initrd is above 4G, but kernel lacks support.");
+ return log_error_status(EFI_UNSUPPORTED, "Initrd is above 4G, but kernel lacks support.");
_cleanup_pages_ Pages boot_params_page = xmalloc_pages(
can_4g ? AllocateAnyPages : AllocateMaxAddress,
boot_params->hdr.ramdisk_size = initrd_length;
boot_params->ext_ramdisk_size = ((uint64_t) initrd_length) >> 32;
+ log_wait();
linux_efi_handover(parent, (uintptr_t) linux_buffer, boot_params);
return EFI_LOAD_ERROR;
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "log.h"
+
+static unsigned log_count = 0;
+
+void efi_assert(const char *expr, const char *file, unsigned line, const char *function) {
+ log_error("systemd-boot assertion '%s' failed at %s:%u@%s. Halting.", expr, file, line, function);
+ for (;;)
+ BS->Stall(60 * 1000 * 1000);
+}
+
+EFI_STATUS log_internal(EFI_STATUS status, const char *format, ...) {
+ assert(format);
+
+ int32_t attr = ST->ConOut->Mode->Attribute;
+
+ if (ST->ConOut->Mode->CursorColumn > 0)
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) u"\r\n");
+ ST->ConOut->SetAttribute(ST->ConOut, EFI_LIGHTRED | EFI_BACKGROUND_BLACK);
+
+ va_list ap;
+ va_start(ap, format);
+ vprintf_status(status, format, ap);
+ va_end(ap);
+
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) u"\r\n");
+ ST->ConOut->SetAttribute(ST->ConOut, attr);
+
+ log_count++;
+ return status;
+}
+
+void log_wait(void) {
+ if (log_count == 0)
+ return;
+
+ BS->Stall(MIN(4u, log_count) * 2500 * 1000);
+ log_count = 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi-string.h"
+
+void log_wait(void);
+_gnu_printf_(2, 3) EFI_STATUS log_internal(EFI_STATUS status, const char *format, ...);
+#define log_error_status(status, ...) log_internal(status, __VA_ARGS__)
+#define log_error(...) log_internal(EFI_INVALID_PARAMETER, __VA_ARGS__)
+#define log_oom() log_internal(EFI_OUT_OF_RESOURCES, "Out of memory.")
+#define log_trace() log_internal(EFI_SUCCESS, "%s:%i@%s", __FILE__, __LINE__, __func__)
load_options,
&measured);
if (err != EFI_SUCCESS)
- return log_error_status_stall(
+ return log_error_status(
err,
- L"Unable to add load options (i.e. kernel command) line measurement to PCR %u: %r",
- TPM_PCR_INDEX_KERNEL_PARAMETERS,
- err);
+ "Unable to add load options (i.e. kernel command) line measurement to PCR %u: %m",
+ TPM_PCR_INDEX_KERNEL_PARAMETERS);
if (ret_measured)
*ret_measured = measured;
]
)
-# Our code size has increased enough to possibly create overlapping PE sections
-# at sd-stub runtime, which will often enough prevent the image from booting.
-# This only happens because the usual instructions for assembling a unified
-# kernel image contain hardcoded addresses for section VMAs added in. Until a
-# proper solution is in place, we can at least compile with as least -O1 to
-# reduce the likelihood of this happening.
-# https://github.com/systemd/systemd/issues/24202
-efi_cflags += '-O1'
-
efi_cflags += cc.get_supported_arguments({
'ia32': ['-mno-sse', '-mno-mmx'],
'x86_64': ['-mno-red-zone', '-mno-sse', '-mno-mmx'],
'graphics.h',
'initrd.h',
'linux.h',
+ 'log.h',
'measure.h',
'missing_efi.h',
'part-discovery.h',
)
common_sources = files(
- 'assert.c',
'console.c',
'devicetree.c',
+ 'drivers.c',
'disk.c',
'efi-string.c',
'graphics.c',
'initrd.c',
+ 'log.c',
'measure.c',
'part-discovery.c',
'pe.c',
'secure-boot.c',
'ticks.c',
'util.c',
+ 'vmm.c',
)
systemd_boot_sources = files(
'boot.c',
- 'drivers.c',
'shim.c',
- 'vmm.c',
)
stub_sources = files(
fuzzers += [
[files('fuzz-bcd.c', 'bcd.c', 'efi-string.c')],
[files('fuzz-efi-string.c', 'efi-string.c')],
+ [files('fuzz-efi-printf.c', 'efi-string.c')],
]
endif
'-j', '.sdata',
'-j', '.sdmagic',
'-j', '.text',
+ '--strip-all',
'--section-alignment=512',
efi_format,
'@INPUT@', '@OUTPUT@'],
void *StdErr;
} EFI_SHELL_PARAMETERS_PROTOCOL;
#endif
+
+#ifndef EFI_WARN_UNKNOWN_GLYPH
+# define EFI_WARN_UNKNOWN_GLYPH 1
+#endif
+
+#ifndef EFI_WARN_RESET_REQUIRED
+# define EFI_WARN_STALE_DATA 5
+# define EFI_WARN_FILE_SYSTEM 6
+# define EFI_WARN_RESET_REQUIRED 7
+# define EFI_IP_ADDRESS_CONFLICT EFIERR(34)
+# define EFI_HTTP_ERROR EFIERR(35)
+#endif
if (in_memory) {
if (prev_section_addr > sect->VirtualAddress)
- log_error_stall(u"Overlapping PE sections detected. Boot may fail due to image memory corruption!");
+ log_error("Overlapping PE sections detected. Boot may fail due to image memory corruption!");
prev_section_addr = sect->VirtualAddress + sect->VirtualSize;
}
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);
+ return log_error_status(err, "Failed to acquire RNG data: %m");
return EFI_SUCCESS;
}
err = efivar_get_raw(LOADER_GUID, L"LoaderSystemToken", &data, &size);
if (err != EFI_SUCCESS) {
if (err != EFI_NOT_FOUND)
- log_error_stall(L"Failed to read LoaderSystemToken EFI variable: %r", err);
+ log_error_status(err, "Failed to read LoaderSystemToken EFI variable: %m");
return err;
}
if (size <= 0)
- return log_error_status_stall(EFI_NOT_FOUND, L"System token too short, ignoring.");
+ return log_error_status(EFI_NOT_FOUND, "System token too short, ignoring.");
*ret = TAKE_PTR(data);
*ret_size = size;
0);
if (err != EFI_SUCCESS) {
if (err != EFI_NOT_FOUND && err != EFI_WRITE_PROTECTED)
- log_error_stall(L"Failed to open random seed file: %r", err);
+ log_error_status(err, "Failed to open random seed file: %m");
return err;
}
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", err);
+ return log_error_status(err, "Failed to get file info for random seed: %m");
size = info->FileSize;
if (size < RANDOM_MAX_SIZE_MIN)
- return log_error_status_stall(EFI_INVALID_PARAMETER, L"Random seed file is too short.");
+ return log_error("Random seed file is too short.");
if (size > RANDOM_MAX_SIZE_MAX)
- return log_error_status_stall(EFI_INVALID_PARAMETER, L"Random seed file is too large.");
+ return log_error("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);
+ return log_error_status(err, "Failed to read random seed file: %m");
if (rsize != size) {
explicit_bzero_safe(seed, rsize);
- return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short read on random seed file.");
+ return log_error_status(EFI_PROTOCOL_ERROR, "Short read on random seed file.");
}
sha256_process_bytes(&size, sizeof(size), &hash);
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);
+ return log_error_status(err, "Failed to seek to beginning of random seed file: %m");
/* 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 && !seeded_by_efi)
- return log_error_status_stall(err, L"Failed to acquire UEFI monotonic counter: %r", err);
+ return log_error_status(err, "Failed to acquire UEFI monotonic counter: %m");
size = sizeof(uefi_monotonic_counter);
sha256_process_bytes(&size, sizeof(size), &hash);
sha256_process_bytes(&uefi_monotonic_counter, size, &hash);
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);
+ return log_error_status(err, "Failed to seek to offset of random seed file: %m");
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);
+ return log_error_status(err, "Failed to write random seed file: %m");
if (wsize != info->FileSize - size)
- return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
+ return log_error_status(EFI_PROTOCOL_ERROR, "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);
+ return log_error_status(err, "Failed to flush random seed file: %m");
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);
+ return log_error_status(err, "Failed to seek to beginning of random seed file: %m");
/* 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);
+ * return log_error_status(err, "Failed to truncate random seed file: %u");
*
* 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
wsize = size;
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);
+ return log_error_status(err, "Failed to write random seed file: %m");
if (wsize != size)
- return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
+ return log_error_status(EFI_PROTOCOL_ERROR, "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);
+ return log_error_status(err, "Failed to flush random seed file: %m");
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);
+ return log_error_status(err, "Failed to allocate EFI table for random seed: %m");
new_seed_table->size = DESIRED_SEED_SIZE;
/* hash = hash_key || 1 */
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 install EFI table for random seed: %r", err);
+ return log_error_status(err, "Failed to install EFI table for random seed: %m");
TAKE_PTR(new_seed_table);
if (previous_seed_table) {
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "console.h"
#include "sbat.h"
#include "secure-boot.h"
-#include "console.h"
#include "util.h"
+#include "vmm.h"
bool secure_boot_enabled(void) {
bool secure = false; /* avoid false maybe-uninitialized warning */
clear_screen(COLOR_NORMAL);
- Print(L"Enrolling secure boot keys from directory: %s\n"
- L"Warning: Enrolling custom Secure Boot keys might soft-brick your machine!\n",
- path);
-
- unsigned timeout_sec = 15;
- for(;;) {
- /* Enrolling secure boot keys is safe to do in virtualized environments as there is nothing
- * we can brick there. */
- if (in_hypervisor())
- break;
-
- PrintAt(0, ST->ConOut->Mode->CursorRow, L"Enrolling in %2u s, press any key to abort.", timeout_sec);
-
- uint64_t key;
- err = console_key_read(&key, 1000 * 1000);
- if (err == EFI_NOT_READY)
- continue;
- if (err == EFI_TIMEOUT) {
- if (timeout_sec == 0) /* continue enrolling keys */
- break;
- timeout_sec--;
- continue;
+ printf("Enrolling secure boot keys from directory: %ls\n", path);
+
+ /* Enrolling secure boot keys is safe to do in virtualized environments as there is nothing
+ * we can brick there. */
+ if (!in_hypervisor()) {
+ printf("Warning: Enrolling custom Secure Boot keys might soft-brick your machine!\n");
+
+ unsigned timeout_sec = 15;
+ for (;;) {
+ printf("\rEnrolling in %2u s, press any key to abort.", timeout_sec);
+
+ uint64_t key;
+ err = console_key_read(&key, 1000 * 1000);
+ if (err == EFI_NOT_READY)
+ continue;
+ if (err == EFI_TIMEOUT) {
+ if (timeout_sec == 0) /* continue enrolling keys */
+ break;
+ timeout_sec--;
+ continue;
+ }
+ if (err != EFI_SUCCESS)
+ return log_error_status(
+ err,
+ "Error waiting for user input to enroll Secure Boot keys: %m");
+
+ /* user aborted, returning EFI_SUCCESS here allows the user to go back to the menu */
+ return EFI_SUCCESS;
}
- if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error waiting for user input to enroll Secure Boot keys: %r", err);
-
- /* user aborted, returning EFI_SUCCESS here allows the user to go back to the menu */
- return EFI_SUCCESS;
}
_cleanup_(file_closep) EFI_FILE *dir = NULL;
err = open_directory(root_dir, path, &dir);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Failed opening keys directory %s: %r", path, err);
+ return log_error_status(err, "Failed opening keys directory %ls: %m", path);
struct {
const char16_t *name;
for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) {
err = file_read(dir, sb_vars[i].filename, 0, 0, &sb_vars[i].buffer, &sb_vars[i].size);
if (err != EFI_SUCCESS) {
- log_error_stall(L"Failed reading file %s\\%s: %r", path, sb_vars[i].filename, err);
+ log_error_status(err, "Failed reading file %ls\\%ls: %m", path, sb_vars[i].filename);
goto out_deallocate;
}
}
err = efivar_set_raw(&sb_vars[i].vendor, sb_vars[i].name, sb_vars[i].buffer, sb_vars[i].size, sb_vars_opts);
if (err != EFI_SUCCESS) {
- log_error_stall(L"Failed to write %s secure boot variable: %r", sb_vars[i].name, err);
+ log_error_status(err, "Failed to write %ls secure boot variable: %m", sb_vars[i].name);
goto out_deallocate;
}
}
/* if LoaderFirmwareInfo is not set, let's set it */
if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) {
_cleanup_free_ char16_t *s = NULL;
- s = xpool_print(L"%s %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
+ s = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
efivar_set(LOADER_GUID, L"LoaderFirmwareInfo", s, 0);
}
/* ditto for LoaderFirmwareType */
if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) {
_cleanup_free_ char16_t *s = NULL;
- s = xpool_print(L"UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
+ s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0);
}
*ret = xstrdup16(shell->Argv[1]);
for (size_t i = 2; i < shell->Argc; i++) {
_cleanup_free_ char16_t *old = *ret;
- *ret = xpool_print(u"%s %s", old, shell->Argv[i]);
+ *ret = xasprintf("%ls %ls", old, shell->Argv[i]);
}
mangle_stub_cmdline(*ret);
return true;
}
-EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
+static EFI_STATUS real_main(EFI_HANDLE image) {
_cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
size_t linux_size, initrd_size, dt_size;
uint64_t loader_features = 0;
EFI_STATUS err;
- InitializeLib(image, sys_table);
- debug_hook(L"systemd-stub");
- /* Uncomment the next line if you need to wait for debugger. */
- // debug_break();
-
err = BS->OpenProtocol(
image,
&LoadedImageProtocol,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err);
+ return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
if (efivar_get_uint64_le(LOADER_GUID, L"LoaderFeatures", &loader_features) != EFI_SUCCESS ||
!FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED)) {
if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) {
if (err == EFI_SUCCESS)
err = EFI_NOT_FOUND;
- return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err);
+ return log_error_status(err, "Unable to locate embedded .linux section: %m");
}
/* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written
err = devicetree_install_from_memory(
&dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size);
if (err != EFI_SUCCESS)
- log_error_stall(L"Error loading embedded devicetree: %r", err);
+ log_error_status(err, "Error loading embedded devicetree: %m");
}
err = linux_exec(image, cmdline,
graphics_mode(false);
return err;
}
+
+EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
+ InitializeLib(image, sys_table);
+
+ debug_hook("systemd-stub");
+ /* Uncomment the next line if you need to wait for debugger. */
+ // debug_break();
+
+ EFI_STATUS err = real_main(image);
+ log_wait();
+ return err;
+}
assert_se(streq16(tail, u"rest"));
}
+_printf_(1, 2) static void test_printf_one(const char *format, ...) {
+ va_list ap, ap_efi;
+ va_start(ap, format);
+ va_copy(ap_efi, ap);
+
+ _cleanup_free_ char *buf = NULL;
+ int r = vasprintf(&buf, format, ap);
+ assert_se(r >= 0);
+ log_info("/* %s(%s) -> \"%.100s\" */", __func__, format, buf);
+
+ _cleanup_free_ char16_t *buf_efi = xvasprintf_status(0, format, ap_efi);
+
+ bool eq = true;
+ for (size_t i = 0; i <= (size_t) r; i++) {
+ if (buf[i] != buf_efi[i])
+ eq = false;
+ buf[i] = buf_efi[i];
+ }
+
+ log_info("%.100s", buf);
+ assert_se(eq);
+
+ va_end(ap);
+ va_end(ap_efi);
+}
+
+TEST(xvasprintf_status) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-zero-length"
+ test_printf_one("");
+#pragma GCC diagnostic pop
+ test_printf_one("string");
+ test_printf_one("%%-%%%%");
+
+ test_printf_one("%p %p %32p %*p %*p", NULL, (void *) 0xF, &errno, 0, &saved_argc, 20, &saved_argv);
+ test_printf_one("%-10p %-32p %-*p %-*p", NULL, &errno, 0, &saved_argc, 20, &saved_argv);
+
+ test_printf_one("%c %3c %*c %*c %-8c", '1', '!', 0, 'a', 9, '_', '>');
+
+ test_printf_one("%s %s %s", "012345", "6789", "ab");
+ test_printf_one("%.4s %.4s %.4s %.0s", "cdefgh", "ijkl", "mn", "@");
+ test_printf_one("%8s %8s %8s", "opqrst", "uvwx", "yz");
+ test_printf_one("%8.4s %8.4s %8.4s %8.0s", "ABCDEF", "GHIJ", "KL", "$");
+ test_printf_one("%4.8s %4.8s %4.8s", "ABCDEFGHIJ", "ABCDEFGH", "ABCD");
+
+ test_printf_one("%.*s %.*s %.*s %.*s", 4, "012345", 4, "6789", 4, "ab", 0, "&");
+ test_printf_one("%*s %*s %*s", 8, "cdefgh", 8, "ijkl", 8, "mn");
+ test_printf_one("%*.*s %*.*s %*.*s %*.*s", 8, 4, "opqrst", 8, 4, "uvwx", 8, 4, "yz", 8, 0, "#");
+ test_printf_one("%*.*s %*.*s %*.*s", 3, 8, "OPQRST", 3, 8, "UVWX", 3, 8, "YZ");
+
+ test_printf_one("%ls %ls %ls", L"012345", L"6789", L"ab");
+ test_printf_one("%.4ls %.4ls %.4ls %.0ls", L"cdefgh", L"ijkl", L"mn", L"@");
+ test_printf_one("%8ls %8ls %8ls", L"opqrst", L"uvwx", L"yz");
+ test_printf_one("%8.4ls %8.4ls %8.4ls %8.0ls", L"ABCDEF", L"GHIJ", L"KL", L"$");
+ test_printf_one("%4.8ls %4.8ls %4.8ls", L"ABCDEFGHIJ", L"ABCDEFGH", L"ABCD");
+
+ test_printf_one("%.*ls %.*ls %.*ls %.*ls", 4, L"012345", 4, L"6789", 4, L"ab", 0, L"&");
+ test_printf_one("%*ls %*ls %*ls", 8, L"cdefgh", 8, L"ijkl", 8, L"mn");
+ test_printf_one("%*.*ls %*.*ls %*.*ls %*.*ls", 8, 4, L"opqrst", 8, 4, L"uvwx", 8, 4, L"yz", 8, 0, L"#");
+ test_printf_one("%*.*ls %*.*ls %*.*ls", 3, 8, L"OPQRST", 3, 8, L"UVWX", 3, 8, L"YZ");
+
+ test_printf_one("%-14s %-10.0s %-10.3s", "left", "", "chopped");
+ test_printf_one("%-14ls %-10.0ls %-10.3ls", L"left", L"", L"chopped");
+
+ test_printf_one("%.6s", (char[]){ 'n', 'o', ' ', 'n', 'u', 'l' });
+ test_printf_one("%.6ls", (wchar_t[]){ 'n', 'o', ' ', 'n', 'u', 'l' });
+
+ test_printf_one("%u %u %u", 0U, 42U, 1234567890U);
+ test_printf_one("%i %i %i", 0, -42, -1234567890);
+ test_printf_one("%x %x %x", 0x0U, 0x42U, 0x123ABCU);
+ test_printf_one("%X %X %X", 0x1U, 0x43U, 0x234BCDU);
+ test_printf_one("%#x %#x %#x", 0x2U, 0x44U, 0x345CDEU);
+ test_printf_one("%#X %#X %#X", 0x3U, 0x45U, 0x456FEDU);
+
+ test_printf_one("%u %lu %llu %zu", UINT_MAX, ULONG_MAX, ULLONG_MAX, SIZE_MAX);
+ test_printf_one("%i %i %zi", INT_MIN, INT_MAX, SSIZE_MAX);
+ test_printf_one("%li %li %lli %lli", LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX);
+ test_printf_one("%x %#lx %llx %#zx", UINT_MAX, ULONG_MAX, ULLONG_MAX, SIZE_MAX);
+ test_printf_one("%X %#lX %llX %#zX", UINT_MAX, ULONG_MAX, ULLONG_MAX, SIZE_MAX);
+ test_printf_one("%ju %ji %ji", UINTMAX_MAX, INTMAX_MIN, INTMAX_MAX);
+ test_printf_one("%ti %ti", PTRDIFF_MIN, PTRDIFF_MAX);
+
+ test_printf_one("%" PRIu32 " %" PRIi32 " %" PRIi32, UINT32_MAX, INT32_MIN, INT32_MAX);
+ test_printf_one("%" PRIx32 " %" PRIX32, UINT32_MAX, UINT32_MAX);
+ test_printf_one("%#" PRIx32 " %#" PRIX32, UINT32_MAX, UINT32_MAX);
+
+ test_printf_one("%" PRIu64 " %" PRIi64 " %" PRIi64, UINT64_MAX, INT64_MIN, INT64_MAX);
+ test_printf_one("%" PRIx64 " %" PRIX64, UINT64_MAX, UINT64_MAX);
+ test_printf_one("%#" PRIx64 " %#" PRIX64, UINT64_MAX, UINT64_MAX);
+
+ test_printf_one("%.11u %.11i %.11x %.11X %#.11x %#.11X", 1U, -2, 3U, 0xA1U, 0xB2U, 0xC3U);
+ test_printf_one("%13u %13i %13x %13X %#13x %#13X", 4U, -5, 6U, 0xD4U, 0xE5U, 0xF6U);
+ test_printf_one("%9.5u %9.5i %9.5x %9.5X %#9.5x %#9.5X", 7U, -8, 9U, 0xA9U, 0xB8U, 0xC7U);
+ test_printf_one("%09u %09i %09x %09X %#09x %#09X", 4U, -5, 6U, 0xD6U, 0xE5U, 0xF4U);
+
+ test_printf_one("%*u %.*u %*i %.*i", 15, 42U, 15, 43U, 15, -42, 15, -43);
+ test_printf_one("%*.*u %*.*i", 14, 10, 13U, 14, 10, -14);
+ test_printf_one("%*x %*X %.*x %.*X", 15, 0x1AU, 15, 0x2BU, 15, 0x3CU, 15, 0x4DU);
+ test_printf_one("%#*x %#*X %#.*x %#.*X", 15, 0xA1U, 15, 0xB2U, 15, 0xC3U, 15, 0xD4U);
+ test_printf_one("%*.*x %*.*X", 14, 10, 0x1AU, 14, 10, 0x2BU);
+ test_printf_one("%#*.*x %#*.*X", 14, 10, 0x3CU, 14, 10, 0x4DU);
+
+ test_printf_one("%+.5i %+.5i % .7i % .7i", -15, 51, -15, 51);
+ test_printf_one("%+5.i %+5.i % 7.i % 7.i", -15, 51, -15, 51);
+
+ test_printf_one("%-10u %-10i %-10x %#-10X %- 10i", 1u, -2, 0xA2D2u, 0XB3F4u, -512);
+ test_printf_one("%-10.6u %-10.6i %-10.6x %#-10.6X %- 10.6i", 1u, -2, 0xA2D2u, 0XB3F4u, -512);
+ test_printf_one("%-6.10u %-6.10i %-6.10x %#-6.10X %- 6.10i", 3u, -4, 0x2A2Du, 0X3B4Fu, -215);
+ test_printf_one("%*.u %.*i %.*i", -4, 9u, -4, 8, -4, -6);
+
+ test_printf_one("%.0u %.0i %.0x %.0X", 0u, 0, 0u, 0u);
+ test_printf_one("%.*u %.*i %.*x %.*X", 0, 0u, 0, 0, 0, 0u, 0, 0u);
+ test_printf_one("%*u %*i %*x %*X", -1, 0u, -1, 0, -1, 0u, -1, 0u);
+
+ test_printf_one("%*s%*s%*s", 256, "", 256, "", 4096, ""); /* Test buf growing. */
+ test_printf_one("%0*i%0*i%0*i", 256, 0, 256, 0, 4096, 0); /* Test buf growing. */
+ test_printf_one("%0*i", INT16_MAX, 0); /* Poor programmer's memzero. */
+
+ /* Non printf-compatible behavior tests below. */
+ char16_t *s;
+
+ assert_se(s = xasprintf_status(0, "\n \r \r\n"));
+ assert_se(streq16(s, u"\r\n \r \r\r\n"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(EFI_SUCCESS, "%m"));
+ assert_se(streq16(s, u"Success"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(EFI_SUCCESS, "%15m"));
+ assert_se(streq16(s, u" Success"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(EFI_LOAD_ERROR, "%m"));
+ assert_se(streq16(s, u"Load error"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(0x42, "%m"));
+ assert_se(streq16(s, u"0x42"));
+ s = mfree(s);
+}
+
TEST(efi_memcmp) {
assert_se(efi_memcmp(NULL, NULL, 0) == 0);
assert_se(efi_memcmp(NULL, NULL, 1) == 0);
#include "ticks.h"
#include "util.h"
+#include "vmm.h"
#ifdef __x86_64__
static uint64_t ticks_read(void) {
#include <efi.h>
#include <efilib.h>
-#if defined(__i386__) || defined(__x86_64__)
-# include <cpuid.h>
-#endif
#include "ticks.h"
#include "util.h"
return err;
}
-void log_error_stall(const char16_t *fmt, ...) {
- va_list args;
-
- assert(fmt);
-
- int32_t attr = ST->ConOut->Mode->Attribute;
- ST->ConOut->SetAttribute(ST->ConOut, EFI_LIGHTRED|EFI_BACKGROUND_BLACK);
-
- if (ST->ConOut->Mode->CursorColumn > 0)
- Print(L"\n");
-
- va_start(args, fmt);
- VPrint(fmt, args);
- va_end(args);
-
- Print(L"\n");
-
- ST->ConOut->SetAttribute(ST->ConOut, attr);
-
- /* Give the user a chance to see the message. */
- BS->Stall(3 * 1000 * 1000);
-}
-
-EFI_STATUS log_oom(void) {
- log_error_stall(L"Out of memory.");
- return EFI_OUT_OF_RESOURCES;
-}
-
void print_at(UINTN x, UINTN y, UINTN attr, const char16_t *str) {
assert(str);
ST->ConOut->SetCursorPosition(ST->ConOut, x, y);
}
void clear_screen(UINTN attr) {
+ log_wait();
ST->ConOut->SetAttribute(ST->ConOut, attr);
ST->ConOut->ClearScreen(ST->ConOut);
}
buf[size*2] = 0;
- log_error_stall(L"%s[%" PRIuN "]: %s", prefix, size, buf);
+ log_error("%ls[%zu]: %ls", prefix, size, buf);
}
#endif
return EFI_SUCCESS;
}
-#if defined(__i386__) || defined(__x86_64__)
-bool in_hypervisor(void) {
- uint32_t eax, ebx, ecx, edx;
-
- /* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
- * environment. */
-
- if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
- return false;
-
- return !!(ecx & 0x80000000U);
-}
-#endif
-
void *find_configuration_table(const EFI_GUID *guid) {
for (UINTN i = 0; i < ST->NumberOfTableEntries; i++)
if (efi_guid_equal(&ST->ConfigurationTable[i].VendorGuid, guid))
#include <efilib.h>
#include <stddef.h>
+#include "log.h"
#include "string-util-fundamental.h"
#define UINTN_MAX (~(UINTN)0)
#define INTN_MAX ((INTN)(UINTN_MAX>>1))
-/* 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. */
-assert_cc(sizeof(int) == sizeof(uint32_t));
-#if __SIZEOF_POINTER__ == 4
-# define PRIuN L"u"
-# define PRIiN L"d"
-#elif __SIZEOF_POINTER__ == 8
-# define PRIuN L"lu"
-# define PRIiN L"ld"
-#else
-# error "Unexpected pointer size"
-#endif
-
static inline void free(void *p) {
if (!p)
return;
return t;
}
-#define xpool_print(fmt, ...) ((char16_t *) ASSERT_SE_PTR(PoolPrint((fmt), ##__VA_ARGS__)))
#define xnew(type, n) ((type *) xmalloc_multiply(sizeof(type), (n)))
typedef struct {
&(const EFI_GUID) { 0x4a67b082, 0x0a4c, 0x41cf, { 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f } }
#define EFI_GLOBAL_GUID &(const EFI_GUID) EFI_GLOBAL_VARIABLE
-void log_error_stall(const char16_t *fmt, ...);
-EFI_STATUS log_oom(void);
-
-/* This works just like log_error_errno() from userspace, but requires you
- * to provide err a second time if you want to use %r in the message! */
-#define log_error_status_stall(err, fmt, ...) \
- ({ \
- log_error_stall(fmt, ##__VA_ARGS__); \
- err; \
- })
-
void print_at(UINTN x, UINTN y, UINTN attr, const char16_t *str);
void clear_screen(UINTN attr);
extern uint8_t _text, _data;
/* Report the relocated position of text and data sections so that a debugger
* can attach to us. See debug-sd-boot.sh for how this can be done. */
-# define debug_hook(identity) Print(identity L"@0x%lx,0x%lx\n", POINTER_TO_PHYSICAL_ADDRESS(&_text), POINTER_TO_PHYSICAL_ADDRESS(&_data))
+# define debug_hook(identity) printf(identity "@%p,%p\n", &_text, &_data)
#else
# define debug_hook(identity)
#endif
EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp);
EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret);
-#if defined(__i386__) || defined(__x86_64__)
-bool in_hypervisor(void);
-#else
-static inline bool in_hypervisor(void) {
- return false;
-}
-#endif
-
static inline bool efi_guid_equal(const EFI_GUID *a, const EFI_GUID *b) {
return memcmp(a, b, sizeof(EFI_GUID)) == 0;
}
#include <efi.h>
#include <efilib.h>
#include <stdbool.h>
+#if defined(__i386__) || defined(__x86_64__)
+# include <cpuid.h>
+#endif
#include "drivers.h"
#include "efi-string.h"
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);
+ _cleanup_free_ char16_t *order_str = xasprintf("VMMBootOrder%04zx", 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++) {
}
assert_not_reached();
}
+
+static bool cpuid_in_hypervisor(void) {
+#if defined(__i386__) || defined(__x86_64__)
+ unsigned eax, ebx, ecx, edx;
+
+ /* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
+ * environment. */
+
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
+ return false;
+
+ if (FLAGS_SET(ecx, 0x80000000U))
+ return true;
+#endif
+
+ return false;
+}
+
+typedef struct {
+ uint8_t anchor_string[4];
+ uint8_t entry_point_structure_checksum;
+ uint8_t entry_point_length;
+ uint8_t major_version;
+ uint8_t minor_version;
+ uint16_t max_structure_size;
+ uint8_t entry_point_revision;
+ uint8_t formatted_area[5];
+ uint8_t intermediate_anchor_string[5];
+ uint8_t intermediate_checksum;
+ uint16_t table_length;
+ uint32_t table_address;
+ uint16_t number_of_smbios_structures;
+ uint8_t smbios_bcd_revision;
+} _packed_ SmbiosEntryPoint;
+
+typedef struct {
+ uint8_t anchor_string[5];
+ uint8_t entry_point_structure_checksum;
+ uint8_t entry_point_length;
+ uint8_t major_version;
+ uint8_t minor_version;
+ uint8_t docrev;
+ uint8_t entry_point_revision;
+ uint8_t reserved;
+ uint32_t table_maximum_size;
+ uint64_t table_address;
+} _packed_ Smbios3EntryPoint;
+
+typedef struct {
+ uint8_t type;
+ uint8_t length;
+ uint8_t handle[2];
+} _packed_ SmbiosHeader;
+
+typedef struct {
+ SmbiosHeader header;
+ uint8_t vendor;
+ uint8_t bios_version;
+ uint16_t bios_segment;
+ uint8_t bios_release_date;
+ uint8_t bios_size;
+ uint64_t bios_characteristics;
+ uint8_t bios_characteristics_ext[2];
+} _packed_ SmbiosTableType0;
+
+static void *find_smbios_configuration_table(uint64_t *ret_size) {
+ assert(ret_size);
+
+ Smbios3EntryPoint *entry3 = find_configuration_table(&(EFI_GUID) SMBIOS3_TABLE_GUID);
+ if (entry3 && memcmp(entry3->anchor_string, "_SM3_", 5) == 0 &&
+ entry3->entry_point_length <= sizeof(*entry3)) {
+ *ret_size = entry3->table_maximum_size;
+ return PHYSICAL_ADDRESS_TO_POINTER(entry3->table_address);
+ }
+
+ SmbiosEntryPoint *entry = find_configuration_table(&(EFI_GUID) SMBIOS_TABLE_GUID);
+ if (entry && memcmp(entry->anchor_string, "_SM_", 4) == 0 &&
+ entry->entry_point_length <= sizeof(*entry)) {
+ *ret_size = entry->table_length;
+ return PHYSICAL_ADDRESS_TO_POINTER(entry->table_address);
+ }
+
+ return NULL;
+}
+
+static SmbiosHeader *get_smbios_table(uint8_t type) {
+ uint64_t size = 0;
+ uint8_t *p = find_smbios_configuration_table(&size);
+ if (!p)
+ return false;
+
+ for (;;) {
+ if (size < sizeof(SmbiosHeader))
+ return NULL;
+
+ SmbiosHeader *header = (SmbiosHeader *) p;
+
+ /* End of table. */
+ if (header->type == 127)
+ return NULL;
+
+ if (size < header->length)
+ return NULL;
+
+ if (header->type == type)
+ return header; /* Yay! */
+
+ /* Skip over formatted area. */
+ size -= header->length;
+ p += header->length;
+
+ /* Skip over string table. */
+ for (;;) {
+ while (size > 0 && *p != '\0') {
+ p++;
+ size--;
+ }
+ if (size == 0)
+ return NULL;
+ p++;
+ size--;
+
+ /* Double NUL terminates string table. */
+ if (*p == '\0') {
+ if (size == 0)
+ return NULL;
+ p++;
+ break;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static bool smbios_in_hypervisor(void) {
+ /* Look up BIOS Information (Type 0). */
+ SmbiosTableType0 *type0 = (SmbiosTableType0 *) get_smbios_table(0);
+ if (!type0 || type0->header.length < sizeof(SmbiosTableType0))
+ return false;
+
+ /* Bit 4 of 2nd BIOS characteristics extension bytes indicates virtualization. */
+ return FLAGS_SET(type0->bios_characteristics_ext[1], 1 << 4);
+}
+
+bool in_hypervisor(void) {
+ static int cache = -1;
+ if (cache >= 0)
+ return cache;
+
+ cache = cpuid_in_hypervisor() || smbios_in_hypervisor();
+ return cache;
+}
bool is_direct_boot(EFI_HANDLE device);
EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir);
+
+bool in_hypervisor(void);
#include <getopt.h>
+#include <sd-device.h>
#include <sd-messages.h>
+#include "blkid-util.h"
+#include "blockdev-util.h"
#include "build.h"
+#include "chase-symlinks.h"
+#include "efi-loader.h"
#include "efivars.h"
-#include "env-util.h"
+#include "escape.h"
+#include "fd-util.h"
#include "main-func.h"
+#include "mountpoint-util.h"
#include "openssl-util.h"
-#include "parse-util.h"
+#include "parse-argument.h"
#include "pretty-print.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
static bool arg_graceful = false;
static char *arg_tpm2_device = NULL;
static char **arg_banks = NULL;
+static char *arg_file_system = NULL;
+static bool arg_machine_id = false;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
if (r < 0)
return log_oom();
- printf("%1$s [OPTIONS...] WORD ...\n"
+ printf("%1$s [OPTIONS...] WORD\n"
+ "%1$s [OPTIONS...] --file-system=PATH\n"
+ "%1$s [OPTIONS...] --machine-id\n"
"\n%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
" --tpm2-device=PATH Use specified TPM2 device\n"
" --graceful Exit gracefully if no TPM2 device is found\n"
+ " --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
+ " --machine-id Measure machine ID into PCR 15\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ARG_BANK,
ARG_TPM2_DEVICE,
ARG_GRACEFUL,
+ ARG_FILE_SYSTEM,
+ ARG_MACHINE_ID,
};
static const struct option options[] = {
{ "bank", required_argument, NULL, ARG_BANK },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
+ { "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
+ { "machine-id", no_argument, NULL, ARG_MACHINE_ID },
{}
};
- int c;
+ int c, r;
assert(argc >= 0);
assert(argv);
arg_graceful = true;
break;
+ case ARG_FILE_SYSTEM:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_MACHINE_ID:
+ arg_machine_id = true;
+ break;
+
case '?':
return -EINVAL;
assert_not_reached();
}
+ if (arg_file_system && arg_machine_id)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined.");
+
return 1;
}
-static int determine_banks(struct tpm2_context *c) {
- _cleanup_free_ TPMI_ALG_HASH *algs = NULL;
- int n_algs, r;
+static int determine_banks(struct tpm2_context *c, unsigned target_pcr_nr) {
+ _cleanup_strv_free_ char **l = NULL;
+ int r;
assert(c);
if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
return 0;
- n_algs = tpm2_get_good_pcr_banks(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &algs);
- if (n_algs <= 0)
- return n_algs;
+ r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << target_pcr_nr, &l);
+ if (r < 0)
+ return r;
+
+ strv_free_and_replace(arg_banks, l);
+ return 0;
+}
+
+static int get_file_system_word(
+ sd_device *d,
+ const char *prefix,
+ char **ret) {
+
+ int r;
+
+ assert(d);
+ assert(prefix);
+ assert(ret);
+
+ _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (block_fd < 0)
+ return block_fd;
+
+ _cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe();
+ if (!b)
+ return -ENOMEM;
- for (int i = 0; i < n_algs; i++) {
- const EVP_MD *implementation;
- const char *salg;
+ errno = 0;
+ r = blkid_probe_set_device(b, block_fd, 0, 0);
+ if (r != 0)
+ return errno_or_else(ENOMEM);
- salg = tpm2_pcr_bank_to_string(algs[i]);
- if (!salg)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure.");
+ (void) blkid_probe_enable_superblocks(b, 1);
+ (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_UUID|BLKID_SUBLKS_LABEL);
+ (void) blkid_probe_enable_partitions(b, 1);
+ (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
- implementation = EVP_get_digestbyname(salg);
- if (!implementation)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure.");
+ errno = 0;
+ r = blkid_do_safeprobe(b);
+ if (r == _BLKID_SAFEPROBE_ERROR)
+ return errno_or_else(EIO);
+ if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND))
+ return -ENOPKG;
- r = strv_extend(&arg_banks, EVP_MD_name(implementation));
+ assert(r == _BLKID_SAFEPROBE_FOUND);
+
+ _cleanup_strv_free_ char **l = strv_new(prefix);
+ if (!l)
+ return log_oom();
+
+ FOREACH_STRING(field, "TYPE", "UUID", "LABEL", "PART_ENTRY_UUID", "PART_ENTRY_TYPE", "PART_ENTRY_NAME") {
+ const char *v = NULL;
+
+ (void) blkid_probe_lookup_value(b, field, &v, NULL);
+
+ _cleanup_free_ char *escaped = xescape(strempty(v), ":"); /* Avoid ambiguity around ":" */
+ if (!escaped)
+ return log_oom();
+
+ r = strv_consume(&l, TAKE_PTR(escaped));
if (r < 0)
return log_oom();
+
}
+ assert(strv_length(l) == 7); /* We always want 7 components, to avoid ambiguous strings */
+
+ _cleanup_free_ char *word = strv_join(l, ":");
+ if (!word)
+ return log_oom();
+
+ *ret = TAKE_PTR(word);
return 0;
}
static int run(int argc, char *argv[]) {
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
- _cleanup_free_ char *joined = NULL, *pcr_string = NULL;
- const char *word;
- unsigned pcr_nr;
+ _cleanup_free_ char *joined = NULL, *word = NULL;
+ unsigned target_pcr_nr;
size_t length;
- TSS2_RC rc;
int r;
log_setup();
if (r <= 0)
return r;
- if (optind+1 != argc)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
+ if (arg_file_system) {
+ _cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+ _cleanup_close_ int dfd = -EBADF;
+
+ if (optind != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
+
+ dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
+ if (dfd < 0)
+ return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system);
- word = argv[optind];
+ r = fd_is_mount_point(dfd, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized);
+
+ normalized_escaped = xescape(normalized, ":"); /* Avoid ambiguity around ":" */
+ if (!normalized_escaped)
+ return log_oom();
- /* Refuse to measure an empty word. We want to be able to write the series of measured words
- * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
- * disallow an empty word to avoid ambiguities. */
- if (isempty(word))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
+ _cleanup_free_ char* prefix = strjoin("file-system:", normalized_escaped);
+ if (!prefix)
+ return log_oom();
+
+ r = block_device_new_from_fd(dfd, BLOCK_DEVICE_LOOKUP_BACKING, &d);
+ if (r < 0) {
+ log_notice_errno(r, "Unable to determine backing block device of '%s', measuring generic fallback file system identity string: %m", arg_file_system);
+
+ word = strjoin(prefix, "::::::");
+ if (!word)
+ return log_oom();
+ } else {
+ r = get_file_system_word(d, prefix, &word);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get file system identifier string for '%s': %m", arg_file_system);
+ }
+
+ target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */
+
+ } else if (arg_machine_id) {
+ sd_id128_t mid;
+
+ if (optind != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
+
+ r = sd_id128_get_machine(&mid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire machine ID: %m");
+
+ word = strjoin("machine-id:", SD_ID128_TO_STRING(mid));
+ if (!word)
+ return log_oom();
+
+ target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */
+
+ } else {
+ if (optind+1 != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
+
+ word = strdup(argv[optind]);
+ if (!word)
+ return log_oom();
+
+ /* Refuse to measure an empty word. We want to be able to write the series of measured words
+ * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
+ * disallow an empty word to avoid ambiguities. */
+ if (isempty(word))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
+
+ target_pcr_nr = TPM_PCR_INDEX_KERNEL_IMAGE; /* → PCR 11 */
+ }
if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
log_notice("No complete TPM2 support detected, exiting gracefully.");
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) {
- 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");
- 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 = efi_stub_measured();
+ if (r < 0)
+ return log_error_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled: %m");
+ if (r == 0) {
+ log_info("Kernel stub did not measure kernel image into PCR %u, skipping userspace measurement, too.", TPM_PCR_INDEX_KERNEL_IMAGE);
+ return EXIT_SUCCESS;
}
r = dlopen_tpm2();
if (r < 0)
return r;
- r = determine_banks(&c);
+ r = determine_banks(&c, target_pcr_nr);
if (r < 0)
return r;
if (strv_isempty(arg_banks)) /* Still none? */
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
- TPML_DIGEST_VALUES values = {};
- STRV_FOREACH(bank, arg_banks) {
- const EVP_MD *implementation;
- int id;
-
- assert_se(implementation = EVP_get_digestbyname(*bank));
-
- if (values.count >= ELEMENTSOF(values.digests))
- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected.");
-
- if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest))
- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2.");
-
- id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation));
- if (id < 0)
- return log_error_errno(id, "Can't map hash name to TPM2.");
-
- values.digests[values.count].hashAlg = id;
-
- if (EVP_Digest(word, length, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word.");
-
- values.count++;
- }
-
joined = strv_join(arg_banks, ", ");
if (!joined)
return log_oom();
- log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined);
-
- rc = sym_Esys_PCR_Extend(
- c.esys_context,
- ESYS_TR_PCR0 + TPM_PCR_INDEX_KERNEL_IMAGE, /* → PCR 11 */
- ESYS_TR_PASSWORD,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- &values);
- if (rc != TSS2_RC_SUCCESS)
- return log_error_errno(
- SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to measure '%s': %s",
- word,
- sym_Tss2_RC_Decode(rc));
+ log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined);
+
+ r = tpm2_extend_bytes(c.esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0);
+ if (r < 0)
+ return r;
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
- LOG_MESSAGE("Successfully extended PCR index %u with '%s' (banks %s).", TPM_PCR_INDEX_KERNEL_IMAGE, word, joined),
+ LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", target_pcr_nr, word, joined),
"MEASURING=%s", word,
- "PCR=%u", TPM_PCR_INDEX_KERNEL_IMAGE,
+ "PCR=%u", target_pcr_nr,
"BANKS=%s", joined);
return EXIT_SUCCESS;
return bus_log_parse_error(r);
for (;;) {
- Member *z;
- _cleanup_free_ char *buf = NULL;
_cleanup_fclose_ FILE *mf = NULL;
+ _cleanup_free_ char *buf = NULL;
+ const char *name, *contents;
size_t sz = 0;
- const char *name;
+ Member *z;
+ char type;
r = sd_bus_message_enter_container(reply, 'e', "sv");
if (r < 0)
return bus_log_parse_error(r);
-
if (r == 0)
break;
if (r < 0)
return bus_log_parse_error(r);
- r = sd_bus_message_enter_container(reply, 'v', NULL);
+ r = sd_bus_message_peek_type(reply, &type, &contents);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (type != 'v')
+ return bus_log_parse_error(EINVAL);
+
+ r = sd_bus_message_enter_container(reply, 'v', contents);
if (r < 0)
return bus_log_parse_error(r);
z = set_get(members, &((Member) {
.type = "property",
.interface = m->interface,
+ .signature = (char*) contents,
.name = (char*) name }));
if (z)
free_and_replace(z->value, buf);
bpf_clang_flags = [
'-std=gnu11',
'-Wno-compare-distinct-pointer-types',
+ '-fno-stack-protector',
'-O2',
'-target',
'bpf',
bpf_gcc_flags = [
'-std=gnu11',
+ '-fno-stack-protector',
'-O2',
'-mkernel=5.2',
'-mcpu=v3',
if (unit_has_unified_memory_config(u)) {
val = c->memory_max;
- log_cgroup_compat(u, "Applying MemoryMax=%" PRIu64 " as MemoryLimit=", val);
+ if (val != CGROUP_LIMIT_MAX)
+ log_cgroup_compat(u, "Applying MemoryMax=%" PRIu64 " as MemoryLimit=", val);
} else
val = c->memory_limit;
return reply_unit_path(u, message, error);
}
+static int method_get_unit_by_pidfd(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ Manager *m = ASSERT_PTR(userdata);
+ _cleanup_free_ char *path = NULL;
+ int r, pidfd;
+ pid_t pid;
+ Unit *u;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "h", &pidfd);
+ if (r < 0)
+ return r;
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to get PID from PIDFD: %m");
+
+ u = manager_get_unit_by_pid(m, pid);
+ if (!u)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_PID, "PID "PID_FMT" does not belong to any loaded unit.", pid);
+
+ r = mac_selinux_unit_access_check(u, message, "status", error);
+ if (r < 0)
+ return r;
+
+ path = unit_dbus_path(u);
+ if (!path)
+ return log_oom();
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "os", path, u->id);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_array(reply, 'y', u->invocation_id.bytes, sizeof(u->invocation_id.bytes));
+ if (r < 0)
+ return r;
+
+ /* Double-check that the process is still alive and that the PID did not change before returning the
+ * answer. */
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r == -ESRCH)
+ return sd_bus_error_setf(error,
+ BUS_ERROR_NO_SUCH_PROCESS,
+ "The PIDFD's PID "PID_FMT" changed during the lookup operation.",
+ pid);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to get PID from PIDFD: %m");
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
static int method_load_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
const char *name;
SD_BUS_RESULT("o", unit),
method_get_unit_by_control_group,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetUnitByPIDFD",
+ SD_BUS_ARGS("h", pidfd),
+ SD_BUS_RESULT("o", unit, "s", unit_id, "ay", invocation_id),
+ method_get_unit_by_pidfd,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("LoadUnit",
SD_BUS_ARGS("s", name),
SD_BUS_RESULT("o", unit),
/* If the user namespace was not set up above, try to do it now.
* It's preferred to set up the user namespace later (after all other namespaces) so as not to be
- * restricted by rules pertaining to combining user namspaces with other namespaces (e.g. in the
+ * restricted by rules pertaining to combining user namespaces with other namespaces (e.g. in the
* case of mount namespaces being less privileged when the mount point list is copied from a
* different user namespace). */
return 0;
}
+int exec_context_destroy_mount_ns_dir(Unit *u) {
+ _cleanup_free_ char *p = NULL;
+
+ if (!u || !MANAGER_IS_SYSTEM(u->manager))
+ return 0;
+
+ p = path_join("/run/systemd/propagate/", u->id);
+ if (!p)
+ return -ENOMEM;
+
+ /* This is only filled transiently (see mount_in_namespace()), should be empty or even non-existent*/
+ if (rmdir(p) < 0 && errno != ENOENT)
+ log_unit_debug_errno(u, errno, "Unable to remove propagation dir '%s', ignoring: %m", p);
+
+ return 0;
+}
+
static void exec_command_done(ExecCommand *c) {
assert(c);
if (*l) {
/* It's kind of important, that we keep the order here */
- LIST_FIND_TAIL(command, *l, end);
+ end = LIST_FIND_TAIL(command, *l);
LIST_INSERT_AFTER(command, *l, end, e);
} else
*l = e;
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_root);
int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_root, const char *unit);
+int exec_context_destroy_mount_ns_dir(Unit *u);
const char* exec_context_fdname(const ExecContext *c, int fd_index);
p->n_auxiliary_fds = 0;
p->socket = s;
- LIST_FIND_TAIL(port, s->ports, tail);
+ tail = LIST_FIND_TAIL(port, s->ports);
LIST_INSERT_AFTER(port, s->ports, tail, p);
p = NULL;
#include "device.h"
#include "exit-status.h"
#include "format-util.h"
+#include "fs-util.h"
#include "fstab-util.h"
#include "initrd-util.h"
#include "libmount-util.h"
#include "process-util.h"
#include "serialize.h"
#include "special.h"
+#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
static void mount_enter_mounting(Mount *m) {
int r;
MountParameters *p;
+ bool source_is_dir = true;
assert(m);
if (r < 0)
goto fail;
- (void) mkdir_p_label(m->where, m->directory_mode);
+ p = get_mount_parameters_fragment(m);
+ if (p && mount_is_bind(p)) {
+ r = is_dir(p->what, /* follow = */ true);
+ if (r < 0 && r != -ENOENT)
+ log_unit_info_errno(UNIT(m), r, "Failed to determine type of bind mount source '%s', ignoring: %m", p->what);
+ else if (r == 0)
+ source_is_dir = false;
+ }
- unit_warn_if_dir_nonempty(UNIT(m), m->where);
+ if (source_is_dir)
+ (void) mkdir_p_label(m->where, m->directory_mode);
+ else
+ (void) touch_file(m->where, /* parents = */ true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
+
+ if (source_is_dir)
+ unit_warn_if_dir_nonempty(UNIT(m), m->where);
unit_warn_leftover_processes(UNIT(m), unit_log_leftover_process_start);
m->control_command_id = MOUNT_EXEC_MOUNT;
m->control_command = m->exec_command + MOUNT_EXEC_MOUNT;
/* Create the source directory for bind-mounts if needed */
- p = get_mount_parameters_fragment(m);
if (p && mount_is_bind(p)) {
r = mkdir_p_label(p->what, m->directory_mode);
/* mkdir_p_label() can return -EEXIST if the target path exists and is not a directory - which is
r = loop_device_make_by_path(
root_image,
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
+ /* sector_size= */ UINT32_MAX,
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
LOCK_SH,
&loop_device);
send_interface="org.freedesktop.systemd1.Manager"
send_member="GetUnitByControlGroup"/>
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="GetUnitByPIDFD"/>
+
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="LoadUnit"/>
s->reload_result = f;
- /* If the last notification we received from the service process indiciates
+ /* If the last notification we received from the service process indicates
* we are still reloading, then don't leave reloading state just yet, just
* transition into SERVICE_RELOAD_NOTIFY, to wait for the READY=1 coming,
* too. */
}
if (is_new && !ignore_requirements && type != JOB_NOP) {
- Set *following;
+ _cleanup_set_free_ Set *following = NULL;
/* If we are following some other unit, make sure we
* add all dependencies of everybody following. */
- if (unit_following_set(ret->unit, &following) > 0) {
+ if (unit_following_set(ret->unit, &following) > 0)
SET_FOREACH(dep, following) {
r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, false, false, ignore_order, e);
if (r < 0) {
}
}
- set_free(following);
- }
-
/* Finally, recursively add in all dependencies. */
if (IN_SET(type, JOB_START, JOB_RESTART)) {
UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START) {
r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e);
if (r < 0) {
if (r != -EBADR) /* job type not applicable */
- goto fail;
+ return r;
sd_bus_error_free(e);
}
r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, false, false, ignore_order, e);
if (r < 0) {
if (r != -EBADR) /* job type not applicable */
- goto fail;
+ return r;
sd_bus_error_free(e);
}
r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, true, false, ignore_order, e);
if (r < 0) {
if (r != -EBADR) /* job type not applicable */
- goto fail;
+ return r;
sd_bus_error_free(e);
}
}
if (IN_SET(type, JOB_STOP, JOB_RESTART)) {
- UnitDependencyAtom atom;
- JobType ptype;
-
+ _cleanup_set_free_ Set *propagated_restart = NULL;
/* We propagate STOP as STOP, but RESTART only as TRY_RESTART, in order not to start
* dependencies that are not around. */
- if (type == JOB_RESTART) {
- atom = UNIT_ATOM_PROPAGATE_RESTART;
- ptype = JOB_TRY_RESTART;
- } else {
- ptype = JOB_STOP;
- atom = UNIT_ATOM_PROPAGATE_STOP;
- }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, atom) {
- JobType nt;
+ if (type == JOB_RESTART)
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_RESTART) {
+ JobType nt;
+
+ r = set_ensure_put(&propagated_restart, NULL, dep);
+ if (r < 0)
+ return r;
+
+ nt = job_type_collapse(JOB_TRY_RESTART, dep);
+ if (nt == JOB_NOP)
+ continue;
- nt = job_type_collapse(ptype, dep);
- if (nt == JOB_NOP)
+ r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ return r;
+
+ sd_bus_error_free(e);
+ }
+ }
+
+ /* The 'stop' part of a restart job is also propagated to
+ * units with UNIT_ATOM_PROPAGATE_STOP */
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_STOP) {
+ /* Units experienced restart propagation are skipped */
+ if (set_contains(propagated_restart, dep))
continue;
- r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e);
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, false, false, ignore_order, e);
if (r < 0) {
if (r != -EBADR) /* job type not applicable */
- goto fail;
+ return r;
sd_bus_error_free(e);
}
}
return 0;
-
-fail:
- return r;
}
int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id);
+ exec_context_destroy_mount_ns_dir(u);
}
int unit_clean(Unit *u, ExecCleanMask mask) {
/* tmpfs might get full quickly, so check the available space too.
* But don't worry about errors here, failing to access the storage
* location will be better logged when writing to it. */
- if (statvfs("/var/lib/systemd/coredump/", &sv) >= 0)
+ if (fstatvfs(fd, &sv) >= 0)
max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size);
log_debug("Limiting core file size to %" PRIu64 " bytes due to cgroup memory limits.", max_size);
[Coredump]
#Storage=external
#Compress=yes
-#ProcessSizeMax=2G
-#ExternalSizeMax=2G
+# On 32-bit, the default is 1G instead of 32G.
+#ProcessSizeMax=32G
+#ExternalSizeMax=32G
#JournalSizeMax=767M
#MaxUse=
#KeepFree=
if (arg_transcode == TRANSCODE_OFF &&
arg_json_format_flags != JSON_FORMAT_OFF) {
-
_cleanup_(erase_and_freep) char *suffixed = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- if (memchr(data, 0, size))
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential data contains embedded NUL, can't parse as JSON.");
-
- suffixed = memdup_suffix0(data, size);
- if (!suffixed)
- return log_oom();
+ r = make_cstring(data, size, MAKE_CSTRING_REFUSE_TRAILING_NUL, &suffixed);
+ if (r < 0)
+ return log_error_errno(r, "Unable to convert binary string to C string: %m");
r = json_parse(suffixed, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
if (r < 0)
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(erase_and_freep) char *passphrase = NULL;
size_t decrypted_key_size;
+ ssize_t passphrase_size;
int r;
assert_se(cd);
/* Because cryptenroll requires a LUKS header, we can assume that this device is not
* a PLAIN device. In this case, we need to base64 encode the secret to use as the passphrase */
- r = base64mem(decrypted_key, decrypted_key_size, &passphrase);
- if (r < 0)
+ passphrase_size = base64mem(decrypted_key, decrypted_key_size, &passphrase);
+ if (passphrase_size < 0)
return log_oom();
r = crypt_volume_key_get(
ret_vk,
ret_vks,
passphrase,
- /* passphrase_size= */ r);
+ passphrase_size);
if (r < 0)
return log_error_errno(r, "Unlocking via FIDO2 device failed: %m");
_cleanup_free_ char *keyslot_as_string = NULL;
size_t cid_size, salt_size, secret_size;
_cleanup_free_ void *cid = NULL;
+ ssize_t base64_encoded_size;
const char *node, *un;
int r, keyslot;
return r;
/* Before we use the secret, we base64 encode it, for compat with homed, and to make it easier to type in manually */
- r = base64mem(secret, secret_size, &base64_encoded);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode secret key: %m");
+ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
volume_key,
volume_key_size,
base64_encoded,
- strlen(base64_encoded));
+ base64_encoded_size);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node);
size_t decrypted_key_size, encrypted_key_size;
_cleanup_free_ void *encrypted_key = NULL;
_cleanup_(X509_freep) X509 *cert = NULL;
+ ssize_t base64_encoded_size;
const char *node;
EVP_PKEY *pkey;
int keyslot, r;
/* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by
* keyboard, if that might ever end up being necessary.) */
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode secret key: %m");
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
volume_key,
volume_key_size,
base64_encoded,
- strlen(base64_encoded));
+ base64_encoded_size);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node);
#include "hexdecoct.h"
#include "json.h"
#include "memory-util.h"
+#include "random-util.h"
+#include "sha256.h"
#include "tpm2-util.h"
static int search_policy_hash(
uint16_t pcr_bank, primary_alg;
const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL;
+ ssize_t base64_encoded_size;
int r, keyslot;
TPM2Flags flags = 0;
+ uint8_t binary_salt[SHA256_DIGEST_SIZE] = {};
+ /*
+ * erase the salt, we'd rather attempt to not have this in a coredump
+ * as an attacker would have all the parameters but pin used to create
+ * the session key. This problem goes away when we move to a trusted
+ * primary key, aka the SRK.
+ */
+ CLEANUP_ERASE(binary_salt);
assert(cd);
assert(volume_key);
r = get_pin(&pin_str, &flags);
if (r < 0)
return r;
+
+ r = crypto_random_bytes(binary_salt, sizeof(binary_salt));
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire random salt: %m");
+
+ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
+ CLEANUP_ERASE(salted_pin);
+ r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), binary_salt, sizeof(binary_salt), salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to perform PBKDF2: %m");
+
+ pin_str = erase_and_free(pin_str);
+ /* re-stringify pin_str */
+ base64_encoded_size = base64mem(salted_pin, sizeof(salted_pin), &pin_str);
+ if (base64_encoded_size < 0)
+ return log_error_errno(base64_encoded_size, "Failed to base64 encode salted pin: %m");
}
r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size);
}
/* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
- r = base64mem(secret, secret_size, &base64_encoded);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode secret key: %m");
+ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
volume_key,
volume_key_size,
base64_encoded,
- strlen(base64_encoded));
+ base64_encoded_size);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
primary_alg,
blob, blob_size,
hash, hash_size,
+ use_pin ? binary_salt : NULL,
+ use_pin ? sizeof(binary_salt) : 0,
flags,
&v);
if (r < 0)
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
- size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
+ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0;
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
.search_pcr_mask = UINT32_MAX
};
uint16_t pcr_bank, primary_alg;
+ ssize_t base64_encoded_size;
TPM2Flags flags = 0;
const char *json;
int r;
&blob_size,
&policy_hash,
&policy_hash_size,
+ &salt,
+ &salt_size,
&flags);
if (r < 0)
return log_debug_open_error(cd, r);
blob_size,
policy_hash,
policy_hash_size,
+ salt,
+ salt_size,
flags,
&decrypted_key,
&decrypted_key_size);
return log_debug_open_error(cd, r);
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
- return log_debug_open_error(cd, r);
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_debug_open_error(cd, base64_encoded_size);
/* free'd automatically by libcryptsetup */
- *ret_password_len = strlen(base64_encoded);
*ret_password = TAKE_PTR(base64_encoded);
+ *ret_password_len = base64_encoded_size;
return 0;
}
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size;
+ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags = 0;
&blob_size,
&policy_hash,
&policy_hash_size,
+ &salt,
+ &salt_size,
&flags);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
+ crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt));
}
/*
}
int crypt_normalize_pin(const void *pin, size_t pin_size, char **ret_pin_string) {
-
- _cleanup_free_ char *pin_string = NULL;
-
- assert(pin || !pin_size);
+ assert(pin || pin_size == 0);
assert(ret_pin_string);
- if (!pin) {
+ if (pin_size == 0) {
*ret_pin_string = NULL;
return 0;
}
- /* Refuse embedded NULL bytes, but allow trailing NULL */
- if (memchr(pin, 0, pin_size - 1))
- return -EINVAL;
-
- /* Enforce trailing NULL byte if missing */
- pin_string = memdup_suffix0(pin, pin_size);
- if (!pin_string)
- return -ENOMEM;
-
- *ret_pin_string = TAKE_PTR(pin_string);
-
- return 0;
+ return make_cstring(pin, pin_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, ret_pin_string);
}
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_strv_free_erase_ char **pins = NULL;
+ ssize_t base64_encoded_size;
assert(ret_keyslot_passphrase);
assert(ret_keyslot_passphrase_size);
return r;
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
- return crypt_log_error_errno(cd, r, "Failed to base64 encode key: %m");
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return crypt_log_error_errno(cd, (int) base64_encoded_size, "Failed to base64 encode key: %m");
*ret_keyslot_passphrase = TAKE_PTR(base64_encoded);
- *ret_keyslot_passphrase_size = strlen(*ret_keyslot_passphrase);
+ *ret_keyslot_passphrase_size = base64_encoded_size;
return 0;
}
_cleanup_free_ char *pkcs11_uri = NULL;
_cleanup_free_ void *encrypted_key = NULL;
systemd_pkcs11_plugin_params *pkcs11_params = userdata;
+ ssize_t base64_encoded_size;
assert(json);
assert(ret_password);
if (r < 0)
return r;
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
- return crypt_log_error_errno(cd, r, "Can not base64 encode key: %m");
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return crypt_log_error_errno(cd, (int) base64_encoded_size, "Can not base64 encode key: %m");
*ret_password = TAKE_PTR(base64_encoded);
- *ret_password_size = strlen(*ret_password);
+ *ret_password_size = base64_encoded_size;
return 0;
}
#include "luks2-tpm2.h"
#include "parse-util.h"
#include "random-util.h"
+#include "sha256.h"
#include "strv.h"
#include "tpm2-util.h"
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
+ const void *salt,
+ size_t salt_size,
TPM2Flags flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ char *auto_device = NULL;
+ _cleanup_(erase_and_freep) char *b64_salted_pin = NULL;
int r;
assert(ret_decrypted_key);
if ((flags & TPM2_FLAGS_USE_PIN) && !pin)
return -ENOANO;
+ /* If we're using a PIN, and the luks header has a salt, it better have a pin too */
+ if ((flags & TPM2_FLAGS_USE_PIN) && salt && !pin)
+ return -ENOANO;
+
+ if (pin) {
+ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
+ CLEANUP_ERASE(salted_pin);
+ r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt, salt_size, salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to perform PBKDF2: %m");
+
+ r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to base64 encode salted pin: %m");
+ pin = b64_salted_pin;
+ }
+
if (pubkey_pcr_mask != 0) {
r = tpm2_load_pcr_signature(signature_path, &signature_json);
if (r < 0)
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
+ const void *salt,
+ size_t salt_size,
TPM2Flags flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
#include "json.h"
#include "parse-util.h"
#include "random-util.h"
+#include "sha256.h"
#include "tpm2-util.h"
static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_pin_str) {
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
+ const void *salt,
+ size_t salt_size,
TPM2Flags flags,
usec_t until,
bool headless,
ret_decrypted_key_size);
for (int i = 5;; i--) {
- _cleanup_(erase_and_freep) char *pin_str = NULL;
+ _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL;
if (i <= 0)
return -EACCES;
if (r < 0)
return r;
+ if (salt) {
+ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
+ CLEANUP_ERASE(salted_pin);
+
+ r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt, salt_size, salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to perform PBKDF2: %m");
+
+ r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to base64 encode salted pin: %m");
+ } else
+ /* no salting needed, backwards compat with non-salted pins */
+ b64_salted_pin = TAKE_PTR(pin_str);
+
r = tpm2_unseal(device,
hash_pcr_mask,
pcr_bank,
pubkey, pubkey_size,
pubkey_pcr_mask,
signature_json,
- pin_str,
+ b64_salted_pin,
primary_alg,
blob,
blob_size,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
+ void **ret_salt,
+ size_t *ret_salt_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
assert(cd);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL;
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size;
+ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags;
&primary_alg,
&blob, &blob_size,
&policy_hash, &policy_hash_size,
+ &salt, &salt_size,
&flags);
if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
continue;
*ret_blob_size = blob_size;
*ret_policy_hash = TAKE_PTR(policy_hash);
*ret_policy_hash_size = policy_hash_size;
+ *ret_salt = TAKE_PTR(salt);
+ *ret_salt_size = salt_size;
*ret_keyslot = keyslot;
*ret_token = token;
*ret_flags = flags;
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
+ const void *salt,
+ size_t salt_size,
TPM2Flags flags,
usec_t until,
bool headless,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
+ void **ret_salt,
+ size_t *ret_salt_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token);
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
+ const void *salt,
+ size_t salt_size,
TPM2Flags flags,
usec_t until,
bool headless,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
+ void **ret_salt,
+ size_t *ret_salt_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
#include <unistd.h>
#include "sd-device.h"
+#include "sd-messages.h"
#include "alloc-util.h"
#include "ask-password-api.h"
#include "cryptsetup-util.h"
#include "device-util.h"
#include "efi-api.h"
+#include "efi-loader.h"
#include "env-util.h"
#include "escape.h"
#include "fileio.h"
#include "random-util.h"
#include "string-table.h"
#include "strv.h"
+#include "tpm-pcr.h"
#include "tpm2-util.h"
/* internal helper */
static void *arg_fido2_cid = NULL;
static size_t arg_fido2_cid_size = 0;
static char *arg_fido2_rp_id = NULL;
-static char *arg_tpm2_device = NULL;
+static char *arg_tpm2_device = NULL; /* These and the following fields are about locking an encrypted volume to the local TPM */
static bool arg_tpm2_device_auto = false;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_tpm2_signature = NULL;
static bool arg_tpm2_pin = false;
static bool arg_headless = false;
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
+static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */
+static char **arg_tpm2_measure_banks = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep);
static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
[PASSPHRASE_REGULAR] = "passphrase",
arg_tpm2_pin = r;
+ } else if ((val = startswith(option, "tpm2-measure-pcr="))) {
+ unsigned pcr;
+
+ r = safe_atou(val, &pcr);
+ if (r < 0) {
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX;
+ } else if (pcr >= TPM2_PCRS_MAX) {
+ log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1);
+ return 0;
+ }
+
+ arg_tpm2_measure_pcr = pcr;
+
+ } else if ((val = startswith(option, "tpm2-measure-bank="))) {
+
+#if HAVE_OPENSSL
+ _cleanup_strv_free_ char **l = NULL;
+
+ l = strv_split(optarg, ":");
+ if (!l)
+ return log_oom();
+
+ STRV_FOREACH(i, l) {
+ const EVP_MD *implementation;
+
+ implementation = EVP_get_digestbyname(*i);
+ if (!implementation)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val);
+
+ if (strv_extend(&arg_tpm2_measure_banks, EVP_MD_name(implementation)) < 0)
+ return log_oom();
+ }
+#else
+ log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option);
+#endif
+
} else if ((val = startswith(option, "try-empty-password="))) {
r = parse_boolean(val);
return 0;
}
+static int measure_volume_key(
+ struct crypt_device *cd,
+ const char *name,
+ const void *volume_key,
+ size_t volume_key_size) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+ assert(volume_key);
+ assert(volume_key_size > 0);
+
+ if (arg_tpm2_measure_pcr == UINT_MAX) {
+ log_debug("Not measuring volume key, deactivated.");
+ return 0;
+ }
+
+ r = efi_stub_measured();
+ if (r < 0)
+ return log_warning_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled: %m");
+ if (r == 0) {
+ log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace measurement, too.");
+ return 0;
+ }
+
+#if HAVE_TPM2
+ r = dlopen_tpm2();
+ if (r < 0)
+ return log_error_errno(r, "Failed to load TPM2 libraries: %m");
+
+ _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
+ r = tpm2_context_init(arg_tpm2_device, &c);
+ if (r < 0)
+ return r;
+
+ _cleanup_strv_free_ char **l = NULL;
+ if (strv_isempty(arg_tpm2_measure_banks)) {
+ r = tpm2_get_good_pcr_banks_strv(c.esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l);
+ if (r < 0)
+ return r;
+ }
+
+ _cleanup_free_ char *joined = strv_join(l ?: arg_tpm2_measure_banks, ", ");
+ if (!joined)
+ return log_oom();
+
+ /* Note: we don't directly measure the volume key, it might be a security problem to send an
+ * unprotected direct hash of the secret volume key over the wire to the TPM. Hence let's instead
+ * send a HMAC signature instead. */
+
+ _cleanup_free_ char *escaped = NULL;
+ escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */
+ if (!escaped)
+ return log_oom();
+
+ _cleanup_free_ char *s = NULL;
+ s = strjoin("cryptsetup:", escaped, ":", strempty(crypt_get_uuid(cd)));
+ if (!s)
+ return log_oom();
+
+ r = tpm2_extend_bytes(c.esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size);
+ if (r < 0)
+ return r;
+
+ log_struct(LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
+ LOG_MESSAGE("Successfully extended PCR index %u with '%s' and volume key (banks %s).", arg_tpm2_measure_pcr, s, joined),
+ "MEASURING=%s", s,
+ "PCR=%u", arg_tpm2_measure_pcr,
+ "BANKS=%s", joined);
+
+ return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring.");
+#endif
+}
+
+static int measured_crypt_activate_by_volume_key(
+ struct crypt_device *cd,
+ const char *name,
+ const void *volume_key,
+ size_t volume_key_size,
+ uint32_t flags) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+
+ /* A wrapper around crypt_activate_by_volume_key() which also measures to a PCR if that's requested. */
+
+ r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags);
+ if (r < 0)
+ return r;
+
+ if (volume_key_size == 0) {
+ log_debug("Not measuring volume key, none specified.");
+ return r;
+ }
+
+ (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */
+ return r;
+}
+
+static int measured_crypt_activate_by_passphrase(
+ struct crypt_device *cd,
+ const char *name,
+ int keyslot,
+ const char *passphrase,
+ size_t passphrase_size,
+ uint32_t flags) {
+
+ _cleanup_(erase_and_freep) void *vk = NULL;
+ size_t vks;
+ int r;
+
+ assert(cd);
+
+ /* A wrapper around crypt_activate_by_passphrase() which also measures to a PCR if that's
+ * requested. Note that we need the volume key for the measurement, and
+ * crypt_activate_by_passphrase() doesn't give us access to this. Hence, we operate indirectly, and
+ * retrieve the volume key first, and then activate through that. */
+
+ if (arg_tpm2_measure_pcr == UINT_MAX) {
+ log_debug("Not measuring volume key, deactivated.");
+ goto shortcut;
+ }
+
+ r = crypt_get_volume_key_size(cd);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_debug("Not measuring volume key, none defined.");
+ goto shortcut;
+ }
+
+ vk = malloc(vks = r);
+ if (!vk)
+ return -ENOMEM;
+
+ r = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size);
+ if (r < 0)
+ return r;
+
+ return measured_crypt_activate_by_volume_key(cd, name, vk, vks, flags);
+
+shortcut:
+ return crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags);
+}
+
static int attach_tcrypt(
struct crypt_device *cd,
const char *name,
return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd));
}
- r = crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
if (r < 0)
return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd));
}
static bool libcryptsetup_plugins_support(void) {
+
+#if HAVE_TPM2
+ /* Currently, there's no way for us to query the volume key when plugins are used. Hence don't use
+ * plugins, if measurement has been requested. */
+ if (arg_tpm2_measure_pcr != UINT_MAX)
+ return false;
+#endif
+
#if HAVE_LIBCRYPTSETUP_PLUGINS
int r;
}
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
+ ssize_t base64_encoded_size;
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)");
assert(decrypted_key);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
+ ssize_t base64_encoded_size;
/* Before using this key as passphrase we base64 encode it. Why? For compatibility
* with homed's PKCS#11 hookup: there we want to use the key we acquired through
* without embedded NUL here too, and that's easiest to generate from a binary blob
* via base64 encoding. */
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)");
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data, key_data_size,
/* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
+ /* salt= */ NULL, /* salt_size= */ 0,
arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
until,
arg_headless,
* works. */
for (;;) {
- _cleanup_free_ void *pubkey = NULL;
- size_t pubkey_size = 0;
+ _cleanup_free_ void *pubkey = NULL, *salt = NULL;
+ size_t pubkey_size = 0, salt_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
&primary_alg,
&blob, &blob_size,
&policy_hash, &policy_hash_size,
+ &salt, &salt_size,
&tpm2_flags,
&keyslot,
&token);
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
blob, blob_size,
policy_hash, policy_hash_size,
+ salt, salt_size,
tpm2_flags,
until,
arg_headless,
assert(decrypted_key);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
+ ssize_t base64_encoded_size;
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)");
assert(key_data);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags);
if (r == -EPERM) {
log_error_errno(r, "Failed to activate. (Key incorrect?)");
return -EAGAIN; /* Log actual error, but return EAGAIN */
return log_error_errno(r, "Failed to read key file '%s': %m", key_file);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags);
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with key file '%s'. (Key data incorrect?)", key_file);
return -EAGAIN; /* Log actual error, but return EAGAIN */
r = -EINVAL;
STRV_FOREACH(p, passwords) {
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
if (r >= 0)
break;
}
else if (arg_json_format_flags & JSON_FORMAT_OFF)
printf(" Size: %s\n", FORMAT_BYTES(size));
+ printf(" Sec. Size: %" PRIu32 "\n", m->sector_size);
+
if (arg_json_format_flags & JSON_FORMAT_OFF)
putc('\n', stdout);
+ fflush(stdout);
+
r = dissected_image_acquire_metadata(m, 0);
if (r == -ENXIO)
return log_error_errno(r, "No root partition discovered.");
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("sectorSize", JSON_BUILD_INTEGER(m->sector_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)),
JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)),
loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
if (arg_in_memory)
- r = loop_device_make_by_path_memory(arg_image, open_flags, loop_flags, LOCK_SH, &d);
+ r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
else
- r = loop_device_make_by_path(arg_image, open_flags, loop_flags, LOCK_SH, &d);
+ r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d);
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image);
#include "bus-error.h"
#include "bus-locator.h"
#include "chase-symlinks.h"
+#include "efi-loader.h"
#include "fd-util.h"
#include "fileio.h"
#include "fstab-util.h"
MOUNT_MAKEFS = 1 << 3,
MOUNT_GROWFS = 1 << 4,
MOUNT_RW_ONLY = 1 << 5,
+ MOUNT_PCRFS = 1 << 6,
} MountPointFlags;
static bool arg_sysroot_check = false;
if (flags & MOUNT_GROWFS)
/* TODO: swap devices must be wiped and recreated */
log_warning("%s: growing swap devices is currently unsupported.", what);
+ if (flags & MOUNT_PCRFS)
+ log_warning("%s: measuring swap devices is currently unsupported.", what);
if (!(flags & MOUNT_NOAUTO)) {
r = generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET,
return r;
}
+ if (flags & MOUNT_PCRFS) {
+ r = efi_stub_measured();
+ if (r < 0)
+ log_warning_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled, assuming not: %m");
+ else if (r == 0)
+ log_debug("Kernel stub did not measure kernel image into PCR, skipping userspace measurement, too.");
+ else {
+ r = generator_hook_up_pcrfs(dest, where, target_unit);
+ if (r < 0)
+ return r;
+ }
+ }
+
if (!FLAGS_SET(flags, MOUNT_AUTOMOUNT)) {
if (!FLAGS_SET(flags, MOUNT_NOAUTO) && strv_isempty(wanted_by) && strv_isempty(required_by)) {
r = generator_add_symlink(dest, target_unit,
while ((me = getmntent(f))) {
_cleanup_free_ char *where = NULL, *what = NULL, *canonical_where = NULL;
- bool makefs, growfs, noauto, nofail;
+ bool makefs, growfs, pcrfs, noauto, nofail;
MountPointFlags flags;
int k;
makefs = fstab_test_option(me->mnt_opts, "x-systemd.makefs\0");
growfs = fstab_test_option(me->mnt_opts, "x-systemd.growfs\0");
+ pcrfs = fstab_test_option(me->mnt_opts, "x-systemd.pcrfs\0");
noauto = fstab_test_yes_no_option(me->mnt_opts, "noauto\0" "auto\0");
nofail = fstab_test_yes_no_option(me->mnt_opts, "nofail\0" "fail\0");
- log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s noauto=%s nofail=%s",
+ log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s pcrfs=%s noauto=%s nofail=%s",
what, where, me->mnt_type,
- yes_no(makefs), yes_no(growfs),
+ yes_no(makefs), yes_no(growfs), yes_no(pcrfs),
yes_no(noauto), yes_no(nofail));
flags = makefs * MOUNT_MAKEFS |
growfs * MOUNT_GROWFS |
+ pcrfs * MOUNT_PCRFS |
noauto * MOUNT_NOAUTO |
nofail * MOUNT_NOFAIL;
fstype,
opts,
is_device_path(what) ? 1 : 0, /* passno */
- flags, /* makefs, growfs off, noauto off, nofail off, automount off */
+ flags, /* makefs off, pcrfs off, noauto off, nofail off, automount off */
SPECIAL_INITRD_ROOT_FS_TARGET);
}
#define assert(expr)
#define assert_not_reached() __builtin_unreachable()
#else
- #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
- #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __PRETTY_FUNCTION__)
+ #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); })
+ #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __func__)
#endif
#define static_assert _Static_assert
- #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
+ #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); })
#endif
/* This passes the argument through after (if asserts are enabled) checking that it is not null. */
#endif
struct VarEraser {
+ /* NB: This is a pointer to memory to erase in case of CLEANUP_ERASE(). Pointer to pointer to memory
+ * to erase in case of CLEANUP_ERASE_PTR() */
void *p;
size_t size;
};
}
/* Mark var to be erased when leaving scope. */
-#define CLEANUP_ERASE(var) \
- _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { .p = &var, .size = sizeof(var) }
+#define CLEANUP_ERASE(var) \
+ _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+ .p = &(var), \
+ .size = sizeof(var), \
+ }
+
+static inline void erase_varp(struct VarEraser *e) {
+
+ /* Very similar to erase_var(), but assumes `p` is a pointer to a pointer whose memory shall be destructed. */
+ if (!e->p)
+ return;
+
+ explicit_bzero_safe(*(void**) e->p, e->size);
+}
+
+/* Mark pointer so that memory pointed to is erased when leaving scope. Note: this takes a pointer to the
+ * specified pointer, instead of just a copy of it. This is to allow callers to invalidate the pointer after
+ * use, if they like, disabling our automatic erasure (for example because they succeeded with whatever they
+ * wanted to do and now intend to return the allocated buffer to their caller without it being erased). */
+#define CLEANUP_ERASE_PTR(ptr, sz) \
+ _cleanup_(erase_varp) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+ .p = (ptr), \
+ .size = (sz), \
+ }
/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */
#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U
+/* This TPM PCR is where we measure the root fs volume key (and maybe /var/'s) if it is split off */
+#define TPM_PCR_INDEX_VOLUME_KEY 15U
+
/* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in
* which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */
typedef enum UnifiedSection {
const char *what,
bool rw,
bool require,
+ bool measure,
char **ret_device) {
#if HAVE_LIBCRYPTSETUP
- _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL;
+ _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL, *options = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
"After=%s\n",
d, d);
- r = generator_write_cryptsetup_service_section(f, id, what, NULL, rw ? NULL : "read-only");
+ if (!rw) {
+ options = strdup("read-only");
+ if (!options)
+ return log_oom();
+ }
+
+ if (measure) {
+ /* We only measure the root volume key into PCR 15 if we are booted with sd-stub (i.e. in a
+ * UKI), and sd-stub measured the UKI. We do this in order not to step into people's own PCR
+ * assignment, under the assumption that people who are fine to use sd-stub with its PCR
+ * assignments are also OK with our PCR 15 use here. */
+
+ r = efi_stub_measured();
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether booted via systemd-stub with measurements enabled, ignoring: %m");
+ else if (r == 0)
+ log_debug("Will not measure volume key of volume '%s', because not booted via systemd-stub with measurements enabled.", id);
+ else if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes"))
+ return log_oom();
+ }
+
+ r = generator_write_cryptsetup_service_section(f, id, what, NULL, options);
if (r < 0)
return r;
const char *fstype,
bool rw,
bool growfs,
+ bool measure,
const char *options,
const char *description,
const char *post) {
log_debug("Adding %s: %s fstype=%s", where, what, fstype ?: "(any)");
if (streq_ptr(fstype, "crypto_LUKS")) {
- r = add_cryptsetup(id, what, rw, true, &crypto_what);
+ r = add_cryptsetup(id, what, rw, /* require= */ true, measure, &crypto_what);
if (r < 0)
return r;
return r;
}
+ if (measure) {
+ r = generator_hook_up_pcrfs(arg_dest, where, post);
+ if (r < 0)
+ return r;
+ }
+
if (post) {
r = generator_add_symlink(arg_dest, post, "requires", unit);
if (r < 0)
p->fstype,
p->rw,
p->growfs,
+ /* measure= */ STR_IN_SET(id, "root", "var"), /* by default measure rootfs and /var, since they contain the "identity" of the system */
NULL,
description,
SPECIAL_LOCAL_FS_TARGET);
}
if (streq_ptr(p->fstype, "crypto_LUKS")) {
- r = add_cryptsetup("swap", p->node, true, true, &crypto_what);
+ r = add_cryptsetup("swap", p->node, /* rw= */ true, /* require= */ true, /* measure= */ false, &crypto_what);
if (r < 0)
return r;
what = crypto_what;
fstype,
rw,
growfs,
+ /* measure= */ false,
options,
description,
NULL);
static const char *esp_or_xbootldr_options(const DissectedPartition *p) {
assert(p);
- /* if we probed vfat or have no idea about the file system then assume these file systems are vfat
- * and thus understand "umask=0077". If we detected something else then don't specify any options and
- * use kernel defaults. */
+ /* Discovered ESP and XBOOTLDR partition are always hardened with "noexec,nosuid,nodev".
+ * If we probed vfat or have no idea about the file system then assume these file systems are vfat
+ * and thus understand "umask=0077". */
if (!p->fstype || streq(p->fstype, "vfat"))
- return "umask=0077";
+ return "umask=0077,noexec,nosuid,nodev";
- return NULL;
+ return "noexec,nosuid,nodev";
}
static int add_partition_xbootldr(DissectedPartition *p) {
/* If a device /dev/gpt-auto-root-luks appears, then make it pull in systemd-cryptsetup-root.service, which
* sets it up, and causes /dev/gpt-auto-root to appear which is all we are looking for. */
- return add_cryptsetup("root", "/dev/gpt-auto-root-luks", true, false, NULL);
+ return add_cryptsetup("root", "/dev/gpt-auto-root-luks", /* rw= */ true, /* require= */ false, /* measure= */ true, NULL);
#else
return 0;
#endif
arg_root_fstype,
/* rw= */ arg_root_rw > 0,
/* growfs= */ false,
+ /* measure= */ true,
arg_root_options,
"Root Partition",
in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *escaped = NULL;
+ ssize_t escaped_size;
int r;
assert(v);
assert(cid);
- r = base64mem(cid, cid_size, &escaped);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m");
+ escaped_size = base64mem(cid, cid_size, &escaped);
+ if (escaped_size < 0)
+ return log_error_errno(escaped_size, "Failed to base64 encode FIDO2 credential ID: %m");
w = json_variant_ref(json_variant_by_key(*v, "fido2HmacCredential"));
if (w) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL;
+ ssize_t base64_encoded_size;
int r;
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
- r = base64mem(secret, secret_size, &base64_encoded);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode secret key: %m");
+ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
r = hash_password(base64_encoded, &hashed);
if (r < 0)
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL;
+ ssize_t base64_encoded_size;
int r;
assert(v);
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode secret key: %m");
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
r = hash_password(base64_encoded, &hashed);
if (r < 0)
return 0;
}
-static int parse_sector_size(const char *t, uint64_t *ret) {
- int r;
-
- assert(t);
- assert(ret);
-
- uint64_t ss;
-
- r = safe_atou64(t, &ss);
- if (r < 0)
- return log_error_errno(r, "Failed to parse sector size parameter %s", t);
- if (ss < 512 || ss > 4096) /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */
- return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Sector size not between 512 and 4096: %s", t);
- if (!ISPOWEROF2(ss))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size not power of 2: %s", t);
-
- *ret = ss;
- return 0;
-}
-
static int resize_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
_cleanup_(erase_and_freep) void *hmac = NULL;
size_t hmac_size;
Fido2EnrollFlags flags = 0;
+ ssize_t ss;
int r;
assert(h);
if (r < 0)
return r;
- r = base64mem(hmac, hmac_size, ret);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode HMAC secret: %m");
+ ss = base64mem(hmac, hmac_size, ret);
+ if (ss < 0)
+ return log_error_errno(ss, "Failed to base64 encode HMAC secret: %m");
return 0;
}
};
memcpy(key.raw, volume_key, volume_key_size);
+ CLEANUP_ERASE(key);
+
/* Upload to the kernel */
serial = add_key("logon", description, &key, sizeof(key), where);
- explicit_bzero_safe(&key, sizeof(key));
-
if (serial < 0)
return log_error_errno(errno, "Failed to install master key in keyring: %m");
* resulting hash.
*/
+ CLEANUP_ERASE(derived);
+
if (PKCS5_PBKDF2_HMAC(
password, strlen(password),
salt, salt_size,
0xFFFF, EVP_sha512(),
- sizeof(derived), derived) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
- goto finish;
- }
+ sizeof(derived), derived) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
context = EVP_CIPHER_CTX_new();
- if (!context) {
- r = log_oom();
- goto finish;
- }
+ if (!context)
+ return log_oom();
/* We use AES256 in counter mode */
assert_se(cc = EVP_aes_256_ctr());
/* We only use the first half of the derived key */
assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
- if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context.");
- goto finish;
- }
-
- /* Flush out the derived key now, we don't need it anymore */
- explicit_bzero_safe(derived, sizeof(derived));
+ if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context.");
decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2;
decrypted = malloc(decrypted_size);
*ret_decrypted_size = decrypted_size;
return 0;
-
-finish:
- explicit_bzero_safe(derived, sizeof(derived));
- return r;
}
static int fscrypt_slot_try_many(
_cleanup_free_ void *encrypted = NULL;
const EVP_CIPHER *cc;
size_t encrypted_size;
+ ssize_t ss;
r = crypto_random_bytes(salt, sizeof(salt));
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
+ CLEANUP_ERASE(derived);
+
if (PKCS5_PBKDF2_HMAC(
password, strlen(password),
salt, sizeof(salt),
0xFFFF, EVP_sha512(),
- sizeof(derived), derived) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
- goto finish;
- }
+ sizeof(derived), derived) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
context = EVP_CIPHER_CTX_new();
- if (!context) {
- r = log_oom();
- goto finish;
- }
+ if (!context)
+ return log_oom();
/* We use AES256 in counter mode */
cc = EVP_aes_256_ctr();
/* We only use the first half of the derived key */
assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
- if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context.");
- goto finish;
- }
-
- /* Flush out the derived key now, we don't need it anymore */
- explicit_bzero_safe(derived, sizeof(derived));
+ if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context.");
encrypted_size = volume_key_size + EVP_CIPHER_key_length(cc) * 2;
encrypted = malloc(encrypted_size);
assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 < encrypted_size);
encrypted_size = (size_t) encrypted_size_out1 + (size_t) encrypted_size_out2;
- r = base64mem(salt, sizeof(salt), &salt_base64);
- if (r < 0)
+ ss = base64mem(salt, sizeof(salt), &salt_base64);
+ if (ss < 0)
return log_oom();
- r = base64mem(encrypted, encrypted_size, &encrypted_base64);
- if (r < 0)
+ ss = base64mem(encrypted, encrypted_size, &encrypted_base64);
+ if (ss < 0)
return log_oom();
joined = strjoin(salt_base64, ":", encrypted_base64);
log_info("Written key slot %s.", label);
return 0;
-
-finish:
- explicit_bzero_safe(derived, sizeof(derived));
- return r;
}
int home_create_fscrypt(
return r;
}
- r = loop_device_make(setup->image_fd, O_RDWR, offset, size, user_record_luks_sector_size(h), 0, LOCK_UN, &setup->loop);
+ r = loop_device_make(
+ setup->image_fd,
+ O_RDWR,
+ offset,
+ size,
+ h->luks_sector_size == UINT64_MAX ? UINT32_MAX : user_record_luks_sector_size(h), /* if sector size is not specified, select UINT32_MAX, i.e. auto-probe */
+ /* loop_flags= */ 0,
+ LOCK_UN,
+ &setup->loop);
if (r == -ENOENT) {
log_error_errno(r, "Loopback block device support is not available on this system.");
return -ENOLINK; /* make recognizable */
static int make_partition_table(
int fd,
+ uint32_t sector_size,
const char *label,
sd_id128_t uuid,
uint64_t *ret_offset,
if (r < 0)
return log_error_errno(r, "Failed to initialize partition type: %m");
- r = fdisk_new_context_fd(fd, /* read_only= */ false, &c);
+ r = fdisk_new_context_fd(fd, /* read_only= */ false, sector_size, &c);
if (r < 0)
return log_error_errno(r, "Failed to open device: %m");
r = make_partition_table(
setup->image_fd,
+ user_record_luks_sector_size(h),
user_record_user_name_and_realm(h),
partition_uuid,
&partition_offset,
log_info("Writing of partition table completed.");
- r = loop_device_make(setup->image_fd, O_RDWR, partition_offset, partition_size, user_record_luks_sector_size(h), 0, LOCK_EX, &setup->loop);
+ r = loop_device_make(
+ setup->image_fd,
+ O_RDWR,
+ partition_offset,
+ partition_size,
+ user_record_luks_sector_size(h),
+ 0,
+ LOCK_EX,
+ &setup->loop);
if (r < 0) {
if (r == -ENOENT) { /* this means /dev/loop-control doesn't exist, i.e. we are in a container
* or similar and loopback bock devices are not available, return a
r = mkfs_options_for_fstype(fstype, &extra_mkfs_options);
if (r < 0)
return log_error_errno(r, "Failed to determine mkfs command line options for '%s': %m", fstype);
- r = make_filesystem(setup->dm_node, fstype, user_record_user_name_and_realm(h), NULL, fs_uuid, user_record_luks_discard(h), extra_mkfs_options);
+ r = make_filesystem(setup->dm_node, fstype, user_record_user_name_and_realm(h), NULL, fs_uuid, user_record_luks_discard(h), 0, extra_mkfs_options);
if (r < 0)
return r;
return 0;
}
- r = fdisk_new_context_fd(fd, /* read_only= */ false, &c);
+ r = fdisk_new_context_fd(fd, /* read_only= */ false, UINT32_MAX, &c);
if (r < 0)
return log_error_errno(r, "Failed to open device: %m");
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_free_ void *two_zero_lbas = NULL;
+ uint32_t ssz;
ssize_t n;
int r;
if (r < 0)
return log_error_errno(r, "Failed to change partition size: %m");
- two_zero_lbas = malloc0(1024U);
+ r = probe_sector_size(fd, &ssz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine current sector size: %m");
+
+ two_zero_lbas = malloc0(ssz * 2);
if (!two_zero_lbas)
return log_oom();
/* libfdisk appears to get confused by the existing PMBR. Let's explicitly flush it out. */
- n = pwrite(fd, two_zero_lbas, 1024U, 0);
+ n = pwrite(fd, two_zero_lbas, ssz * 2, 0);
if (n < 0)
return log_error_errno(errno, "Failed to wipe partition table: %m");
- if (n != 1024)
+ if ((size_t) n != ssz * 2)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while wiping partition table.");
- r = fdisk_new_context_fd(fd, /* read_only= */ false, &c);
+ r = fdisk_new_context_fd(fd, /* read_only= */ false, ssz, &c);
if (r < 0)
return log_error_errno(r, "Failed to open device: %m");
#include <getopt.h>
#include <locale.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
const char *hardware_vendor;
const char *hardware_model;
const char *firmware_version;
+ usec_t firmware_date;
} StatusInfo;
static const char* chassis_string_to_glyph(const char *chassis) {
return table_log_add_error(r);
}
+ if (timestamp_is_set(i->firmware_date)) {
+ r = table_add_many(table,
+ TABLE_FIELD, "Firmware Date",
+ TABLE_TIMESTAMP_DATE, i->firmware_date);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
r = table_print(table, NULL);
if (r < 0)
return table_log_print_error(r);
{ "HardwareVendor", "s", NULL, offsetof(StatusInfo, hardware_vendor) },
{ "HardwareModel", "s", NULL, offsetof(StatusInfo, hardware_model) },
{ "FirmwareVersion", "s", NULL, offsetof(StatusInfo, firmware_version) },
+ { "FirmwareDate", "t", NULL, offsetof(StatusInfo, firmware_date) },
{}
};
return get_hardware_firmware_data("bios_vendor", ret);
}
-static int get_firmware_date(char **ret) {
- return get_hardware_firmware_data("bios_date", ret);
+static int get_firmware_date(usec_t *ret) {
+ _cleanup_free_ char *bios_date = NULL, *month = NULL, *day = NULL, *year = NULL;
+ int r;
+
+ assert(ret);
+
+ r = get_hardware_firmware_data("bios_date", &bios_date);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ *ret = USEC_INFINITY;
+ return 0;
+ }
+
+ const char *p = bios_date;
+ r = extract_many_words(&p, "/", EXTRACT_DONT_COALESCE_SEPARATORS, &month, &day, &year, NULL);
+ if (r < 0)
+ return r;
+ if (r != 3) /* less than three args read? */
+ return -EINVAL;
+ if (!isempty(p)) /* more left in the string? */
+ return -EINVAL;
+
+ unsigned m, d, y;
+ r = safe_atou(month, &m);
+ if (r < 0)
+ return r;
+ if (m < 1 || m > 12)
+ return -EINVAL;
+ m -= 1;
+
+ r = safe_atou(day, &d);
+ if (r < 0)
+ return r;
+ if (d < 1 || d > 31)
+ return -EINVAL;
+
+ r = safe_atou(year, &y);
+ if (r < 0)
+ return r;
+ if (y < 1970 || y > (unsigned) INT_MAX)
+ return -EINVAL;
+ y -= 1900;
+
+ struct tm tm = {
+ .tm_mday = d,
+ .tm_mon = m,
+ .tm_year = y,
+ };
+ time_t v = timegm(&tm);
+ if (v == (time_t) -1)
+ return -errno;
+ if (tm.tm_mday != (int) d || tm.tm_mon != (int) m || tm.tm_year != (int) y)
+ return -EINVAL; /* date was not normalized? (e.g. "30th of feb") */
+
+ *ret = (usec_t) v * USEC_PER_SEC;
+
+ return 0;
}
static const char* valid_chassis(const char *chassis) {
void *userdata,
sd_bus_error *error) {
- _cleanup_free_ char *firmware_date = NULL;
+ usec_t firmware_date = USEC_INFINITY;
(void) get_firmware_date(&firmware_date);
- return sd_bus_message_append(reply, "s", firmware_date);
+ return sd_bus_message_append(reply, "t", firmware_date);
}
static int property_get_hostname(
sd_bus *bus,
static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *text = NULL,
*chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL,
- *firmware_vendor = NULL, *firmware_date = NULL;
+ *firmware_vendor = NULL;
+ usec_t firmware_date = USEC_INFINITY;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
sd_id128_t product_uuid = SD_ID128_NULL;
JSON_BUILD_PAIR("HardwareSerial", JSON_BUILD_STRING(serial)),
JSON_BUILD_PAIR("FirmwareVersion", JSON_BUILD_STRING(firmware_version)),
JSON_BUILD_PAIR("FirmwareVendor", JSON_BUILD_STRING(firmware_vendor)),
- JSON_BUILD_PAIR("FirmwareDate", JSON_BUILD_STRING(firmware_date)),
+ JSON_BUILD_PAIR_FINITE_USEC("FirmwareDate", firmware_date),
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_ID128(product_uuid)),
JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL)));
SD_BUS_PROPERTY("HardwareModel", "s", property_get_hardware_model, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("FirmwareVersion", "s", property_get_firmware_version, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("FirmwareVendor", "s", property_get_firmware_vendor, 0, SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("FirmwareDate", "s", property_get_firmware_date, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("FirmwareDate", "t", property_get_firmware_date, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD_WITH_ARGS("SetHostname",
SD_BUS_ARGS("s", hostname, "b", interactive),
static uint64_t arg_vacuum_size = 0;
static uint64_t arg_vacuum_n_files = 0;
static usec_t arg_vacuum_time = 0;
-static char **arg_output_fields = NULL;
+static Set *arg_output_fields = NULL;
static const char *arg_pattern = NULL;
static pcre2_code *arg_compiled_pattern = NULL;
static PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
STATIC_DESTRUCTOR_REGISTER(arg_user_units, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
-STATIC_DESTRUCTOR_REGISTER(arg_output_fields, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_output_fields, set_freep);
STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pattern_freep);
static enum {
if (!v)
return log_oom();
- if (!arg_output_fields)
- arg_output_fields = TAKE_PTR(v);
- else {
- r = strv_extend_strv(&arg_output_fields, v, true);
- if (r < 0)
- return log_oom();
- }
+ r = set_put_strdupv(&arg_output_fields, v);
+ if (r < 0)
+ return log_oom();
+
break;
}
return r;
}
-static ManagedJournalFile* find_journal(Server *s, uid_t uid) {
+static int find_user_journal(Server *s, uid_t uid, ManagedJournalFile **ret) {
+ _cleanup_(managed_journal_file_closep) ManagedJournalFile *f = NULL;
_cleanup_free_ char *p = NULL;
- ManagedJournalFile *f;
+ int r;
+
+ assert(!uid_for_system_journal(uid));
+
+ f = ordered_hashmap_get(s->user_journals, UID_TO_PTR(uid));
+ if (f)
+ goto found;
+
+ if (asprintf(&p, "%s/user-" UID_FMT ".journal", s->system_storage.path, uid) < 0)
+ return log_oom();
+
+ /* Too many open? Then let's close one (or more) */
+ while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
+ ManagedJournalFile *first;
+
+ assert_se(first = ordered_hashmap_steal_first(s->user_journals));
+ (void) managed_journal_file_close(first);
+ }
+
+ r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_storage.metrics, &f);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(s->user_journals, UID_TO_PTR(uid), f);
+ if (r < 0)
+ return r;
+
+ server_add_acls(f, uid);
+
+found:
+ *ret = TAKE_PTR(f);
+ return 0;
+}
+
+static ManagedJournalFile* find_journal(Server *s, uid_t uid) {
int r;
assert(s);
if (!IN_SET(s->storage, STORAGE_AUTO, STORAGE_PERSISTENT))
return NULL;
- if (uid_for_system_journal(uid))
- return s->system_journal;
-
- f = ordered_hashmap_get(s->user_journals, UID_TO_PTR(uid));
- if (f)
- return f;
-
- if (asprintf(&p, "%s/user-" UID_FMT ".journal", s->system_storage.path, uid) < 0) {
- log_oom();
- return s->system_journal;
- }
+ if (!uid_for_system_journal(uid)) {
+ ManagedJournalFile *f = NULL;
- /* Too many open? Then let's close one (or more) */
- while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
- assert_se(f = ordered_hashmap_steal_first(s->user_journals));
- (void) managed_journal_file_close(f);
- }
-
- r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_storage.metrics, &f);
- if (r < 0)
- return s->system_journal;
+ r = find_user_journal(s, uid, &f);
+ if (r >= 0)
+ return ASSERT_PTR(f);
- r = ordered_hashmap_put(s->user_journals, UID_TO_PTR(uid), f);
- if (r < 0) {
- (void) managed_journal_file_close(f);
- return s->system_journal;
+ log_warning_errno(r, "Failed to open user journal file, falling back to system journal: %m");
}
- server_add_acls(f, uid);
- return f;
+ return s->system_journal;
}
static int do_rotate(
}
}
-static int vacuum_offline_user_journals(Server *s) {
+static int server_archive_offline_user_journals(Server *s) {
_cleanup_closedir_ DIR *d = NULL;
int r;
}
for (;;) {
- _cleanup_free_ char *u = NULL, *full = NULL;
+ _cleanup_free_ char *full = NULL;
_cleanup_close_ int fd = -EBADF;
- const char *a, *b;
struct dirent *de;
ManagedJournalFile *f;
uid_t uid;
log_ratelimit_warning_errno(errno, JOURNAL_LOG_RATELIMIT,
"Failed to enumerate %s, ignoring: %m",
s->system_storage.path);
-
break;
}
- a = startswith(de->d_name, "user-");
- if (!a)
- continue;
- b = endswith(de->d_name, ".journal");
- if (!b)
- continue;
-
- u = strndup(a, b-a);
- if (!u)
- return log_oom();
-
- r = parse_uid(u, &uid);
+ r = journal_file_parse_uid_from_filename(de->d_name, &uid);
if (r < 0) {
- log_debug_errno(r, "Failed to parse UID from file name '%s', ignoring: %m", de->d_name);
+ /* Don't warn if the file is not an online or offline user journal. */
+ if (r != -EREMOTE)
+ log_warning_errno(r, "Failed to parse UID from file name '%s', ignoring: %m", de->d_name);
continue;
}
/* Finally, also rotate all user journals we currently do not have open. (But do so only if we
* actually have access to /var, i.e. are not in the log-to-runtime-journal mode). */
if (!s->runtime_journal)
- (void) vacuum_offline_user_journals(s);
+ (void) server_archive_offline_user_journals(s);
server_process_deferred_closes(s);
}
/* Unless we got *some* sockets and not audit, open audit socket */
if (s->audit_fd >= 0 || no_sockets) {
+ log_info("Collecting audit messages is enabled.");
+
r = server_open_audit(s);
if (r < 0)
return r;
- }
+ } else
+ log_info("Collecting audit messages is disabled.");
r = server_open_varlink(s, varlink_socket, varlink_fd);
if (r < 0)
do { \
int _r_ = (expr); \
if (_unlikely_(_r_ < 0)) \
- log_assert_errno(#expr, -_r_, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__); \
+ log_assert_errno(#expr, -_r_, PROJECT_FILE, __LINE__, __func__); \
} while (false)
static ManagedJournalFile *test_open(const char *name) {
ENTRY_TOKEN="$KERNEL_INSTALL_ENTRY_TOKEN"
BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT"
-BOOT_MNT="$(stat -c %m "$BOOT_ROOT")"
+[ -n "$BOOT_MNT" ] || BOOT_MNT="$(stat -c %m "$BOOT_ROOT")"
if [ "$BOOT_MNT" = '/' ]; then
ENTRY_DIR="$ENTRY_DIR_ABS"
else
D="$(mktemp --tmpdir --directory "test-kernel-install.XXXXXXXXXX")"
+export _KERNEL_INSTALL_BOOTCTL="$PROJECT_BUILD_ROOT/bootctl"
+
# shellcheck disable=SC2064
trap "rm -rf '$D'" EXIT INT QUIT PIPE
mkdir -p "$D/boot"
export KERNEL_INSTALL_CONF_ROOT="$D/sources"
export KERNEL_INSTALL_PLUGINS="$plugin"
export BOOT_ROOT="$D/boot"
+export BOOT_MNT="$D/boot"
export MACHINE_ID='3e0484f3634a418b8e6a39e8828b03e3'
"$kernel_install" -v add 1.1.1 "$D/sources/linux" "$D/sources/initrd"
grep -qE 'image' "$BOOT_ROOT/the-token/1.1.1/linux"
grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
+
+if test -x "$_KERNEL_INSTALL_BOOTCTL"; then
+ echo "Testing bootctl"
+ e2="${entry%+*}_2.conf"
+ cp "$entry" "$e2"
+ export SYSTEMD_ESP_PATH=/
+
+ # create file that is not referenced. Check if cleanup removes
+ # it but leaves the rest alone
+ :> "$BOOT_ROOT/the-token/1.1.2/initrd"
+ "$_KERNEL_INSTALL_BOOTCTL" --root="$BOOT_ROOT" cleanup
+ test ! -e "$BOOT_ROOT/the-token/1.1.2/initrd"
+ test -e "$BOOT_ROOT/the-token/1.1.2/linux"
+ test -e "$BOOT_ROOT/the-token/1.1.1/linux"
+ test -e "$BOOT_ROOT/the-token/1.1.1/initrd"
+ # now remove duplicated entry and make sure files are left over
+ "$_KERNEL_INSTALL_BOOTCTL" --root="$BOOT_ROOT" unlink "${e2##*/}"
+ test -e "$BOOT_ROOT/the-token/1.1.1/linux"
+ test -e "$BOOT_ROOT/the-token/1.1.1/initrd"
+ test -e "$entry"
+ test ! -e "$e2"
+ # remove last entry referencing those files
+ entry_id="${entry##*/}"
+ entry_id="${entry_id%+*}.conf"
+ "$_KERNEL_INSTALL_BOOTCTL" --root="$BOOT_ROOT" unlink "$entry_id"
+ test ! -e "$entry"
+ test ! -e "$BOOT_ROOT/the-token/1.1.1/linux"
+ test ! -e "$BOOT_ROOT/the-token/1.1.1/initrd"
+fi
const struct hw_addr_data *hw_addr,
const struct hw_addr_data *bcast_addr,
uint16_t arp_type,
- uint16_t port);
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority);
int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type);
int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
const void *packet, size_t len);
const struct hw_addr_data *hw_addr,
const struct hw_addr_data *bcast_addr,
uint16_t arp_type,
- uint16_t port) {
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
assert(ifindex > 0);
assert(link);
if (r < 0)
return -errno;
+ if (so_priority_set) {
+ r = setsockopt_int(s, SOL_SOCKET, SO_PRIORITY, so_priority);
+ if (r < 0)
+ return r;
+ }
+
link->ll = (struct sockaddr_ll) {
.sll_family = AF_PACKET,
.sll_protocol = htobe16(ETH_P_IP),
const struct hw_addr_data *hw_addr,
const struct hw_addr_data *bcast_addr,
uint16_t arp_type,
- uint16_t port) {
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
static struct hw_addr_data default_eth_bcast = {
.length = ETH_ALEN,
return _bind_raw_socket(ifindex, link, xid,
hw_addr,
(bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_eth_bcast,
- arp_type, port);
+ arp_type, port, so_priority_set, so_priority);
case ARPHRD_INFINIBAND:
return _bind_raw_socket(ifindex, link, xid,
&HW_ADDR_NULL,
(bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_ib_bcast,
- arp_type, port);
+ arp_type, port, so_priority_set, so_priority);
default:
return -EINVAL;
}
uint8_t code, len;
const uint8_t *option;
size_t offset = 0;
+ int r;
while (offset < buflen) {
code = options[offset ++];
if (error_message) {
_cleanup_free_ char *string = NULL;
- /* Accept a trailing NUL byte */
- if (memchr(option, 0, len - 1))
- return -EINVAL;
-
- string = memdup_suffix0((const char *) option, len);
- if (!string)
- return -ENOMEM;
+ r = make_cstring((const char*) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string);
+ if (r < 0)
+ return r;
if (!ascii_is_valid(string))
return -EINVAL;
sd_dhcp6_client_callback_t callback;
void *userdata;
+ bool send_release;
/* Ignore machine-ID when generating DUID. See dhcp_identifier_set_duid_en(). */
bool test_mode;
}
int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
+ DHCP6Status status;
+
assert(data || data_len == 0);
if (data_len < sizeof(uint16_t))
return -EBADMSG;
+ status = unaligned_read_be16(data);
+
if (ret_status_message) {
- char *msg;
+ _cleanup_free_ char *msg = NULL;
+ const char *s;
/* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415.
* Let's escape unsafe characters for safety. */
if (!msg)
return -ENOMEM;
- *ret_status_message = msg;
+ s = dhcp6_message_status_to_string(status);
+ if (s && !strextend_with_separator(&msg, ": ", s))
+ return -ENOMEM;
+
+ *ret_status_message = TAKE_PTR(msg);
}
- return unaligned_read_be16(data);
+ return status;
}
static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
return r;
if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
- "Received an IA address or PD prefix option with non-zero status: %s%s%s",
- strempty(msg), isempty(msg) ? "" : ": ",
- dhcp6_message_status_to_string(r));
+ "Received an IA address or PD prefix option with non-zero status%s%s",
+ isempty(msg) ? "." : ": ", strempty(msg));
if (r < 0)
/* Let's log but ignore the invalid status option. */
log_dhcp6_client_errno(client, r,
return r;
if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
- "Received an IA option with non-zero status: %s%s%s",
- strempty(msg), isempty(msg) ? "" : ": ",
- dhcp6_message_status_to_string(r));
+ "Received an IA option with non-zero status%s%s",
+ isempty(msg) ? "." : ": ", strempty(msg));
if (r < 0)
log_dhcp6_client_errno(client, r,
"Received an IA option with an invalid status sub option, ignoring: %m");
[DHCP6_STATE_BOUND] = "bound",
[DHCP6_STATE_RENEW] = "renew",
[DHCP6_STATE_REBIND] = "rebind",
+ [DHCP6_STATE_STOPPING] = "stopping",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State);
DHCP6_STATE_BOUND,
DHCP6_STATE_RENEW,
DHCP6_STATE_REBIND,
+ DHCP6_STATE_STOPPING,
_DHCP6_STATE_MAX,
_DHCP6_STATE_INVALID = -EINVAL,
} DHCP6State;
uint32_t id,
const struct hw_addr_data *hw_addr,
const struct hw_addr_data *bcast_addr,
- uint16_t arp_type, uint16_t port) {
+ uint16_t arp_type,
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
int fd;
fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
sd_dhcp_lease *lease;
usec_t start_delay;
int ip_service_type;
+ int socket_priority;
+ bool socket_priority_set;
/* Ignore machine-ID when generating DUID. See dhcp_identifier_set_duid_en(). */
bool test_mode;
r = asprintf(&t, "DATA");
break;
case 1:
- if (len != sizeof_field(sd_dhcp_client_id, eth))
- return -EINVAL;
-
- r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
- client_id->eth.haddr[0],
- client_id->eth.haddr[1],
- client_id->eth.haddr[2],
- client_id->eth.haddr[3],
- client_id->eth.haddr[4],
- client_id->eth.haddr[5]);
+ if (len == sizeof_field(sd_dhcp_client_id, eth))
+ r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
+ client_id->eth.haddr[0],
+ client_id->eth.haddr[1],
+ client_id->eth.haddr[2],
+ client_id->eth.haddr[3],
+ client_id->eth.haddr[4],
+ client_id->eth.haddr[5]);
+ else
+ r = asprintf(&t, "ETHER");
break;
case 2 ... 254:
r = asprintf(&t, "ARP/LL");
break;
case 255:
- if (len < 6)
- return -EINVAL;
-
- uint32_t iaid = be32toh(client_id->ns.iaid);
- uint16_t duid_type = be16toh(client_id->ns.duid.type);
- if (dhcp_validate_duid_len(duid_type, len - 6, true) < 0)
- return -EINVAL;
-
- r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+ if (len < sizeof(uint32_t))
+ r = asprintf(&t, "IAID/DUID");
+ else {
+ uint32_t iaid = be32toh(client_id->ns.iaid);
+ /* TODO: check and stringify DUID */
+ r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+ }
break;
}
-
if (r < 0)
return -ENOMEM;
+
*ret = TAKE_PTR(t);
return 0;
}
return 0;
}
+int sd_dhcp_client_set_socket_priority(sd_dhcp_client *client, int socket_priority) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->socket_priority_set = true;
+ client->socket_priority = socket_priority;
+
+ return 0;
+}
+
int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint32_t fallback_lease_lifetime) {
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
&client->hw_addr, &client->bcast_addr,
- client->arp_type, client->port);
+ client->arp_type, client->port,
+ client->socket_priority_set, client->socket_priority);
if (r < 0) {
client_stop(client, r);
return r;
r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
&client->hw_addr, &client->bcast_addr,
- client->arp_type, client->port);
+ client->arp_type, client->port,
+ client->socket_priority_set, client->socket_priority);
if (r < 0) {
client_stop(client, r);
return 0;
}
static int lease_parse_string(const uint8_t *option, size_t len, char **ret) {
+ int r;
+
assert(option);
assert(ret);
* One trailing NUL byte is OK, we don't mind. See:
* https://github.com/systemd/systemd/issues/1337
*/
- if (memchr(option, 0, len - 1))
- return -EINVAL;
-
- string = memdup_suffix0((const char *) option, len);
- if (!string)
- return -ENOMEM;
+ r = make_cstring((const char*) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string);
+ if (r < 0)
+ return r;
free_and_replace(*ret, string);
}
return 0;
}
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->send_release = enable;
+ return 0;
+}
+
int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
assert_return(client, -EINVAL);
DHCP6_STATE_SOLICITATION,
DHCP6_STATE_REQUEST,
DHCP6_STATE_RENEW,
- DHCP6_STATE_REBIND));
+ DHCP6_STATE_REBIND,
+ DHCP6_STATE_STOPPING));
assert(buf);
assert(*buf);
assert(offset);
return r;
}
- r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
- if (r < 0)
- return r;
+ if (client->state != DHCP6_STATE_STOPPING) {
+ r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
+ if (r < 0)
+ return r;
+ }
r = dhcp6_option_append_user_class(buf, offset, client->user_class);
if (r < 0)
return DHCP6_MESSAGE_RENEW;
case DHCP6_STATE_REBIND:
return DHCP6_MESSAGE_REBIND;
+ case DHCP6_STATE_STOPPING:
+ return DHCP6_MESSAGE_RELEASE;
default:
assert_not_reached();
}
req_opts = p;
break;
+ case DHCP6_STATE_STOPPING:
+ return 0;
+
default:
n = client->n_req_opts;
req_opts = client->req_opts;
return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_ORO, n * sizeof(be16_t), req_opts);
}
+static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
+ assert(client);
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (!client->mudurl)
+ return 0;
+
+ if (client->state == DHCP6_STATE_STOPPING)
+ return 0;
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_MUD_URL_V6,
+ strlen(client->mudurl), client->mudurl);
+}
+
int dhcp6_client_send_message(sd_dhcp6_client *client) {
_cleanup_free_ uint8_t *buf = NULL;
struct in6_addr all_servers =
case DHCP6_STATE_REQUEST:
case DHCP6_STATE_RENEW:
-
+ case DHCP6_STATE_STOPPING:
r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_SERVERID,
client->lease->serverid_len,
client->lease->serverid);
return r;
break;
- case DHCP6_STATE_STOPPED:
case DHCP6_STATE_BOUND:
+ case DHCP6_STATE_STOPPED:
default:
assert_not_reached();
}
- if (client->mudurl) {
- r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_MUD_URL_V6,
- strlen(client->mudurl), client->mudurl);
- if (r < 0)
- return r;
- }
+ r = client_append_mudurl(client, &buf, &offset);
+ if (r < 0)
+ return r;
r = client_append_oro(client, &buf, &offset);
if (r < 0)
break;
case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
case DHCP6_STATE_BOUND:
default:
assert_not_reached();
assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW));
break;
case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
case DHCP6_STATE_BOUND:
default:
assert_not_reached();
case DHCP6_STATE_BOUND:
case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
default:
assert_not_reached();
}
return 0;
}
+static int client_send_release(sd_dhcp6_client *client) {
+ sd_dhcp6_lease *lease;
+
+ assert(client);
+
+ if (!client->send_release)
+ return 0;
+
+ if (sd_dhcp6_client_get_lease(client, &lease) < 0)
+ return 0;
+
+ if (!lease->ia_na && !lease->ia_pd)
+ return 0;
+
+ client_set_state(client, DHCP6_STATE_STOPPING);
+ return dhcp6_client_send_message(client);
+}
+
int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
+ int r;
+
if (!client)
return 0;
+ /* Intentionally ignoring failure to send DHCP6 release. The DHCPv6 client
+ * engine is about to release its UDP socket unconditionally. */
+ r = client_send_release(client);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r,
+ "Failed to send DHCP6 release message, ignoring: %m");
+
client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
client->receive_message = sd_event_source_unref(client->receive_message);
r = dhcp6_option_parse_status(optval, optlen, &msg);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to parse status code: %m");
-
if (r > 0)
return log_dhcp6_client_errno(client, dhcp6_message_status_to_errno(r),
- "Received %s message with non-zero status: %s%s%s",
+ "Received %s message with non-zero status%s%s",
dhcp6_message_type_to_string(message->type),
- strempty(msg), isempty(msg) ? "" : ": ",
- dhcp6_message_status_to_string(r));
+ isempty(msg) ? "." : ": ", strempty(msg));
break;
}
case SD_DHCP6_OPTION_IA_NA: {
uint32_t id,
const struct hw_addr_data *_hw_addr,
const struct hw_addr_data *_bcast_addr,
- uint16_t arp_type, uint16_t port) {
+ uint16_t arp_type,
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
return -errno;
0x00, 0x00,
};
+/* RFC 3315 section 18.1.6. The DHCP6 Release message must include:
+ - transaction id
+ - server identifier
+ - client identifier
+ - all released IA with addresses included
+ - elapsed time (required for all messages).
+ All other options aren't required. */
+static const uint8_t msg_release[] = {
+ /* Message type */
+ DHCP6_MESSAGE_RELEASE,
+ /* Transaction ID */
+ 0x00, 0x00, 0x00,
+ /* Server ID */
+ 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e,
+ SERVER_ID_BYTES,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x44,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS1_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS2_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX1_BYTES,
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX2_BYTES,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Extra options */
+ /* Elapsed time */
+ 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02,
+ 0x00, 0x00,
+};
+
static const uint8_t msg_reply[] = {
/* Message type */
DHCP6_MESSAGE_REPLY,
assert_se(memcmp(msg, msg_solicit, len - sizeof(be16_t)) == 0);
}
+static void test_client_verify_release(const DHCP6Message *msg, size_t len) {
+ log_debug("/* %s */", __func__);
+
+ assert_se(len == sizeof(msg_release));
+ assert_se(msg->type == DHCP6_MESSAGE_RELEASE);
+ /* The transaction ID and elapsed time value are not deterministic. Skip them. */
+ assert_se(memcmp(msg->options, msg_release + offsetof(DHCP6Message, options),
+ len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+}
+
static void test_client_verify_request(const DHCP6Message *msg, size_t len) {
log_debug("/* %s */", __func__);
assert_se(len == sizeof(msg_request));
assert_se(msg->type == DHCP6_MESSAGE_REQUEST);
/* The transaction ID and elapsed time value are not deterministic. Skip them. */
- assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options), len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+ assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options),
+ len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
}
static void test_lease_common(sd_dhcp6_client *client) {
case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
log_debug("/* %s (event=ip-acquire) */", __func__);
- assert_se(IN_SET(test_client_sent_message_count, 3, 4));
+ assert_se(IN_SET(test_client_sent_message_count, 3, 5));
test_lease_managed(client);
assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0);
break;
- case 4:
+ case 5:
assert_se(sd_event_exit(sd_dhcp6_client_get_event(client), 0) >= 0);
break;
break;
case 3:
+ test_client_verify_release(packet, len);
+ /* when stopping, dhcp6 client doesn't wait for release server reply */
+ assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
+ break;
+
+ case 4:
test_client_verify_solicit(packet, len);
assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
break;
assert_se(sd_dhcp6_client_set_local_address(client, &local_address) >= 0);
assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") >= 0);
assert_se(sd_dhcp6_client_set_iaid(client, unaligned_read_be32((uint8_t[]) { IA_ID_BYTES })) >= 0);
+ assert_se(sd_dhcp6_client_set_send_release(client, true) >= 0);
dhcp6_client_set_test_mode(client, true);
assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER) >= 0);
assert_se(sd_event_loop(e) >= 0);
- assert_se(test_client_sent_message_count == 4);
+ assert_se(test_client_sent_message_count == 5);
assert_se(!sd_dhcp6_client_unref(client_ref));
test_fd[1] = safe_close(test_fd[1]);
static bool test_stopped;
static int test_fd[2];
-static sd_event_source *recv_router_advertisement;
static struct {
struct in6_addr address;
unsigned char prefixlen;
}
TEST(ra) {
- sd_event *e;
- sd_radv *ra;
- unsigned i;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *recv_router_advertisement = NULL;
+ _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL;
assert_se(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0);
assert_se(sd_radv_set_rdnss(ra, 60, &test_rdnss, 1) >= 0);
assert_se(sd_radv_set_dnssl(ra, 60, (char **)test_dnssl) >= 0);
- for (i = 0; i < ELEMENTSOF(prefix); i++) {
+ for (unsigned i = 0; i < ELEMENTSOF(prefix); i++) {
sd_radv_prefix *p;
printf("Test prefix %u\n", i);
assert_se(!p);
}
- assert_se(sd_event_add_io(e, &recv_router_advertisement, test_fd[0],
- EPOLLIN, radv_recv, ra) >= 0);
+ assert_se(sd_event_add_io(e, &recv_router_advertisement, test_fd[0], EPOLLIN, radv_recv, ra) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(recv_router_advertisement, true) >= 0);
assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
2 * USEC_PER_SEC, 0,
assert_se(sd_radv_start(ra) >= 0);
assert_se(sd_event_loop(e) >= 0);
-
- ra = sd_radv_unref(ra);
- assert_se(!ra);
-
- close(test_fd[0]);
-
- sd_event_unref(e);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
#include "sd-ndisc.h"
#include "alloc-util.h"
+#include "fd-util.h"
#include "hexdecoct.h"
#include "icmp6-util.h"
#include "socket-util.h"
}
TEST(rs) {
- sd_event *e;
- sd_ndisc *nd;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
send_ra_function = send_ra;
assert_se(sd_ndisc_start(nd) >= 0);
assert_se(sd_ndisc_start(nd) >= 0);
assert_se(sd_ndisc_stop(nd) >= 0);
+ test_fd[1] = safe_close(test_fd[1]);
assert_se(sd_ndisc_start(nd) >= 0);
assert_se(sd_event_loop(e) >= 0);
- nd = sd_ndisc_unref(nd);
- assert_se(!nd);
-
- close(test_fd[1]);
-
- sd_event_unref(e);
+ test_fd[1] = safe_close(test_fd[1]);
}
static int test_timeout_value(uint8_t flags) {
}
TEST(timeout) {
- sd_event *e;
- sd_ndisc *nd;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
send_ra_function = test_timeout_value;
assert_se(sd_event_loop(e) >= 0);
- nd = sd_ndisc_unref(nd);
-
- sd_event_unref(e);
+ test_fd[1] = safe_close(test_fd[1]);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
sd_bus_emit_signal_to;
sd_bus_emit_signal_tov;
sd_bus_message_new_signal_to;
+ sd_pidfd_get_cgroup;
+ sd_pidfd_get_machine_name;
+ sd_pidfd_get_owner_uid;
+ sd_pidfd_get_session;
+ sd_pidfd_get_slice;
+ sd_pidfd_get_unit;
+ sd_pidfd_get_user_slice;
+ sd_pidfd_get_user_unit;
} LIBSYSTEMD_252;
############################################################
tests += [
+ [files('sd-journal/test-journal-file.c')],
+
[files('sd-journal/test-journal-send.c')],
[files('sd-journal/test-journal-match.c')],
m->sensitive = true;
return 0;
}
+
+char** bus_message_make_log_fields(sd_bus_message *m) {
+ _cleanup_strv_free_ char **strv = NULL;
+
+ assert(m);
+
+ (void) strv_extend_assignment(&strv, "DBUS_MESSAGE_TYPE", bus_message_type_to_string(m->header->type));
+ (void) strv_extend_assignment(&strv, "DBUS_SENDER", sd_bus_message_get_sender(m));
+ (void) strv_extend_assignment(&strv, "DBUS_DESTINATION", sd_bus_message_get_destination(m));
+ (void) strv_extend_assignment(&strv, "DBUS_PATH", sd_bus_message_get_path(m));
+ (void) strv_extend_assignment(&strv, "DBUS_INTERFACE", sd_bus_message_get_interface(m));
+ (void) strv_extend_assignment(&strv, "DBUS_MEMBER", sd_bus_message_get_member(m));
+
+ (void) strv_extendf(&strv, "DBUS_MESSAGE_COOKIE=%" PRIu64, BUS_MESSAGE_COOKIE(m));
+ if (m->reply_cookie != 0)
+ (void) strv_extendf(&strv, "DBUS_MESSAGE_REPLY_COOKIE=%" PRIu64, m->reply_cookie);
+
+ (void) strv_extend_assignment(&strv, "DBUS_SIGNATURE", m->root_container.signature);
+ (void) strv_extend_assignment(&strv, "DBUS_ERROR_NAME", m->error.name);
+ (void) strv_extend_assignment(&strv, "DBUS_ERROR_MESSAGE", m->error.message);
+
+ return TAKE_PTR(strv);
+}
sd_bus_message* bus_message_ref_queued(sd_bus_message *m, sd_bus *bus);
sd_bus_message* bus_message_unref_queued(sd_bus_message *m, sd_bus *bus);
+
+char** bus_message_make_log_fields(sd_bus_message *m);
}
static int process_message(sd_bus *bus, sd_bus_message *m) {
+ _unused_ _cleanup_(log_context_freep) LogContext *c = NULL;
int r;
assert(bus);
bus->current_message = m;
bus->iteration_counter++;
+ if (log_context_enabled())
+ c = log_context_new_consume(bus_message_make_log_fields(m));
+
log_debug_bus_message(m);
r = process_hello(bus, m);
static int device_monitor_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ _unused_ _cleanup_(log_context_freep) LogContext *c = NULL;
sd_device_monitor *m = ASSERT_PTR(userdata);
if (device_monitor_receive_device(m, &device) <= 0)
return 0;
+ if (log_context_enabled())
+ c = log_context_new_consume(device_make_log_fields(device));
+
if (m->callback)
return m->callback(m, device, m->userdata);
void device_set_devlink_priority(sd_device *device, int priority);
int device_ensure_usec_initialized(sd_device *device, sd_device *device_old);
int device_add_devlink(sd_device *device, const char *devlink);
+void device_remove_devlink(sd_device *device, const char *devlink);
bool device_has_devlink(sd_device *device, const char *devlink);
int device_add_property(sd_device *device, const char *property, const char *value);
int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4);
#include "devnum-util.h"
#include "fd-util.h"
#include "string-util.h"
+#include "strv.h"
int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) {
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
return TAKE_FD(fd);
}
+
+static int add_string_field(
+ sd_device *device,
+ const char *field,
+ int (*func)(sd_device *dev, const char **s),
+ char ***strv) {
+
+ const char *s;
+ int r;
+
+ assert(device);
+ assert(field);
+ assert(func);
+ assert(strv);
+
+ r = func(device, &s);
+ if (r < 0 && r != -ENOENT)
+ log_device_debug_errno(device, r, "Failed to get device \"%s\" property, ignoring: %m", field);
+ if (r >= 0)
+ (void) strv_extend_assignment(strv, field, s);
+
+ return 0;
+}
+
+char** device_make_log_fields(sd_device *device) {
+ _cleanup_strv_free_ char **strv = NULL;
+ dev_t devnum;
+ int ifindex;
+ sd_device_action_t action;
+ uint64_t seqnum, diskseq;
+ int r;
+
+ assert(device);
+
+ (void) add_string_field(device, "SYSPATH", sd_device_get_syspath, &strv);
+ (void) add_string_field(device, "SUBSYSTEM", sd_device_get_subsystem, &strv);
+ (void) add_string_field(device, "DEVTYPE", sd_device_get_devtype, &strv);
+ (void) add_string_field(device, "DRIVER", sd_device_get_driver, &strv);
+ (void) add_string_field(device, "DEVPATH", sd_device_get_devpath, &strv);
+ (void) add_string_field(device, "DEVNAME", sd_device_get_devname, &strv);
+ (void) add_string_field(device, "SYSNAME", sd_device_get_sysname, &strv);
+ (void) add_string_field(device, "SYSNUM", sd_device_get_sysnum, &strv);
+
+ r = sd_device_get_devnum(device, &devnum);
+ if (r < 0 && r != -ENOENT)
+ log_device_debug_errno(device, r, "Failed to get device \"DEVNUM\" property, ignoring: %m");
+ if (r >= 0)
+ (void) strv_extendf(&strv, "DEVNUM="DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(devnum));
+
+ r = sd_device_get_ifindex(device, &ifindex);
+ if (r < 0 && r != -ENOENT)
+ log_device_debug_errno(device, r, "Failed to get device \"IFINDEX\" property, ignoring: %m");
+ if (r >= 0)
+ (void) strv_extendf(&strv, "IFINDEX=%i", ifindex);
+
+ r = sd_device_get_action(device, &action);
+ if (r < 0 && r != -ENOENT)
+ log_device_debug_errno(device, r, "Failed to get device \"ACTION\" property, ignoring: %m");
+ if (r >= 0)
+ (void) strv_extendf(&strv, "ACTION=%s", device_action_to_string(action));
+
+ r = sd_device_get_seqnum(device, &seqnum);
+ if (r < 0 && r != -ENOENT)
+ log_device_debug_errno(device, r, "Failed to get device \"SEQNUM\" property, ignoring: %m");
+ if (r >= 0)
+ (void) strv_extendf(&strv, "SEQNUM=%"PRIu64, seqnum);
+
+ r = sd_device_get_diskseq(device, &diskseq);
+ if (r < 0 && r != -ENOENT)
+ log_device_debug_errno(device, r, "Failed to get device \"DISKSEQ\" property, ignoring: %m");
+ if (r >= 0)
+ (void) strv_extendf(&strv, "DISKSEQ=%"PRIu64, diskseq);
+
+ return TAKE_PTR(strv);
+}
return devname_from_devnum(st->st_mode, st->st_rdev, ret);
}
int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret);
+
+char** device_make_log_fields(sd_device *device);
return 0;
}
+void device_remove_devlink(sd_device *device, const char *devlink) {
+ _cleanup_free_ char *s = NULL;
+
+ assert(device);
+ assert(devlink);
+
+ s = set_remove(device->devlinks, devlink);
+ if (!s)
+ return;
+
+ device->devlinks_generation++;
+ device->property_devlinks_outdated = true;
+}
+
bool device_has_devlink(sd_device *device, const char *devlink) {
assert(device);
assert(devlink);
}
DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(id128_hash_ops_free, sd_id128_t, id128_hash_func, id128_compare_func, free);
int id128_get_product(sd_id128_t *ret) {
sd_id128_t uuid;
void id128_hash_func(const sd_id128_t *p, struct siphash *state);
int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
extern const struct hash_ops id128_hash_ops;
+extern const struct hash_ops id128_hash_ops_free;
sd_id128_t id128_make_v4_uuid(sd_id128_t id);
#include "string-util.h"
#include "strv.h"
#include "sync-util.h"
+#include "user-util.h"
#include "xattr-util.h"
#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
return r;
}
+int journal_file_parse_uid_from_filename(const char *path, uid_t *ret_uid) {
+ _cleanup_free_ char *buf = NULL, *p = NULL;
+ const char *a, *b, *at;
+ int r;
+
+ /* This helper returns -EREMOTE when the filename doesn't match user online/offline journal
+ * pattern. Hence it currently doesn't parse archived or disposed user journals. */
+
+ assert(path);
+ assert(ret_uid);
+
+ r = path_extract_filename(path, &p);
+ if (r < 0)
+ return r;
+ if (r == O_DIRECTORY)
+ return -EISDIR;
+
+ a = startswith(p, "user-");
+ if (!a)
+ return -EREMOTE;
+ b = endswith(p, ".journal");
+ if (!b)
+ return -EREMOTE;
+
+ at = strchr(a, '@');
+ if (at)
+ return -EREMOTE;
+
+ buf = strndup(a, b-a);
+ if (!buf)
+ return -ENOMEM;
+
+ return parse_uid(buf, ret_uid);
+}
+
int journal_file_archive(JournalFile *f, char **ret_previous_path) {
_cleanup_free_ char *p = NULL;
void journal_file_print_header(JournalFile *f);
int journal_file_archive(JournalFile *f, char **ret_previous_path);
+int journal_file_parse_uid_from_filename(const char *path, uid_t *uid);
JournalFile* journal_initiate_close(JournalFile *f, Set *deferred_closes);
int journal_file_dispose(int dir_fd, const char *fname);
}
static char *match_make_string(Match *m) {
- char *p = NULL, *r;
+ _cleanup_free_ char *p = NULL;
bool enclose = false;
if (!m)
return cescape_length(m->data, m->size);
LIST_FOREACH(matches, i, m->matches) {
- char *t, *k;
+ _cleanup_free_ char *t = NULL;
t = match_make_string(i);
if (!t)
- return mfree(p);
+ return NULL;
if (p) {
- k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t);
- free(p);
- free(t);
-
- if (!k)
+ if (!strextend(&p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t))
return NULL;
- p = k;
-
enclose = true;
} else
- p = t;
+ p = TAKE_PTR(t);
}
- if (enclose) {
- r = strjoin("(", p, ")");
- free(p);
- return r;
- }
+ if (enclose)
+ return strjoin("(", p, ")");
- return p;
+ return TAKE_PTR(p);
}
char *journal_make_match_string(sd_journal *j) {
static sd_journal *journal_new(int flags, const char *path, const char *namespace) {
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
- j = new0(sd_journal, 1);
+ j = new(sd_journal, 1);
if (!j)
return NULL;
- j->original_pid = getpid_cached();
- j->toplevel_fd = -EBADF;
- j->inotify_fd = -EBADF;
- j->flags = flags;
- j->data_threshold = DEFAULT_DATA_THRESHOLD;
+ *j = (sd_journal) {
+ .original_pid = getpid_cached(),
+ .toplevel_fd = -EBADF,
+ .inotify_fd = -EBADF,
+ .flags = flags,
+ .data_threshold = DEFAULT_DATA_THRESHOLD,
+ };
if (path) {
char *t;
}
_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
- Object *o;
JournalFile *f;
+ Object *o;
int r;
assert_return(j, -EINVAL);
f = j->current_file;
if (!f)
return -EADDRNOTAVAIL;
-
if (f->current_offset <= 0)
return -EADDRNOTAVAIL;
return -ESTALE;
}
+ uint64_t t = le64toh(o->entry.monotonic);
+ if (!VALID_MONOTONIC(t))
+ return -EBADMSG;
+
if (ret)
- *ret = le64toh(o->entry.monotonic);
+ *ret = t;
return 0;
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "journal-file.h"
+#include "tests.h"
+#include "user-util.h"
+
+static void test_journal_file_parse_uid_from_filename_simple(
+ const char *path,
+ uid_t expected_uid,
+ int expected_error) {
+
+ uid_t uid = UID_INVALID;
+ int r;
+
+ log_info("testing %s", path);
+
+ r = journal_file_parse_uid_from_filename(path, &uid);
+ assert_se(r == expected_error);
+ if (r < 0)
+ assert_se(uid == UID_INVALID);
+ else
+ assert_se(uid == expected_uid);
+}
+
+TEST(journal_file_parse_uid_from_filename) {
+
+ test_journal_file_parse_uid_from_filename_simple("/var/log/journal/", 0, -EISDIR);
+
+ /* The helper should return -EREMOTE for any filenames that don't look like an online or offline user
+ * journals. This includes archived and disposed journal files. */
+ test_journal_file_parse_uid_from_filename_simple("/etc/password", 0, -EREMOTE);
+ test_journal_file_parse_uid_from_filename_simple("system.journal", 0, -EREMOTE);
+ test_journal_file_parse_uid_from_filename_simple("user-1000@0005d26980bdce6e-2f2a4939583822ef.journal~", 0, -EREMOTE);
+ test_journal_file_parse_uid_from_filename_simple("user-1000@xxx-yyy-zzz.journal", 0, -EREMOTE);
+
+ test_journal_file_parse_uid_from_filename_simple("user-1000.journal", 1000, 0);
+ test_journal_file_parse_uid_from_filename_simple("user-foo.journal", 0, -EINVAL);
+ test_journal_file_parse_uid_from_filename_simple("user-65535.journal", 0, -ENXIO);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
#include "macro.h"
#include "parse-util.h"
#include "path-util.h"
+#include "process-util.h"
#include "socket-util.h"
#include "stdio-util.h"
#include "string-util.h"
return 0;
}
+_public_ int sd_pidfd_get_session(int pidfd, char **ret_session) {
+ _cleanup_free_ char *session = NULL;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EBADF);
+ assert_return(ret_session, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_session(pid, &session);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_session = TAKE_PTR(session);
+
+ return 0;
+}
+
+_public_ int sd_pidfd_get_unit(int pidfd, char **ret_unit) {
+ _cleanup_free_ char *unit = NULL;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EBADF);
+ assert_return(ret_unit, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_unit(pid, &unit);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_unit = TAKE_PTR(unit);
+
+ return 0;
+}
+
+_public_ int sd_pidfd_get_user_unit(int pidfd, char **ret_unit) {
+ _cleanup_free_ char *unit = NULL;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EBADF);
+ assert_return(ret_unit, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_user_unit(pid, &unit);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_unit = TAKE_PTR(unit);
+
+ return 0;
+}
+
+_public_ int sd_pidfd_get_machine_name(int pidfd, char **ret_name) {
+ _cleanup_free_ char *name = NULL;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EBADF);
+ assert_return(ret_name, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_machine_name(pid, &name);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_name = TAKE_PTR(name);
+
+ return 0;
+}
+
+_public_ int sd_pidfd_get_slice(int pidfd, char **ret_slice) {
+ _cleanup_free_ char *slice = NULL;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EBADF);
+ assert_return(ret_slice, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_slice(pid, &slice);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_slice = TAKE_PTR(slice);
+
+ return 0;
+}
+
+_public_ int sd_pidfd_get_user_slice(int pidfd, char **ret_slice) {
+ _cleanup_free_ char *slice = NULL;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EBADF);
+ assert_return(ret_slice, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_user_slice(pid, &slice);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_slice = TAKE_PTR(slice);
+
+ return 0;
+}
+
+_public_ int sd_pidfd_get_owner_uid(int pidfd, uid_t *ret_uid) {
+ uid_t uid;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EINVAL);
+ assert_return(ret_uid, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_owner_uid(pid, &uid);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_uid = uid;
+
+ return 0;
+}
+
+_public_ int sd_pidfd_get_cgroup(int pidfd, char **ret_cgroup) {
+ _cleanup_free_ char *cgroup = NULL;
+ pid_t pid;
+ int r;
+
+ assert_return(pidfd >= 0, -EBADF);
+ assert_return(ret_cgroup, -EINVAL);
+
+ r = pidfd_get_pid(pidfd, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_pid_get_cgroup(pid, &cgroup);
+ if (r < 0)
+ return r;
+
+ r = pidfd_verify_pid(pidfd, pid);
+ if (r < 0)
+ return r;
+
+ *ret_cgroup = TAKE_PTR(cgroup);
+
+ return 0;
+}
+
_public_ int sd_peer_get_session(int fd, char **session) {
struct ucred ucred = UCRED_INVALID;
int r;
#include "fd-util.h"
#include "format-util.h"
#include "log.h"
+#include "missing_syscall.h"
+#include "process-util.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
*type = NULL, *class = NULL, *state = NULL, *state2 = NULL,
*seat = NULL, *session = NULL,
*unit = NULL, *user_unit = NULL, *slice = NULL;
+ _cleanup_close_ int pidfd = -EBADF;
int r;
uid_t u, u2 = UID_INVALID;
char *t, **seats = NULL, **sessions = NULL;
log_info("sd_pid_get_cgroup(0, …) → %s / \"%s\"", e(r), strnull(cgroup));
assert_se(IN_SET(r, 0, -ENOMEDIUM));
+ pidfd = pidfd_open(getpid_cached(), 0);
+ if (pidfd >= 0) {
+ _cleanup_free_ char *cgroup2 = NULL, *session2 = NULL,
+ *unit2 = NULL, *user_unit2 = NULL, *slice2 = NULL;
+
+ r = sd_pidfd_get_unit(pidfd, &unit2);
+ log_info("sd_pidfd_get_unit(pidfd, …) → %s / \"%s\"", e(r), strnull(unit2));
+ assert_se(IN_SET(r, 0, -ENODATA));
+
+ r = sd_pidfd_get_user_unit(pidfd, &user_unit2);
+ log_info("sd_pidfd_get_user_unit(pidfd, …) → %s / \"%s\"", e(r), strnull(user_unit2));
+ assert_se(IN_SET(r, 0, -ENODATA));
+
+ r = sd_pidfd_get_slice(pidfd, &slice2);
+ log_info("sd_pidfd_get_slice(pidfd, …) → %s / \"%s\"", e(r), strnull(slice2));
+ assert_se(IN_SET(r, 0, -ENODATA));
+
+ r = sd_pidfd_get_owner_uid(pidfd, &u2);
+ log_info("sd_pidfd_get_owner_uid(pidfd, …) → %s / "UID_FMT, e(r), u2);
+ assert_se(IN_SET(r, 0, -ENODATA));
+
+ r = sd_pidfd_get_session(pidfd, &session2);
+ log_info("sd_pidfd_get_session(pidfd, …) → %s / \"%s\"", e(r), strnull(session2));
+
+ r = sd_pidfd_get_cgroup(pidfd, &cgroup2);
+ log_info("sd_pidfd_get_cgroup(pidfd, …) → %s / \"%s\"", e(r), strnull(cgroup2));
+ assert_se(IN_SET(r, 0, -ENOMEDIUM));
+ }
+
r = sd_uid_get_display(u2, &display_session);
log_info("sd_uid_get_display("UID_FMT", …) → %s / \"%s\"", u2, e(r), strnull(display_session));
if (u2 == UID_INVALID)
return network_link_get_string(ifindex, "NETWORK_FILE", ret);
}
+int sd_network_link_get_network_file_dropins(int ifindex, char ***ret) {
+ _cleanup_free_ char **sv = NULL, *joined = NULL;
+ int r;
+
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = network_link_get_string(ifindex, "NETWORK_FILE_DROPINS", &joined);
+ if (r < 0)
+ return r;
+
+ r = strv_split_full(&sv, joined, ":", EXTRACT_CUNESCAPE);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(sv);
+ return 0;
+}
+
int sd_network_link_get_operational_state(int ifindex, char **ret) {
return network_link_get_string(ifindex, "OPER_STATE", ret);
}
case SD_PATH_CATALOG:
*ret = "/usr/lib/systemd/catalog";
return 0;
+
+ case SD_PATH_SYSTEMD_SYSTEM_ENVIRONMENT_GENERATOR:
+ *ret = SYSTEM_ENV_GENERATOR_DIR;
+ return 0;
+
+ case SD_PATH_SYSTEMD_USER_ENVIRONMENT_GENERATOR:
+ *ret = USER_ENV_GENERATOR_DIR;
+ return 0;
}
return -EOPNOTSUPP;
return 0;
}
+ case SD_PATH_SYSTEMD_SEARCH_SYSTEM_ENVIRONMENT_GENERATOR:
+ case SD_PATH_SYSTEMD_SEARCH_USER_ENVIRONMENT_GENERATOR: {
+ char **t;
+
+ t = env_generator_binary_paths(type == SD_PATH_SYSTEMD_SEARCH_SYSTEM_ENVIRONMENT_GENERATOR);
+ if (!t)
+ return -ENOMEM;
+
+ *list = t;
+ return 0;
+ }
+
case SD_PATH_SYSTEMD_SEARCH_NETWORK:
return strv_from_nulstr(list, NETWORK_DIRS_NULSTR);
/* If that didn't work, strip off the
* other layouts from the entry, too */
- x = strndup(a[1], strcspn(a[1], ","));
+ x = strdupcspn(a[1], ",");
+ if (!x)
+ return -ENOMEM;
if (startswith_comma(c->x11_layout, x))
matching = 1;
}
return log_oom();
}
- type = l2tp_local_address_type_from_string(rvalue);
+ type = l2tp_local_address_type_from_string(addr_or_type);
if (type >= 0) {
free_and_replace(t->local_ifname, ifname);
t->local_address_type = type;
return 0;
}
- r = in_addr_from_string_auto(rvalue, &f, &a);
+ r = in_addr_from_string_auto(addr_or_type, &f, &a);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid L2TP Tunnel local address specified, ignoring assignment: %s", rvalue);
+ "Invalid L2TP Tunnel local address \"%s\" specified, ignoring assignment: %s", addr_or_type, rvalue);
return 0;
}
if (in_addr_is_null(f, &a)) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
"L2TP Tunnel local address cannot be null, ignoring assignment: %s", rvalue);
return 0;
}
}
if (in_addr_is_null(f, &a)) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
"L2TP Tunnel remote address cannot be null, ignoring assignment: %s", rvalue);
return 0;
}
return 0;
}
+static int format_dropins(char **dropins) {
+ STRV_FOREACH(d, dropins) {
+ _cleanup_free_ char *s = NULL;
+ int glyph = *(d + 1) == NULL ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH;
+
+ s = strjoin(special_glyph(glyph), *d);
+ if (!s)
+ return log_oom();
+
+ free_and_replace(*d, s);
+ }
+
+ return 0;
+}
+
static int link_status_one(
sd_bus *bus,
sd_netlink *rtnl,
sd_hwdb *hwdb,
const LinkInfo *info) {
- _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL, **route_domains = NULL;
+ _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL,
+ **route_domains = NULL, **link_dropins = NULL, **network_dropins = NULL;
_cleanup_free_ char *t = NULL, *network = NULL, *iaid = NULL, *duid = NULL,
*setup_state = NULL, *operational_state = NULL, *online_state = NULL, *activation_policy = NULL;
const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL,
(void) sd_network_link_get_ntp(info->ifindex, &ntp);
(void) sd_network_link_get_sip(info->ifindex, &sip);
(void) sd_network_link_get_network_file(info->ifindex, &network);
+ (void) sd_network_link_get_network_file_dropins(info->ifindex, &network_dropins);
(void) sd_network_link_get_carrier_bound_to(info->ifindex, &carrier_bound_to);
(void) sd_network_link_get_carrier_bound_by(info->ifindex, &carrier_bound_by);
(void) sd_network_link_get_activation_policy(info->ifindex, &activation_policy);
if (info->sd_device) {
+ const char *joined;
+
(void) sd_device_get_property_value(info->sd_device, "ID_NET_LINK_FILE", &link);
+
+ if (sd_device_get_property_value(info->sd_device, "ID_NET_LINK_FILE_DROPINS", &joined) >= 0) {
+ r = strv_split_full(&link_dropins, joined, ":", EXTRACT_CUNESCAPE);
+ if (r < 0)
+ return r;
+ }
+
(void) sd_device_get_property_value(info->sd_device, "ID_NET_DRIVER", &driver);
(void) sd_device_get_property_value(info->sd_device, "ID_PATH", &path);
(void) dhcp_lease_load(&lease, lease_file);
+ r = format_dropins(network_dropins);
+ if (r < 0)
+ return r;
+
+ if (strv_prepend(&network_dropins, network) < 0)
+ return log_oom();
+
+ r = format_dropins(link_dropins);
+ if (r < 0)
+ return r;
+
+ if (strv_prepend(&link_dropins, link) < 0)
+ return log_oom();
+
table = table_new("dot", "key", "value");
if (!table)
return log_oom();
TABLE_EMPTY,
TABLE_STRING, "Link File:",
TABLE_SET_ALIGN_PERCENT, 100,
- TABLE_STRING, strna(link),
+ TABLE_STRV, link_dropins ?: STRV_MAKE("n/a"),
TABLE_EMPTY,
TABLE_STRING, "Network File:",
- TABLE_STRING, strna(network),
+ TABLE_STRV, network_dropins ?: STRV_MAKE("n/a"),
TABLE_EMPTY,
TABLE_STRING, "State:");
if (r < 0)
(void) address_get(link, address, &existing);
- if (address->lifetime_valid_usec == 0)
+ if (address->lifetime_valid_usec == 0) {
+ if (consume_object)
+ address_free(address);
+
/* The requested address is outdated. Let's remove it. */
return address_remove_and_drop(existing);
+ }
if (!existing) {
_cleanup_(address_freep) Address *tmp = NULL;
/* Address=address/prefixlen */
r = in_addr_prefix_from_string_auto_internal(rvalue, PREFIXLEN_REFUSE, &f, &buffer, &prefixlen);
if (r == -ENOANO) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "An address '%s' is specified without prefix length. "
- "The behavior of parsing addresses without prefix length will be changed in the future release. "
- "Please specify prefix length explicitly.", rvalue);
-
- r = in_addr_prefix_from_string_auto_internal(rvalue, PREFIXLEN_LEGACY, &f, &buffer, &prefixlen);
+ r = in_addr_prefix_from_string_auto(rvalue, &f, &buffer, &prefixlen);
+ if (r >= 0)
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "An address '%s' is specified without prefix length. Assuming the prefix length is %u."
+ "Please specify the prefix length explicitly.", rvalue, prefixlen);
}
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid address '%s', ignoring assignment: %m", rvalue);
assert(route);
assert(link);
+ assert(link->network);
assert(link->dhcp_lease);
r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &server);
route->table = link_get_dhcp4_route_table(link);
if (route->mtu == 0)
route->mtu = link->network->dhcp_route_mtu;
+ if (route->quickack < 0)
+ route->quickack = link->network->dhcp_quickack;
if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
link->dhcp4_configured = false;
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set IP service type: %m");
}
+ if (link->network->dhcp_socket_priority_set) {
+ r = sd_dhcp_client_set_socket_priority(link->dhcp_client, link->network->dhcp_socket_priority);
+ if (r < 0)
+ return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set socket priority: %m");
+ }
+
if (link->network->dhcp_fallback_lease_lifetime > 0) {
r = sd_dhcp_client_set_fallback_lease_lifetime(link->dhcp_client, link->network->dhcp_fallback_lease_lifetime);
if (r < 0)
return 0;
}
+int config_parse_dhcp_socket_priority(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = ASSERT_PTR(data);
+ int a, r;
+
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ network->dhcp_socket_priority_set = false;
+ return 0;
+ }
+
+ r = safe_atoi(rvalue, &a);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse socket priority, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ network->dhcp_socket_priority_set = true;
+ network->dhcp_socket_priority = a;
+
+ return 0;
+}
+
int config_parse_dhcp_fallback_lease_lifetime(
const char *unit,
const char *filename,
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_client_identifier);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_ip_service_type);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_socket_priority);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_mud_url);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_fallback_lease_lifetime);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_label);
"DHCPv6 CLIENT: Failed to %s rapid commit: %m",
enable_disable(link->network->dhcp6_use_rapid_commit));
+ r = sd_dhcp6_client_set_send_release(client, link->network->dhcp6_send_release);
+ if (r < 0)
+ return log_link_debug_errno(link, r,
+ "DHCPv6 CLIENT: Failed to %s sending release message on stop: %m",
+ enable_disable(link->network->dhcp6_send_release));
+
link->dhcp6_client = TAKE_PTR(client);
return 0;
return json_build(ret, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("NetworkFile", network->filename),
+ JSON_BUILD_PAIR_STRV("NetworkFileDropins", network->dropins),
JSON_BUILD_PAIR_BOOLEAN("RequiredForOnline", network->required_for_online),
JSON_BUILD_PAIR("RequiredOperationalStateForOnline",
JSON_BUILD_ARRAY(JSON_BUILD_STRING(link_operstate_to_string(network->required_operstate_for_online.min)),
}
static int device_build_json(sd_device *device, JsonVariant **ret) {
- const char *link = NULL, *path = NULL, *vendor = NULL, *model = NULL;
+ _cleanup_strv_free_ char **link_dropins = NULL;
+ const char *link = NULL, *path = NULL, *vendor = NULL, *model = NULL, *joined;
+ int r;
assert(ret);
}
(void) sd_device_get_property_value(device, "ID_NET_LINK_FILE", &link);
+
+ if (sd_device_get_property_value(device, "ID_NET_LINK_FILE_DROPINS", &joined) >= 0) {
+ r = strv_split_full(&link_dropins, joined, ":", EXTRACT_CUNESCAPE);
+ if (r < 0)
+ return r;
+ }
+
(void) sd_device_get_property_value(device, "ID_PATH", &path);
if (sd_device_get_property_value(device, "ID_VENDOR_FROM_DATABASE", &vendor) < 0)
return json_build(ret, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING_NON_EMPTY("LinkFile", link),
+ JSON_BUILD_PAIR_STRV_NON_EMPTY("LinkFileDropins", link_dropins),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Path", path),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Vendor", vendor),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Model", model)));
log_link_debug(link, "Saved new link: ifindex=%i, iftype=%s(%u), kind=%s",
link->ifindex, strna(arphrd_to_name(link->iftype)), link->iftype, strna(link->kind));
+ /* If contained in this set, the link is wireless and the corresponding NL80211_CMD_NEW_INTERFACE
+ * message arrived too early. Request the wireless link information again.
+ */
+ if (set_remove(manager->new_wlan_ifindices, INT_TO_PTR(link->ifindex))) {
+ r = link_get_wlan_interface(link);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to get wireless interface, ignoring: %m");
+ }
+
*ret = TAKE_PTR(link);
return 0;
}
m->request_queue = ordered_set_free(m->request_queue);
m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref);
+ m->new_wlan_ifindices = set_free(m->new_wlan_ifindices);
m->links_by_name = hashmap_free(m->links_by_name);
m->links_by_hw_addr = hashmap_free(m->links_by_hw_addr);
m->links_by_dhcp_pd_subnet_prefix = hashmap_free(m->links_by_dhcp_pd_subnet_prefix);
bool manage_foreign_rules;
Set *dirty_links;
+ Set *new_wlan_ifindices;
char *state_file;
LinkOperationalState operational_state;
assert(route);
assert(link);
+ assert(link->network);
assert(rt);
r = sd_ndisc_router_get_address(rt, &router);
ndisc_set_route_priority(link, route);
if (!route->protocol_set)
route->protocol = RTPROT_RA;
+ if (route->quickack < 0)
+ route->quickack = link->network->ipv6_accept_ra_quickack;
is_new = route_get(NULL, link, route, NULL) < 0;
return log_link_error_errno(link, r, "Failed to get RA option type: %m");
switch (type) {
-
case SD_NDISC_OPTION_PREFIX_INFORMATION:
r = ndisc_router_process_prefix(link, rt);
- if (r < 0)
- return r;
break;
case SD_NDISC_OPTION_ROUTE_INFORMATION:
r = ndisc_router_process_route(link, rt);
- if (r < 0)
- return r;
break;
case SD_NDISC_OPTION_RDNSS:
r = ndisc_router_process_rdnss(link, rt);
- if (r < 0)
- return r;
break;
case SD_NDISC_OPTION_DNSSL:
r = ndisc_router_process_dnssl(link, rt);
- if (r < 0)
- return r;
break;
}
+ if (r < 0 && r != -EBADMSG)
+ return r;
}
}
assert(rt);
r = sd_ndisc_router_get_address(rt, &router);
+ if (r == -ENODATA) {
+ log_link_debug(link, "Received RA without router address, ignoring.");
+ return 0;
+ }
if (r < 0)
return log_link_error_errno(link, r, "Failed to get router address from RA: %m");
}
r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, ×tamp_usec);
+ if (r == -ENODATA) {
+ log_link_debug(link, "Received RA without timestamp, ignoring.");
+ return 0;
+ }
if (r < 0)
return r;
case SD_NDISC_EVENT_ROUTER:
r = ndisc_router_handler(link, rt);
- if (r < 0) {
+ if (r < 0 && r != -EBADMSG) {
link_enter_failed(link);
return;
}
DHCPv4.UseDomains, config_parse_dhcp_use_domains, AF_INET, 0
DHCPv4.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
DHCPv4.UseGateway, config_parse_tristate, 0, offsetof(Network, dhcp_use_gateway)
+DHCPv4.QuickAck, config_parse_bool, 0, offsetof(Network, dhcp_quickack)
DHCPv4.RequestOptions, config_parse_dhcp_request_options, AF_INET, 0
DHCPv4.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
DHCPv4.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
DHCPv4.DenyList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip)
DHCPv4.AllowList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_allow_listed_ip)
DHCPv4.IPServiceType, config_parse_dhcp_ip_service_type, 0, offsetof(Network, dhcp_ip_service_type)
+DHCPv4.SocketPriority, config_parse_dhcp_socket_priority, 0, 0
DHCPv4.SendOption, config_parse_dhcp_send_option, AF_INET, offsetof(Network, dhcp_client_send_options)
DHCPv4.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_client_send_vendor_options)
DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu)
DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid)
DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel)
+DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release)
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client)
IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 0
IPv6AcceptRA.RouteMetric, config_parse_ipv6_accept_ra_route_metric, 0, 0
+IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_quickack)
IPv6AcceptRA.RouterAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_router)
IPv6AcceptRA.RouterDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_router)
IPv6AcceptRA.PrefixAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_prefix)
.dhcp6_use_rapid_commit = true,
.dhcp6_duid.type = _DUID_TYPE_INVALID,
.dhcp6_client_start_mode = _DHCP6_CLIENT_START_MODE_INVALID,
+ .dhcp6_send_release = true,
.dhcp_pd = -1,
.dhcp_pd_announce = true,
CONFIG_PARSE_WARN,
network,
&network->stats_by_path,
- NULL);
+ &network->dropins);
if (r < 0)
return r; /* config_parse_many() logs internally. */
free(network->name);
free(network->filename);
free(network->description);
+ strv_free(network->dropins);
hashmap_free(network->stats_by_path);
/* conditions */
char *name;
char *filename;
+ char **dropins;
Hashmap *stats_by_path;
char *description;
uint16_t dhcp_client_port;
int dhcp_critical;
int dhcp_ip_service_type;
+ int dhcp_socket_priority;
+ bool dhcp_socket_priority_set;
bool dhcp_anonymize;
bool dhcp_send_hostname;
int dhcp_broadcast;
bool dhcp_use_mtu;
bool dhcp_use_routes;
int dhcp_use_gateway;
+ bool dhcp_quickack;
bool dhcp_use_timezone;
bool dhcp_use_hostname;
bool dhcp_use_6rd;
OrderedHashmap *dhcp6_client_send_vendor_options;
Set *dhcp6_request_options;
char *dhcp6_netlabel;
+ bool dhcp6_send_release;
/* DHCP Server Support */
bool dhcp_server;
bool ipv6_accept_ra_use_autonomous_prefix;
bool ipv6_accept_ra_use_onlink_prefix;
bool ipv6_accept_ra_use_mtu;
+ bool ipv6_accept_ra_quickack;
bool active_slave;
bool primary_slave;
DHCPUseDomains ipv6_accept_ra_use_domains;
(void) route_get(link->manager, link, route, &existing);
- if (route->lifetime_usec == 0)
+ if (route->lifetime_usec == 0) {
+ if (consume_object)
+ route_free(route);
+
/* The requested route is outdated. Let's remove it. */
return route_remove_and_drop(existing);
+ }
if (!existing) {
_cleanup_(route_freep) Route *tmp = NULL;
#include "alloc-util.h"
#include "dns-domain.h"
+#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
if (link->network) {
const char *online_state;
- bool space;
+ bool space = false;
online_state = link_online_state_to_string(link->online_state);
if (online_state)
fprintf(f, "NETWORK_FILE=%s\n", link->network->filename);
+ fputs("NETWORK_FILE_DROPINS=\"", f);
+ STRV_FOREACH(d, link->network->dropins) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = xescape(*d, ":");
+ if (!escaped)
+ return -ENOMEM;
+
+ fputs_with_space(f, escaped, ":", &space);
+ }
+ fputs("\"\n", f);
+
/************************************************************/
fputs("DNS=", f);
if (r < 0) {
log_debug_errno(r, "nl80211: received %s(%u) message for link '%"PRIu32"' we don't know about, ignoring.",
strna(nl80211_cmd_to_string(cmd)), cmd, ifindex);
+
+ /* The NL80211_CMD_NEW_INTERFACE message might arrive before RTM_NEWLINK, in which case a
+ * link will not have been created yet. Store the interface index such that the wireless
+ * properties of the link (such as wireless interface type) are queried again after the link
+ * is created.
+ */
+ if (cmd == NL80211_CMD_NEW_INTERFACE) {
+ r = set_ensure_put(&manager->new_wlan_ifindices, NULL, INT_TO_PTR(ifindex));
+ if (r < 0)
+ log_warning_errno(r, "Failed to add new wireless interface index to set, ignoring: %m");
+ } else if (cmd == NL80211_CMD_DEL_INTERFACE)
+ set_remove(manager->new_wlan_ifindices, INT_TO_PTR(ifindex));
+
return 0;
}
test_config_parse_address_one("", AF_INET, 0, NULL, 0);
test_config_parse_address_one("/", AF_INET, 0, NULL, 0);
test_config_parse_address_one("/8", AF_INET, 0, NULL, 0);
- test_config_parse_address_one("1.2.3.4", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 8);
+ test_config_parse_address_one("1.2.3.4", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32);
test_config_parse_address_one("1.2.3.4/0", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 0);
test_config_parse_address_one("1.2.3.4/1", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 1);
test_config_parse_address_one("1.2.3.4/2", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 2);
test_config_parse_address_one("", AF_INET6, 0, NULL, 0);
test_config_parse_address_one("/", AF_INET6, 0, NULL, 0);
test_config_parse_address_one("/8", AF_INET6, 0, NULL, 0);
- test_config_parse_address_one("::1", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0);
+ test_config_parse_address_one("::1", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128);
test_config_parse_address_one("::1/0", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0);
test_config_parse_address_one("::1/1", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 1);
test_config_parse_address_one("::1/2", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 2);
r = loop_device_make_by_path(
arg_image,
arg_read_only ? O_RDONLY : O_RDWR,
+ /* sector_size= */ UINT32_MAX,
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
LOCK_SH,
&loop);
if (r < 0)
return log_error_errno(r, "Failed to extract file name from '%s': %m", device);
- return make_filesystem(device, fstype, label, NULL, uuid, true, NULL);
+ return make_filesystem(device, fstype, label, NULL, uuid, true, 0, NULL);
}
DEFINE_MAIN_FUNCTION(run);
static FilterPartitionsType arg_filter_partitions_type = FILTER_PARTITIONS_NONE;
static sd_id128_t *arg_defer_partitions = NULL;
static size_t arg_n_defer_partitions = 0;
+static uint64_t arg_sector_size = 0;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
assert(context->end == UINT64_MAX);
assert(context->total == UINT64_MAX);
- /* libfdisk doesn't have an API to operate on arbitrary fds, hence reopen the fd going via the
- * /proc/self/fd/ magic path if we have an existing fd. Open the original file otherwise. */
- if (context->backing_fd < 0) {
- c = fdisk_new_context();
- if (!c)
- return log_oom();
+ c = fdisk_new_context();
+ if (!c)
+ return log_oom();
- r = fdisk_assign_device(c, context->node, arg_dry_run);
- } else
- r = fdisk_new_context_fd(context->backing_fd, arg_dry_run, &c);
+ if (arg_sector_size > 0)
+ r = fdisk_save_user_sector_size(c, /* phy= */ 0, arg_sector_size);
+ else {
+ uint32_t ssz;
+
+ if (context->backing_fd < 0) {
+ context->backing_fd = open(context->node, O_RDONLY|O_CLOEXEC);
+ if (context->backing_fd < 0)
+ return log_error_errno(errno, "Failed to open device '%s': %m", context->node);
+ }
+
+ /* Auto-detect sector size if not specified. */
+ r = probe_sector_size_prefer_ioctl(context->backing_fd, &ssz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to probe sector size of '%s': %m", context->node);
+ r = fdisk_save_user_sector_size(c, /* phy= */ 0, ssz);
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to set sector size: %m");
+
+ /* libfdisk doesn't have an API to operate on arbitrary fds, hence reopen the fd going via the
+ * /proc/self/fd/ magic path if we have an existing fd. Open the original file otherwise. */
+ r = fdisk_assign_device(
+ c,
+ context->backing_fd >= 0 ? FORMAT_PROC_FD_PATH(context->backing_fd) : context->node,
+ arg_dry_run);
if (r == -EINVAL && arg_size_auto) {
struct stat st;
if (S_ISREG(st.st_mode) && st.st_size == 0) {
/* User the fallback values if we have no better idea */
- context->sector_size = 512;
+ context->sector_size = arg_sector_size ?: 512;
context->grain_size = 4096;
return /* from_scratch = */ true;
}
_cleanup_free_ void *pubkey = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
size_t secret_size, blob_size, hash_size, pubkey_size = 0;
+ ssize_t base64_encoded_size;
uint16_t pcr_bank, primary_alg;
int keyslot;
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
- r = base64mem(secret, secret_size, &base64_encoded);
- if (r < 0)
- return log_error_errno(r, "Failed to base64 encode secret key: %m");
+ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
NULL,
VOLUME_KEY_SIZE,
base64_encoded,
- strlen(base64_encoded));
+ base64_encoded_size);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key: %m");
primary_alg,
blob, blob_size,
hash, hash_size,
+ NULL, 0, /* no salt because tpm2_seal has no pin */
0,
&v);
if (r < 0)
}
r = make_filesystem(partition_target_path(t), p->format, strempty(p->new_label), root,
- p->fs_uuid, arg_discard, NULL);
+ p->fs_uuid, arg_discard, context->sector_size, NULL);
if (r < 0)
return r;
return r;
}
- r = make_filesystem(d ? d->node : temp, p->format, strempty(p->new_label), root, fs_uuid, arg_discard, NULL);
+ r = make_filesystem(d ? d->node : temp, p->format, strempty(p->new_label), root, fs_uuid,
+ arg_discard, context->sector_size, NULL);
if (r < 0)
return r;
if (r < 0 && r != -ENOENT && !ERRNO_IS_PRIVILEGE(r))
return log_error_errno(r, "Failed to make loopback device of %s: %m", temp);
- r = make_filesystem(d ? d->node : temp, p->format, strempty(p->new_label), root, p->fs_uuid, arg_discard, NULL);
+ r = make_filesystem(d ? d->node : temp, p->format, strempty(p->new_label), root, p->fs_uuid,
+ arg_discard, context->sector_size, NULL);
if (r < 0)
return r;
" --defer-partitions=PARTITION1,PARTITION2,PARTITION3,…\n"
" Take partitions of the specified types into account\n"
" but don't populate them yet\n"
+ " --sector-size=SIZE Set the logical sector size for the image\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ARG_INCLUDE_PARTITIONS,
ARG_EXCLUDE_PARTITIONS,
ARG_DEFER_PARTITIONS,
+ ARG_SECTOR_SIZE,
};
static const struct option options[] = {
{ "include-partitions", required_argument, NULL, ARG_INCLUDE_PARTITIONS },
{ "exclude-partitions", required_argument, NULL, ARG_EXCLUDE_PARTITIONS },
{ "defer-partitions", required_argument, NULL, ARG_DEFER_PARTITIONS },
+ { "sector-size", required_argument, NULL, ARG_SECTOR_SIZE },
{}
};
break;
+ case ARG_SECTOR_SIZE:
+ r = parse_sector_size(optarg, &arg_sector_size);
+ if (r < 0)
+ return r;
+
+ break;
+
case '?':
return -EINVAL;
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "Failed to discover root block device.");
}
-static int resize_pt(int fd) {
+static int resize_pt(int fd, uint64_t sector_size) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
int r;
* possession of the enlarged backing file. For this it suffices to open the device with libfdisk and
* immediately write it again, with no changes. */
- r = fdisk_new_context_fd(fd, /* read_only= */ false, &c);
+ r = fdisk_new_context_fd(fd, /* read_only= */ false, sector_size, &c);
if (r < 0)
return log_error_errno(r, "Failed to open device '%s': %m", FORMAT_PROC_FD_PATH(fd));
const char *node, /* The primary way we access the disk image to operate on */
int *fd, /* An O_RDONLY fd referring to that inode */
const char *backing_file, /* If the above refers to a loopback device, the backing regular file for that, which we can grow */
- LoopDevice *loop_device) {
+ LoopDevice *loop_device,
+ uint64_t sector_size) {
_cleanup_close_ int writable_fd = -EBADF;
uint64_t current_size;
node, FORMAT_BYTES(current_size), FORMAT_BYTES(arg_size));
done:
- r = resize_pt(writable_fd);
+ r = resize_pt(writable_fd, sector_size);
if (r < 0)
return r;
context->node,
&context->backing_fd,
node_is_our_loop ? arg_image : NULL,
- node_is_our_loop ? loop_device : NULL);
+ node_is_our_loop ? loop_device : NULL,
+ context->sector_size);
if (r < 0)
return r;
}
context->node,
&context->backing_fd,
node_is_our_loop ? arg_image : NULL,
- node_is_our_loop ? loop_device : NULL);
+ node_is_our_loop ? loop_device : NULL,
+ context->sector_size);
if (r < 0)
return r;
static const char *arg_suffix = NULL;
static const char* const path_table[_SD_PATH_MAX] = {
- [SD_PATH_TEMPORARY] = "temporary",
- [SD_PATH_TEMPORARY_LARGE] = "temporary-large",
- [SD_PATH_SYSTEM_BINARIES] = "system-binaries",
- [SD_PATH_SYSTEM_INCLUDE] = "system-include",
- [SD_PATH_SYSTEM_LIBRARY_PRIVATE] = "system-library-private",
- [SD_PATH_SYSTEM_LIBRARY_ARCH] = "system-library-arch",
- [SD_PATH_SYSTEM_SHARED] = "system-shared",
- [SD_PATH_SYSTEM_CONFIGURATION_FACTORY] = "system-configuration-factory",
- [SD_PATH_SYSTEM_STATE_FACTORY] = "system-state-factory",
- [SD_PATH_SYSTEM_CONFIGURATION] = "system-configuration",
- [SD_PATH_SYSTEM_RUNTIME] = "system-runtime",
- [SD_PATH_SYSTEM_RUNTIME_LOGS] = "system-runtime-logs",
- [SD_PATH_SYSTEM_STATE_PRIVATE] = "system-state-private",
- [SD_PATH_SYSTEM_STATE_LOGS] = "system-state-logs",
- [SD_PATH_SYSTEM_STATE_CACHE] = "system-state-cache",
- [SD_PATH_SYSTEM_STATE_SPOOL] = "system-state-spool",
- [SD_PATH_USER_BINARIES] = "user-binaries",
- [SD_PATH_USER_LIBRARY_PRIVATE] = "user-library-private",
- [SD_PATH_USER_LIBRARY_ARCH] = "user-library-arch",
- [SD_PATH_USER_SHARED] = "user-shared",
- [SD_PATH_USER_CONFIGURATION] = "user-configuration",
- [SD_PATH_USER_RUNTIME] = "user-runtime",
- [SD_PATH_USER_STATE_CACHE] = "user-state-cache",
- [SD_PATH_USER] = "user",
- [SD_PATH_USER_DOCUMENTS] = "user-documents",
- [SD_PATH_USER_MUSIC] = "user-music",
- [SD_PATH_USER_PICTURES] = "user-pictures",
- [SD_PATH_USER_VIDEOS] = "user-videos",
- [SD_PATH_USER_DOWNLOAD] = "user-download",
- [SD_PATH_USER_PUBLIC] = "user-public",
- [SD_PATH_USER_TEMPLATES] = "user-templates",
- [SD_PATH_USER_DESKTOP] = "user-desktop",
- [SD_PATH_SEARCH_BINARIES] = "search-binaries",
- [SD_PATH_SEARCH_BINARIES_DEFAULT] = "search-binaries-default",
- [SD_PATH_SEARCH_LIBRARY_PRIVATE] = "search-library-private",
- [SD_PATH_SEARCH_LIBRARY_ARCH] = "search-library-arch",
- [SD_PATH_SEARCH_SHARED] = "search-shared",
- [SD_PATH_SEARCH_CONFIGURATION_FACTORY] = "search-configuration-factory",
- [SD_PATH_SEARCH_STATE_FACTORY] = "search-state-factory",
- [SD_PATH_SEARCH_CONFIGURATION] = "search-configuration",
-
- [SD_PATH_SYSTEMD_UTIL] = "systemd-util",
- [SD_PATH_SYSTEMD_SYSTEM_UNIT] = "systemd-system-unit",
- [SD_PATH_SYSTEMD_SYSTEM_PRESET] = "systemd-system-preset",
- [SD_PATH_SYSTEMD_SYSTEM_CONF] = "systemd-system-conf",
- [SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT] = "systemd-search-system-unit",
- [SD_PATH_SYSTEMD_SYSTEM_GENERATOR] = "systemd-system-generator",
- [SD_PATH_SYSTEMD_SEARCH_SYSTEM_GENERATOR] = "systemd-search-system-generator",
- [SD_PATH_SYSTEMD_USER_UNIT] = "systemd-user-unit",
- [SD_PATH_SYSTEMD_USER_PRESET] = "systemd-user-preset",
- [SD_PATH_SYSTEMD_USER_CONF] = "systemd-user-conf",
- [SD_PATH_SYSTEMD_SEARCH_USER_UNIT] = "systemd-search-user-unit",
- [SD_PATH_SYSTEMD_SEARCH_USER_GENERATOR] = "systemd-search-user-generator",
- [SD_PATH_SYSTEMD_USER_GENERATOR] = "systemd-user-generator",
- [SD_PATH_SYSTEMD_SLEEP] = "systemd-sleep",
- [SD_PATH_SYSTEMD_SHUTDOWN] = "systemd-shutdown",
-
- [SD_PATH_TMPFILES] = "tmpfiles",
- [SD_PATH_SYSUSERS] = "sysusers",
- [SD_PATH_SYSCTL] = "sysctl",
- [SD_PATH_BINFMT] = "binfmt",
- [SD_PATH_MODULES_LOAD] = "modules-load",
- [SD_PATH_CATALOG] = "catalog",
-
- [SD_PATH_SYSTEMD_SEARCH_NETWORK] = "systemd-search-network",
+ [SD_PATH_TEMPORARY] = "temporary",
+ [SD_PATH_TEMPORARY_LARGE] = "temporary-large",
+ [SD_PATH_SYSTEM_BINARIES] = "system-binaries",
+ [SD_PATH_SYSTEM_INCLUDE] = "system-include",
+ [SD_PATH_SYSTEM_LIBRARY_PRIVATE] = "system-library-private",
+ [SD_PATH_SYSTEM_LIBRARY_ARCH] = "system-library-arch",
+ [SD_PATH_SYSTEM_SHARED] = "system-shared",
+ [SD_PATH_SYSTEM_CONFIGURATION_FACTORY] = "system-configuration-factory",
+ [SD_PATH_SYSTEM_STATE_FACTORY] = "system-state-factory",
+ [SD_PATH_SYSTEM_CONFIGURATION] = "system-configuration",
+ [SD_PATH_SYSTEM_RUNTIME] = "system-runtime",
+ [SD_PATH_SYSTEM_RUNTIME_LOGS] = "system-runtime-logs",
+ [SD_PATH_SYSTEM_STATE_PRIVATE] = "system-state-private",
+ [SD_PATH_SYSTEM_STATE_LOGS] = "system-state-logs",
+ [SD_PATH_SYSTEM_STATE_CACHE] = "system-state-cache",
+ [SD_PATH_SYSTEM_STATE_SPOOL] = "system-state-spool",
+ [SD_PATH_USER_BINARIES] = "user-binaries",
+ [SD_PATH_USER_LIBRARY_PRIVATE] = "user-library-private",
+ [SD_PATH_USER_LIBRARY_ARCH] = "user-library-arch",
+ [SD_PATH_USER_SHARED] = "user-shared",
+ [SD_PATH_USER_CONFIGURATION] = "user-configuration",
+ [SD_PATH_USER_RUNTIME] = "user-runtime",
+ [SD_PATH_USER_STATE_CACHE] = "user-state-cache",
+ [SD_PATH_USER] = "user",
+ [SD_PATH_USER_DOCUMENTS] = "user-documents",
+ [SD_PATH_USER_MUSIC] = "user-music",
+ [SD_PATH_USER_PICTURES] = "user-pictures",
+ [SD_PATH_USER_VIDEOS] = "user-videos",
+ [SD_PATH_USER_DOWNLOAD] = "user-download",
+ [SD_PATH_USER_PUBLIC] = "user-public",
+ [SD_PATH_USER_TEMPLATES] = "user-templates",
+ [SD_PATH_USER_DESKTOP] = "user-desktop",
+ [SD_PATH_SEARCH_BINARIES] = "search-binaries",
+ [SD_PATH_SEARCH_BINARIES_DEFAULT] = "search-binaries-default",
+ [SD_PATH_SEARCH_LIBRARY_PRIVATE] = "search-library-private",
+ [SD_PATH_SEARCH_LIBRARY_ARCH] = "search-library-arch",
+ [SD_PATH_SEARCH_SHARED] = "search-shared",
+ [SD_PATH_SEARCH_CONFIGURATION_FACTORY] = "search-configuration-factory",
+ [SD_PATH_SEARCH_STATE_FACTORY] = "search-state-factory",
+ [SD_PATH_SEARCH_CONFIGURATION] = "search-configuration",
+
+ [SD_PATH_SYSTEMD_UTIL] = "systemd-util",
+ [SD_PATH_SYSTEMD_SYSTEM_UNIT] = "systemd-system-unit",
+ [SD_PATH_SYSTEMD_SYSTEM_PRESET] = "systemd-system-preset",
+ [SD_PATH_SYSTEMD_SYSTEM_CONF] = "systemd-system-conf",
+ [SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT] = "systemd-search-system-unit",
+ [SD_PATH_SYSTEMD_SYSTEM_GENERATOR] = "systemd-system-generator",
+ [SD_PATH_SYSTEMD_SEARCH_SYSTEM_GENERATOR] = "systemd-search-system-generator",
+ [SD_PATH_SYSTEMD_USER_UNIT] = "systemd-user-unit",
+ [SD_PATH_SYSTEMD_USER_PRESET] = "systemd-user-preset",
+ [SD_PATH_SYSTEMD_USER_CONF] = "systemd-user-conf",
+ [SD_PATH_SYSTEMD_SEARCH_USER_UNIT] = "systemd-search-user-unit",
+ [SD_PATH_SYSTEMD_SEARCH_USER_GENERATOR] = "systemd-search-user-generator",
+ [SD_PATH_SYSTEMD_USER_GENERATOR] = "systemd-user-generator",
+ [SD_PATH_SYSTEMD_SLEEP] = "systemd-sleep",
+ [SD_PATH_SYSTEMD_SHUTDOWN] = "systemd-shutdown",
+
+ [SD_PATH_TMPFILES] = "tmpfiles",
+ [SD_PATH_SYSUSERS] = "sysusers",
+ [SD_PATH_SYSCTL] = "sysctl",
+ [SD_PATH_BINFMT] = "binfmt",
+ [SD_PATH_MODULES_LOAD] = "modules-load",
+ [SD_PATH_CATALOG] = "catalog",
+
+ [SD_PATH_SYSTEMD_SEARCH_NETWORK] = "systemd-search-network",
+
+ [SD_PATH_SYSTEMD_SYSTEM_ENVIRONMENT_GENERATOR] = "systemd-system-environment-generator",
+ [SD_PATH_SYSTEMD_USER_ENVIRONMENT_GENERATOR] = "systemd-user-environment-generator",
+ [SD_PATH_SYSTEMD_SEARCH_SYSTEM_ENVIRONMENT_GENERATOR] = "systemd-search-system-environment-generator",
+ [SD_PATH_SYSTEMD_SEARCH_USER_ENVIRONMENT_GENERATOR] = "systemd-search-user-environment-generator",
};
static int list_homes(void) {
assert(path);
- r = loop_device_make_by_path(path, O_RDONLY, LO_FLAGS_PARTSCAN, LOCK_SH, &d);
+ r = loop_device_make_by_path(path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d);
if (r == -EISDIR) {
_cleanup_free_ char *image_name = NULL;
}
int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) {
- assert(p);
-
_cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
+ _cleanup_free_ char *t = NULL;
const void *d;
- char *t;
uint8_t c;
int r;
+ assert(p);
+
r = dns_packet_read_uint8(p, &c, NULL);
if (r < 0)
return r;
if (r < 0)
return r;
- if (memchr(d, 0, c))
- return -EBADMSG;
-
- t = memdup_suffix0(d, c);
- if (!t)
- return -ENOMEM;
+ r = make_cstring(d, c, MAKE_CSTRING_REFUSE_TRAILING_NUL, &t);
+ if (r < 0)
+ return r;
- if (!utf8_is_valid(t)) {
- free(t);
+ if (!utf8_is_valid(t))
return -EBADMSG;
- }
- *ret = t;
+ *ret = TAKE_PTR(t);
if (start)
*start = rewinder.saved_rindex;
case DNS_SEARCH_DOMAIN_LINK:
assert(d->link);
- LIST_FIND_TAIL(domains, d, tail);
+ tail = LIST_FIND_TAIL(domains, d);
LIST_REMOVE(domains, d->link->search_domains, d);
LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d);
break;
case DNS_SEARCH_DOMAIN_SYSTEM:
- LIST_FIND_TAIL(domains, d, tail);
+ tail = LIST_FIND_TAIL(domains, d);
LIST_REMOVE(domains, d->manager->search_domains, d);
LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d);
break;
case DNS_SERVER_LINK:
assert(s->link);
- LIST_FIND_TAIL(servers, s, tail);
+ tail = LIST_FIND_TAIL(servers, s);
LIST_REMOVE(servers, s->link->dns_servers, s);
LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s);
break;
case DNS_SERVER_SYSTEM:
- LIST_FIND_TAIL(servers, s, tail);
+ tail = LIST_FIND_TAIL(servers, s);
LIST_REMOVE(servers, s->manager->dns_servers, s);
LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s);
break;
case DNS_SERVER_FALLBACK:
- LIST_FIND_TAIL(servers, s, tail);
+ tail = LIST_FIND_TAIL(servers, s);
LIST_REMOVE(servers, s->manager->fallback_dns_servers, s);
LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s);
break;
if (r < 0)
return r;
+ CLEANUP_ERASE(buffer);
+
pollfd[POLL_SOCKET].fd = fd;
pollfd[POLL_SOCKET].events = POLLIN;
pollfd[POLL_INOTIFY].fd = notify;
else
timeout = USEC_INFINITY;
- if (flag_file && access(flag_file, F_OK) < 0) {
- r = -errno;
- goto finish;
- }
+ if (flag_file && access(flag_file, F_OK) < 0)
+ return -errno;
r = ppoll_usec(pollfd, notify >= 0 ? 2 : 1, timeout);
if (r == -EINTR)
continue;
if (r < 0)
- goto finish;
- if (r == 0) {
- r = -ETIME;
- goto finish;
- }
+ return r;
+ if (r == 0)
+ return -ETIME;
if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
(void) flush_fd(notify);
if (ERRNO_IS_TRANSIENT(errno))
continue;
- r = -errno;
- goto finish;
- }
- if (k == 0) {
- r = -EIO;
- goto finish;
+ return -errno;
}
+ if (k == 0)
+ return -EIO;
p += k;
* with a normal password request */
packet = mfree(packet);
- if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
- r = -ENOMEM;
- goto finish;
- }
+ if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
+ return -ENOMEM;
r = loop_write(fd, packet, n+1, true);
if (r < 0)
- goto finish;
+ return r;
flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
p = 0;
}
/* No password, because UI not shown */
- r = -ENOENT;
- goto finish;
+ return -ENOENT;
} else if (IN_SET(buffer[0], 2, 9)) {
uint32_t size;
memcpy(&size, buffer+1, sizeof(size));
size = le32toh(size);
- if (size + 5 > sizeof(buffer)) {
- r = -EIO;
- goto finish;
- }
+ if (size + 5 > sizeof(buffer))
+ return -EIO;
if (p-5 < size)
continue;
l = strv_parse_nulstr(buffer + 5, size);
- if (!l) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!l)
+ return -ENOMEM;
*ret = l;
break;
- } else {
+ } else
/* Unknown packet */
- r = -EIO;
- goto finish;
- }
+ return -EIO;
}
- r = 0;
-
-finish:
- explicit_bzero_safe(buffer, sizeof(buffer));
- return r;
+ return 0;
}
#define NO_ECHO "(no echo) "
return -errno;
}
+ CLEANUP_ERASE(passphrase);
+
/* If the caller didn't specify a TTY, then use the controlling tty, if we can. */
if (ttyfd < 0)
ttyfd = cttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC);
}
x = strndup(passphrase, p);
- explicit_bzero_safe(passphrase, sizeof(passphrase));
if (!x) {
r = -ENOMEM;
goto finish;
goto finish;
}
+ CLEANUP_ERASE(passphrase);
+
cmsg_close_all(&msghdr);
if (n == 0) {
l = strv_new("");
else
l = strv_parse_nulstr(passphrase+1, n-1);
- explicit_bzero_safe(passphrase, n);
if (!l) {
r = -ENOMEM;
goto finish;
if (r < 0)
return r;
+ r = btrfs_forget_device(devname);
+ if (r < 0 && r != -ENOENT)
+ log_debug_errno(r, "Failed to forget btrfs device %s, ignoring: %m", devname);
+
r = block_device_remove_partition(fd, devname, nr);
if (r == -ENODEV) {
log_debug("Kernel removed partition %s before us, ignoring", devname);
return 0;
}
+
+int blockdev_get_sector_size(int fd, uint32_t *ret) {
+ int ssz = 0;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ if (ioctl(fd, BLKSSZGET, &ssz) < 0)
+ return -errno;
+ if (ssz <= 0) /* make sure the field is initialized */
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Block device reported invalid sector size %i.", ssz);
+
+ *ret = ssz;
+ return 0;
+}
int block_device_remove_all_partitions(sd_device *dev, int fd);
int block_device_has_partitions(sd_device *dev);
int blockdev_reread_partition_table(sd_device *dev);
+
+int blockdev_get_sector_size(int fd, uint32_t *ret);
printf(" architecture: %s\n", e->architecture);
if (e->kernel)
boot_entry_file_list("linux", e->root, e->kernel, &status);
+ if (e->efi)
+ boot_entry_file_list("efi", e->root, e->efi, &status);
STRV_FOREACH(s, e->initrd)
boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
return -ENXIO;
}
+
+int btrfs_forget_device(const char *path) {
+ _cleanup_close_ int control_fd = -EBADF;
+ struct btrfs_ioctl_vol_args args = {};
+
+ assert(path);
+
+ if (strlen(path) > BTRFS_PATH_NAME_MAX)
+ return -E2BIG;
+
+ strcpy(args.name, path);
+
+ control_fd = open("/dev/btrfs-control", O_RDWR|O_CLOEXEC);
+ if (control_fd < 0)
+ return -errno;
+
+ return RET_NERRNO(ioctl(control_fd, BTRFS_IOC_FORGET_DEV, &args));
+}
return S_ISDIR(st->st_mode) && st->st_ino == 256;
}
+
+int btrfs_forget_device(const char *path);
#include "sd-id128.h"
#include "blockdev-util.h"
+#include "capability-util.h"
#include "chattr-util.h"
#include "constants.h"
#include "creds-util.h"
void **ret_data,
size_t *ret_size) {
- struct credential_host_secret_format buf;
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
assert(dfd >= 0);
assert(fn);
- fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
+ /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the
+ * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH
+ * capability when it uses the AT_EMPTY_PATH flag. */
+ if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) {
+ fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
+ if (fd < 0)
+ log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
+ }
if (fd < 0) {
- log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
-
if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0)
return -ENOMEM;
if (r < 0)
log_debug_errno(r, "Failed to set file attributes for secrets file, ignoring: %m");
- buf = (struct credential_host_secret_format) {
+ struct credential_host_secret_format buf = {
.machine_id = machine_id,
};
+ CLEANUP_ERASE(buf);
+
r = crypto_random_bytes(buf.data, sizeof(buf.data));
if (r < 0)
- goto finish;
+ goto fail;
r = loop_write(fd, &buf, sizeof(buf), false);
if (r < 0)
- goto finish;
+ goto fail;
if (fsync(fd) < 0) {
r = -errno;
- goto finish;
+ goto fail;
}
warn_not_encrypted(fd, flags, dirname, fn);
if (t) {
r = rename_noreplace(dfd, t, dfd, fn);
if (r < 0)
- goto finish;
+ goto fail;
t = mfree(t);
} else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) {
r = -errno;
- goto finish;
+ goto fail;
}
if (fsync(dfd) < 0) {
r = -errno;
- goto finish;
+ goto fail;
}
if (ret_data) {
copy = memdup(buf.data, sizeof(buf.data));
if (!copy) {
r = -ENOMEM;
- goto finish;
+ goto fail;
}
*ret_data = copy;
if (ret_size)
*ret_size = sizeof(buf.data);
- r = 0;
+ return 0;
-finish:
+fail:
if (t && unlinkat(dfd, t, 0) < 0)
log_debug_errno(errno, "Failed to remove temporary credential key: %m");
- explicit_bzero_safe(&buf, sizeof(buf));
return r;
}
#if HAVE_TPM2
bool try_tpm2;
- if (sd_id128_equal(with_key, _CRED_AUTO)) {
- /* If automatic mode is selected and we are running in a container, let's not try TPM2. OTOH
- * if user picks TPM2 explicitly, let's always honour the request and try. */
-
- r = detect_container();
- if (r < 0)
- log_debug_errno(r, "Failed to determine whether we are running in a container, ignoring: %m");
- else if (r > 0)
- log_debug("Running in container, not attempting to use TPM2.");
-
- try_tpm2 = r <= 0;
- } else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) {
- /* If automatic mode for initrds is selected, we'll use the TPM2 key if the firmware does it,
- * otherwise we'll use a fixed key */
+ if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
+ /* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a
+ * container tpm2_support will detect this, and will return a different flag combination of
+ * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */
- try_tpm2 = efi_has_tpm2();
+ try_tpm2 = tpm2_support() == TPM2_SUPPORT_FULL;
if (!try_tpm2)
- log_debug("Firmware lacks TPM2 support, not attempting to use TPM2.");
+ log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2.");
} else
try_tpm2 = sd_id128_in_set(with_key,
CRED_AES256_GCM_BY_TPM2_HMAC,
&tpm2_primary_alg);
if (r < 0) {
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
- log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
+ log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
else if (!sd_id128_equal(with_key, _CRED_AUTO))
return r;
if (le32toh(m->name_size) > 0) {
_cleanup_free_ char *embedded_name = NULL;
- if (memchr(m->name, 0, le32toh(m->name_size)))
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name contains NUL byte, refusing.");
-
- embedded_name = memdup_suffix0(m->name, le32toh(m->name_size));
- if (!embedded_name)
- return log_oom();
+ r = make_cstring(m->name, le32toh(m->name_size), MAKE_CSTRING_REFUSE_TRAILING_NUL, &embedded_name);
+ if (r < 0)
+ return log_error_errno(r, "Unable to convert embedded credential name to C string: %m");
if (!credential_name_valid(embedded_name))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name is not valid, refusing.");
static int extract_pretty(const char *path, const char *suffix, char **ret) {
_cleanup_free_ char *name = NULL;
const char *p;
- size_t n;
assert(path);
assert(ret);
p = last_path_component(path);
- n = strcspn(p, "/");
- name = strndup(p, n);
+ name = strdupcspn(p, "/");
if (!name)
return -ENOMEM;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
- r = loop_device_make_by_path(i->path, O_RDONLY, LO_FLAGS_PARTSCAN, LOCK_SH, &d);
+ r = loop_device_make_by_path(i->path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d);
if (r < 0)
return r;
#include "ask-password-api.h"
#include "blkid-util.h"
#include "blockdev-util.h"
+#include "btrfs-util.h"
#include "chase-symlinks.h"
#include "conf-files.h"
#include "constants.h"
#include "raw-clone.h"
#include "resize-fs.h"
#include "signal-util.h"
+#include "sparse-endian.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-table.h"
return false;
}
+int probe_sector_size(int fd, uint32_t *ret) {
+
+ struct gpt_header {
+ char signature[8];
+ le32_t revision;
+ le32_t header_size;
+ le32_t crc32;
+ le32_t reserved;
+ le64_t my_lba;
+ le64_t alternate_lba;
+ le64_t first_usable_lba;
+ le64_t last_usable_lba;
+ sd_id128_t disk_guid;
+ le64_t partition_entry_lba;
+ le32_t number_of_partition_entries;
+ le32_t size_of_partition_entry;
+ le32_t partition_entry_array_crc32;
+ } _packed_;
+
+ /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching
+ * for the GPT headers at the relevant byte offsets */
+
+ assert_cc(sizeof(struct gpt_header) == 92);
+
+ /* We expect a sector size in the range 512…4096. The GPT header is located in the second
+ * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must
+ * read with granularity of the largest sector size we care about. Which means 8K. */
+ uint8_t sectors[2 * 4096];
+ uint32_t found = 0;
+ ssize_t n;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ n = pread(fd, sectors, sizeof(sectors), 0);
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(sectors)) /* too short? */
+ goto not_found;
+
+ /* Let's see if we find the GPT partition header with various expected sector sizes */
+ for (uint32_t sz = 512; sz <= 4096; sz <<= 1) {
+ struct gpt_header *p;
+
+ assert(sizeof(sectors) >= sz * 2);
+ p = (struct gpt_header*) (sectors + sz);
+
+ if (memcmp(p->signature, (const char[8]) { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, 8) != 0)
+ continue;
+
+ if (le32toh(p->revision) != UINT32_C(0x00010000)) /* the only known revision of the spec: 1.0 */
+ continue;
+
+ if (le32toh(p->header_size) < sizeof(struct gpt_header))
+ continue;
+
+ if (le32toh(p->header_size) > 4096) /* larger than a sector? something is off… */
+ continue;
+
+ if (le64toh(p->my_lba) != 1) /* this sector must claim to be at sector offset 1 */
+ continue;
+
+ if (found != 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+ "Detected valid partition table at offsets matching multiple sector sizes, refusing.");
+
+ found = sz;
+ }
+
+ if (found != 0) {
+ log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", found);
+ *ret = found;
+ return 1; /* indicate we *did* find it */
+ }
+
+not_found:
+ log_debug("Couldn't find any partition table to derive sector size of.");
+ *ret = 512; /* pick the traditional default */
+ return 0; /* indicate we didn't find it */
+}
+
+int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret) {
+ struct stat st;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ /* Just like probe_sector_size(), but if we are looking at a block device, will use the already
+ * configured sector size rather than probing by contents */
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if (S_ISBLK(st.st_mode))
+ return blockdev_get_sector_size(fd, ret);
+
+ return probe_sector_size(fd, ret);
+}
+
int probe_filesystem_full(
int fd,
const char *path,
assert(!verity || verity->root_hash_sig || verity->root_hash_sig_size == 0);
assert(!verity || (verity->root_hash || !verity->root_hash_sig));
assert(!((flags & DISSECT_IMAGE_GPT_ONLY) && (flags & DISSECT_IMAGE_NO_PARTITION_TABLE)));
+ assert(m->sector_size > 0);
/* Probes a disk image, and returns information about what it found in *ret.
*
if (r != 0)
return errno_or_else(ENOMEM);
+ errno = 0;
+ r = blkid_probe_set_sectorsize(b, m->sector_size);
+ if (r != 0)
+ return errno_or_else(EIO);
+
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);
if (r < 0)
return r;
+ r = probe_sector_size(fd, &m->sector_size);
+ if (r < 0)
+ return r;
+
r = dissect_image(m, fd, path, verity, mount_options, flags);
if (r < 0)
return r;
DecryptedPartition *p = d->decrypted + i;
if (p->device && p->name && !p->relinquished) {
+ _cleanup_free_ char *node = NULL;
+
+ node = path_join("/dev/mapper", p->name);
+ if (node) {
+ r = btrfs_forget_device(node);
+ if (r < 0 && r != -ENOENT)
+ log_debug_errno(r, "Failed to forget btrfs device %s, ignoring: %m", node);
+ } else
+ log_oom_debug();
+
/* Let's deactivate lazily, as the dm volume may be already/still used by other processes. */
r = sym_crypt_deactivate_by_name(p->device, p->name, CRYPT_DEACTIVATE_DEFERRED);
if (r < 0)
return r;
m->loop = loop_device_ref(loop);
+ m->sector_size = m->loop->sector_size;
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, flags);
if (r < 0)
r = loop_device_make_by_path(
image,
FLAGS_SET(flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR,
+ /* sector_size= */ UINT32_MAX,
FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
LOCK_SH,
&d);
* accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */
r = loop_device_make_by_path(
src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
- -1,
+ /* open_flags= */ -1,
+ /* sector_size= */ UINT32_MAX,
verity.data_path ? 0 : LO_FLAGS_PARTSCAN,
LOCK_SH,
&loop_device);
DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX];
DecryptedImage *decrypted_image;
+ uint32_t sector_size;
+
/* Meta information extracted from /etc/os-release and similar */
char *image_name;
sd_id128_t image_uuid;
int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
int dissect_fstype_ok(const char *fstype);
+
+int probe_sector_size(int fd, uint32_t *ret);
+int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret);
#include "alloc-util.h"
#include "efi-loader.h"
+#include "env-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
#include "strv.h"
+#include "tpm-pcr.h"
#include "utf8.h"
#if ENABLE_EFI
return 0;
}
+int efi_stub_measured(void) {
+ _cleanup_free_ char *pcr_string = NULL;
+ unsigned pcr_nr;
+ int r;
+
+ /* Checks if we are booted on a kernel with sd-stub which measured the kernel into PCR 11. Or in
+ * other words, if we are running on a TPM enabled UKI.
+ *
+ * Returns == 0 and > 0 depending on the result of the test. Returns -EREMOTE if we detected a stub
+ * being used, but it measured things into a different PCR than we are configured for in
+ * userspace. (i.e. we expect PCR 11 being used for this by both sd-stub and us) */
+
+ r = getenv_bool_secure("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test,
+ * for debugging purposes */
+ if (r >= 0)
+ return r;
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_FORCE_MEASURE, ignoring: %m");
+
+ if (!is_efi_boot())
+ return 0;
+
+ r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = safe_atou(pcr_string, &pcr_nr);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string);
+ if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE)
+ return log_debug_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);
+
+ return 1;
+}
+
int efi_loader_get_config_timeout_one_shot(usec_t *ret) {
_cleanup_free_ char *v = NULL;
static struct stat cache_stat = {};
int efi_loader_get_features(uint64_t *ret);
int efi_stub_get_features(uint64_t *ret);
+int efi_stub_measured(void);
+
int efi_loader_get_config_timeout_one_shot(usec_t *ret);
int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat);
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
+ CLEANUP_ERASE(ecmd);
+
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
need_update = true;
}
- if (!need_update) {
- explicit_bzero_safe(&ecmd, sizeof(ecmd));
+ if (!need_update)
return 0;
- }
ecmd.cmd = ETHTOOL_SWOL;
- r = RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
-
- explicit_bzero_safe(&ecmd, sizeof(ecmd));
- return r;
+ return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
}
int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring) {
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "dissect-image.h"
#include "fd-util.h"
#include "fdisk-util.h"
#if HAVE_LIBFDISK
-int fdisk_new_context_fd(int fd, bool read_only, struct fdisk_context **ret) {
+int fdisk_new_context_fd(
+ int fd,
+ bool read_only,
+ uint32_t sector_size,
+ struct fdisk_context **ret) {
+
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
int r;
if (!c)
return -ENOMEM;
+ if (sector_size == UINT32_MAX) {
+ r = probe_sector_size_prefer_ioctl(fd, §or_size);
+ if (r < 0)
+ return r;
+ }
+
+ if (sector_size != 0) {
+ r = fdisk_save_user_sector_size(c, /* phy= */ 0, sector_size);
+ if (r < 0)
+ return r;
+ }
+
r = fdisk_assign_device(c, FORMAT_PROC_FD_PATH(fd), read_only);
if (r < 0)
return r;
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_parttype*, fdisk_unref_parttype, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_table*, fdisk_unref_table, NULL);
-int fdisk_new_context_fd(int fd, bool read_only, struct fdisk_context **ret);
+int fdisk_new_context_fd(int fd, bool read_only, uint32_t sector_size, struct fdisk_context **ret);
int fdisk_partition_get_uuid_as_id128(struct fdisk_partition *p, sd_id128_t *ret);
int fdisk_partition_get_type_as_id128(struct fdisk_partition *p, sd_id128_t *ret);
case TABLE_TIMESTAMP:
case TABLE_TIMESTAMP_UTC:
case TABLE_TIMESTAMP_RELATIVE:
+ case TABLE_TIMESTAMP_DATE:
case TABLE_TIMESPAN:
case TABLE_TIMESPAN_MSEC:
return sizeof(usec_t);
case TABLE_TIMESTAMP:
case TABLE_TIMESTAMP_UTC:
case TABLE_TIMESTAMP_RELATIVE:
+ case TABLE_TIMESTAMP_DATE:
case TABLE_TIMESPAN:
case TABLE_TIMESPAN_MSEC:
buffer.usec = va_arg(ap, usec_t);
case TABLE_TIMESTAMP:
case TABLE_TIMESTAMP_UTC:
case TABLE_TIMESTAMP_RELATIVE:
+ case TABLE_TIMESTAMP_DATE:
return CMP(a->timestamp, b->timestamp);
case TABLE_TIMESPAN:
case TABLE_TIMESTAMP:
case TABLE_TIMESTAMP_UTC:
- case TABLE_TIMESTAMP_RELATIVE: {
+ case TABLE_TIMESTAMP_RELATIVE:
+ case TABLE_TIMESTAMP_DATE: {
_cleanup_free_ char *p = NULL;
char *ret;
ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
else if (d->type == TABLE_TIMESTAMP_UTC)
ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
+ else if (d->type == TABLE_TIMESTAMP_DATE)
+ ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
else
ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
if (!ret)
case TABLE_TIMESTAMP:
case TABLE_TIMESTAMP_UTC:
case TABLE_TIMESTAMP_RELATIVE:
+ case TABLE_TIMESTAMP_DATE:
if (d->timestamp == USEC_INFINITY)
return json_variant_new_null(ret);
TABLE_TIMESTAMP,
TABLE_TIMESTAMP_UTC,
TABLE_TIMESTAMP_RELATIVE,
+ TABLE_TIMESTAMP_DATE,
TABLE_TIMESPAN,
TABLE_TIMESPAN_MSEC,
TABLE_SIZE,
return 0;
}
-int generator_add_symlink(const char *dir, const char *dst, const char *dep_type, const char *src) {
- _cleanup_free_ char *bn = NULL;
- const char *from, *to;
+
+int generator_add_symlink_full(
+ const char *dir,
+ const char *dst,
+ const char *dep_type,
+ const char *src,
+ const char *instance) {
+
+ _cleanup_free_ char *dn = NULL, *fn = NULL, *instantiated = NULL, *to = NULL, *from = NULL;
int r;
- /* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute)
- * or ../<src> (otherwise). */
+ assert(dir);
+ assert(dst);
+ assert(dep_type);
+ assert(src);
- r = path_extract_filename(src, &bn);
+ /* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). If
+ * <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
+
+ r = path_extract_directory(src, &dn);
+ if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */
+ return log_error_errno(r, "Failed to extract directory name from '%s': %m", src);
+
+ r = path_extract_filename(src, &fn);
if (r < 0)
- return log_error_errno(r, "Failed to extract filename from '%s': %m", src);
+ return log_error_errno(r, "Failed to extract file name from '%s': %m", src);
+ if (r == O_DIRECTORY)
+ return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Expected path to regular file name, but got '%s', refusing.", src);
- from = path_is_absolute(src) ? src : strjoina("../", src);
- to = strjoina(dir, "/", dst, ".", dep_type, "/", bn);
+ if (instance) {
+ r = unit_name_replace_instance(fn, instance, &instantiated);
+ if (r < 0)
+ return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance);
+ }
+
+ from = path_join(dn ?: "..", fn);
+ if (!from)
+ return log_oom();
+
+ to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
+ if (!to)
+ return log_oom();
(void) mkdir_parents_label(to, 0755);
- if (symlink(from, to) < 0)
- if (errno != EEXIST)
- return log_error_errno(errno, "Failed to create symlink \"%s\": %m", to);
+
+ if (symlink(from, to) < 0 && errno != EEXIST)
+ return log_error_errno(errno, "Failed to create symlink \"%s\": %m", to);
+
+ return 0;
+}
+
+static int generator_add_ordering(
+ const char *dir,
+ const char *src,
+ const char *order,
+ const char *dst,
+ const char *instance) {
+
+ _cleanup_free_ char *instantiated = NULL, *p = NULL, *fn = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *to;
+ int r;
+
+ assert(dir);
+ assert(src);
+ assert(order);
+ assert(dst);
+
+ /* Adds in an explicit ordering dependency of type <order> from <src> to <dst>. If <instance> is
+ * specified, it is inserted into <dst>. */
+
+ if (instance) {
+ r = unit_name_replace_instance(dst, instance, &instantiated);
+ if (r < 0)
+ return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", dst, instance);
+
+ to = instantiated;
+ } else
+ to = dst;
+
+ fn = strjoin(src, ".d/50-order-", to, ".conf");
+ if (!fn)
+ return log_oom();
+
+ p = path_join(dir, fn);
+ if (!p)
+ return log_oom();
+
+ (void) mkdir_parents_label(p, 0755);
+
+ r = fopen_unlocked(p, "wxe", &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create '%s': %m", p);
+
+ fprintf(f,
+ "# Automatically generated by %s\n\n"
+ "[Unit]\n"
+ "%s=%s\n",
+ program_invocation_short_name,
+ order,
+ to);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write drop-in '%s': %m", p);
return 0;
}
const char *where,
const char *target) {
- _cleanup_free_ char *unit = NULL, *escaped = NULL, *where_unit = NULL, *unit_file = NULL;
- _cleanup_fclose_ FILE *f = NULL;
+ const char *growfs_unit, *growfs_unit_path;
+ _cleanup_free_ char *where_unit = NULL, *instance = NULL;
int r;
assert(dir);
assert(where);
- escaped = cescape(where);
- if (!escaped)
- return log_oom();
-
- r = unit_name_from_path_instance("systemd-growfs", where, ".service", &unit);
- if (r < 0)
- return log_error_errno(r, "Failed to make unit instance name from path \"%s\": %m",
- where);
-
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
- return log_error_errno(r, "Failed to make unit name from path \"%s\": %m",
- where);
+ return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
- unit_file = path_join(dir, unit);
- if (!unit_file)
- return log_oom();
+ if (empty_or_root(where)) {
+ growfs_unit = SPECIAL_GROWFS_ROOT_SERVICE;
+ growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_ROOT_SERVICE;
+ } else {
+ growfs_unit = SPECIAL_GROWFS_SERVICE;
+ growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_SERVICE;
- log_debug("Creating %s", unit_file);
+ r = unit_name_path_escape(where, &instance);
+ if (r < 0)
+ return log_error_errno(r, "Failed to escape path '%s': %m", where);
+ }
- f = fopen(unit_file, "wxe");
- if (!f)
- return log_error_errno(errno, "Failed to create unit file %s: %m",
- unit_file);
+ if (target) {
+ r = generator_add_ordering(dir, target, "After", growfs_unit, instance);
+ if (r < 0)
+ return r;
+ }
- fprintf(f,
- "# Automatically generated by %s\n\n"
- "[Unit]\n"
- "Description=Grow File System on %%f\n"
- "Documentation=man:systemd-growfs@.service(8)\n"
- "DefaultDependencies=no\n"
- "BindsTo=%%i.mount\n"
- "Conflicts=shutdown.target\n"
- "After=systemd-repart.service %%i.mount\n"
- "Before=shutdown.target%s%s\n",
- program_invocation_short_name,
- target ? " " : "",
- strempty(target));
+ return generator_add_symlink_full(dir, where_unit, "wants", growfs_unit_path, instance);
+}
- if (empty_or_root(where)) /* Make sure the root fs is actually writable before we resize it */
- fprintf(f,
- "After=systemd-remount-fs.service\n");
+int generator_hook_up_pcrfs(
+ const char *dir,
+ const char *where,
+ const char *target) {
- fprintf(f,
- "\n"
- "[Service]\n"
- "Type=oneshot\n"
- "RemainAfterExit=yes\n"
- "ExecStart="SYSTEMD_GROWFS_PATH " %s\n"
- "TimeoutSec=0\n",
- escaped);
+ const char *pcrfs_unit, *pcrfs_unit_path;
+ _cleanup_free_ char *where_unit = NULL, *instance = NULL;
+ int r;
+
+ assert(dir);
+ assert(where);
+
+ r = unit_name_from_path(where, ".mount", &where_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
+
+ if (empty_or_root(where)) {
+ pcrfs_unit = SPECIAL_PCRFS_ROOT_SERVICE;
+ pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_ROOT_SERVICE;
+ } else {
+ pcrfs_unit = SPECIAL_PCRFS_SERVICE;
+ pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_SERVICE;
+
+ r = unit_name_path_escape(where, &instance);
+ if (r < 0)
+ return log_error_errno(r, "Failed to escape path '%s': %m", where);
+ }
+
+ if (target) {
+ r = generator_add_ordering(dir, target, "After", pcrfs_unit, instance);
+ if (r < 0)
+ return r;
+ }
- return generator_add_symlink(dir, where_unit, "wants", unit);
+ return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance);
}
int generator_enable_remount_fs_service(const char *dir) {
const char *name,
FILE **file);
-int generator_add_symlink(const char *dir, const char *dst, const char *dep_type, const char *src);
+int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance);
+
+static inline int generator_add_symlink(const char *dir, const char *dst, const char *dep_type, const char *src) {
+ return generator_add_symlink_full(dir, dst, dep_type, src, NULL);
+}
int generator_write_fsck_deps(
FILE *f,
const char *dir,
const char *where,
const char *target);
+int generator_hook_up_pcrfs(
+ const char *dir,
+ const char *where,
+ const char *target);
int generator_enable_remount_fs_service(const char *dir);
assert(!changes == !n_changes);
assert(INSTALL_CHANGE_TYPE_VALID(type));
+ /* Message formatting requires <path> to be set. */
+ assert(path);
+
/* Register a change or error. Note that the return value may be the error
* that was passed in, or -ENOMEM generated internally. */
assert(verb || r >= 0);
for (size_t i = 0; i < n_changes; i++) {
- assert(verb || changes[i].type >= 0);
+ if (changes[i].type < 0)
+ assert(verb);
+ assert(changes[i].path);
/* When making changes here, make sure to also change install_error() in dbus-manager.c. */
break;
case INSTALL_CHANGE_AUXILIARY_FAILED:
if (!quiet)
- log_warning("Failed to enable auxiliary unit %s, ignoring.", changes[i].source);
+ log_warning("Failed to enable auxiliary unit %s, ignoring.", changes[i].path);
break;
case -EEXIST:
if (changes[i].source)
q = install_info_traverse(ctx, lp, i, flags, NULL);
if (q < 0) {
if (i->auxiliary) {
- q = install_changes_add(changes, n_changes, INSTALL_CHANGE_AUXILIARY_FAILED, NULL, i->name);
+ q = install_changes_add(changes, n_changes, INSTALL_CHANGE_AUXILIARY_FAILED, i->name, NULL);
if (q < 0)
return q;
continue;
return log_oom();
fprintf(f, "%s%s %s%s", ansi_grey(), prefix, ansi_normal(), ansi_green());
-
fputs(z, f);
-
fprintf(f, "%s\n", ansi_normal());
return 1;
weblink += strspn(weblink, " \t");
/* Cut out till next whitespace/newline */
- url = strndup(weblink, strcspn(weblink, WHITESPACE));
+ url = strdupcspn(weblink, WHITESPACE);
if (!url)
return log_oom();
return 0;
}
-static int parse_field(const void *data, size_t length, const char *field, size_t field_len, char **target, size_t *target_len) {
+static int parse_field(
+ const void *data,
+ size_t length,
+ const char *field,
+ size_t field_len,
+ char **target,
+ size_t *target_len) {
+
size_t nl;
char *buf;
if (!buf)
return log_oom();
- free(*target);
- *target = buf;
+ free_and_replace(*target, buf);
if (target_len)
*target_len = nl;
.target_len = _target_len \
}
-static int parse_fieldv(const void *data, size_t length, const ParseFieldVec *fields, unsigned n_fields) {
- unsigned i;
+static int parse_fieldv(
+ const void *data,
+ size_t length,
+ const ParseFieldVec *fields,
+ size_t n_fields) {
+
+ int r;
- for (i = 0; i < n_fields; i++) {
+ for (size_t i = 0; i < n_fields; i++) {
const ParseFieldVec *f = &fields[i];
- int r;
r = parse_field(data, length, f->field, f->field_len, f->target, f->target_len);
if (r < 0)
return r;
- else if (r > 0)
+ if (r > 0)
break;
}
}
static int output_timestamp_monotonic(
- FILE *f, OutputMode mode,
+ FILE *f,
+ OutputMode mode,
const dual_timestamp *ts,
const sd_id128_t *boot_id,
const dual_timestamp *previous_ts,
finish:
written_chars += fprintf(f, "%s", "]");
-
return written_chars;
}
assert(previous_ts);
assert(previous_boot_id);
- /* Set the threshold to one bigger than the actual print
- * threshold, so that if the line is actually longer than what
- * we're willing to print, ellipsization will occur. This way
- * we won't output a misleading line without any indication of
- * truncation.
+ /* Set the threshold to one bigger than the actual print threshold, so that if the line is actually
+ * longer than what we're willing to print, ellipsization will occur. This way we won't output a
+ * misleading line without any indication of truncation.
*/
- sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1);
+ (void) sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1);
JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
r = parse_fieldv(data, length, fields, ELEMENTSOF(fields));
assert(previous_ts);
assert(previous_boot_id);
- sd_journal_set_data_threshold(j, 0);
+ (void) sd_journal_set_data_threshold(j, 0);
if (!VALID_REALTIME(ts->realtime))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available");
timestamp = format_timestamp_style(buf, sizeof buf, ts->realtime,
flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US);
- fprintf(f, "%s [%s]\n",
+ fprintf(f, "%s%s%s %s[%s]%s\n",
+ timestamp && (flags & OUTPUT_COLOR) ? ANSI_UNDERLINE : "",
timestamp ?: "(no timestamp)",
- cursor);
+ timestamp && (flags & OUTPUT_COLOR) ? ANSI_NORMAL : "",
+ (flags & OUTPUT_COLOR) ? ANSI_GREY : "",
+ cursor,
+ (flags & OUTPUT_COLOR) ? ANSI_NORMAL : "");
JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
- const char *c, *p;
- int fieldlen;
- const char *on = "", *off = "";
_cleanup_free_ char *urlified = NULL;
- size_t valuelen;
+ const char *on = "", *off = "";
+ const char *c, *p = NULL;
+ size_t fieldlen, valuelen;
c = memchr(data, '=', length);
if (!c)
continue;
valuelen = length - 1 - fieldlen;
+ p = c + 1;
+
+ if (flags & OUTPUT_COLOR) {
+ if (startswith(data, "MESSAGE=")) {
+ on = ANSI_HIGHLIGHT;
+ off = ANSI_NORMAL;
+ } else if (startswith(data, "CONFIG_FILE=")) {
+ _cleanup_free_ char *u = NULL;
+
+ u = memdup_suffix0(p, valuelen);
+ if (!u)
+ return log_oom();
+
+ if (terminal_urlify_path(u, NULL, &urlified) >= 0) {
+ p = urlified;
+ valuelen = strlen(urlified);
+ }
- if ((flags & OUTPUT_COLOR) && (p = startswith(data, "MESSAGE="))) {
- on = ANSI_HIGHLIGHT;
- off = ANSI_NORMAL;
- } else if ((p = startswith(data, "CONFIG_FILE="))) {
- if (terminal_urlify_path(p, NULL, &urlified) >= 0) {
- p = urlified;
- valuelen = strlen(urlified);
+ } else if (startswith(data, "_")) {
+ /* Highlight trusted data as such */
+ on = ANSI_GREEN;
+ off = ANSI_NORMAL;
}
- } else
- p = c + 1;
+ }
if ((flags & OUTPUT_SHOW_ALL) ||
(((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH)
&& utf8_is_printable(data, length))) {
- fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data);
+ fprintf(f, " %s%.*s=", on, (int) fieldlen, (const char*)data);
print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, false,
p, valuelen,
NULL);
FORMAT_BYTES(length - (c - (const char *) data) - 1),
off);
}
-
if (r < 0)
return r;
assert(previous_ts);
assert(previous_boot_id);
- sd_journal_set_data_threshold(j, 0);
+ (void) sd_journal_set_data_threshold(j, 0);
r = sd_journal_get_cursor(j, &cursor);
if (r < 0)
struct json_data *d;
int r;
+ assert(name);
+ assert(value);
+
+ if (size == SIZE_MAX)
+ size = strlen(value);
+
if (!(flags & OUTPUT_SHOW_ALL) && strlen(name) + 1 + size >= JSON_THRESHOLD)
r = json_variant_new_null(&v);
else if (utf8_is_printable(value, size))
const dual_timestamp *previous_ts,
const sd_id128_t *previous_boot_id) {
- char sid[SD_ID128_STRING_MAX], usecbuf[DECIMAL_STR_MAX(usec_t)];
+ char usecbuf[DECIMAL_STR_MAX(usec_t)];
_cleanup_(json_variant_unrefp) JsonVariant *object = NULL;
_cleanup_free_ char *cursor = NULL;
JsonVariant **array = NULL;
if (!h)
return log_oom();
- r = update_json_data(h, flags, "__CURSOR", cursor, strlen(cursor));
+ r = update_json_data(h, flags, "__CURSOR", cursor, SIZE_MAX);
if (r < 0)
goto finish;
xsprintf(usecbuf, USEC_FMT, realtime);
- r = update_json_data(h, flags, "__REALTIME_TIMESTAMP", usecbuf, strlen(usecbuf));
+ r = update_json_data(h, flags, "__REALTIME_TIMESTAMP", usecbuf, SIZE_MAX);
if (r < 0)
goto finish;
xsprintf(usecbuf, USEC_FMT, monotonic);
- r = update_json_data(h, flags, "__MONOTONIC_TIMESTAMP", usecbuf, strlen(usecbuf));
+ r = update_json_data(h, flags, "__MONOTONIC_TIMESTAMP", usecbuf, SIZE_MAX);
if (r < 0)
goto finish;
- sd_id128_to_string(journal_boot_id, sid);
- r = update_json_data(h, flags, "_BOOT_ID", sid, strlen(sid));
+ r = update_json_data(h, flags, "_BOOT_ID", SD_ID128_TO_STRING(journal_boot_id), SIZE_MAX);
if (r < 0)
goto finish;
finish:
while ((d = hashmap_steal_first(h))) {
- size_t k;
-
json_variant_unref(d->name);
- for (k = 0; k < d->n_values; k++)
- json_variant_unref(d->values[k]);
-
+ json_variant_unref_many(d->values, d->n_values);
free(d);
}
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
- char **output_fields,
+ Set *output_fields,
const size_t highlight[2],
bool *ellipsized,
dual_timestamp *previous_ts,
sd_id128_t *previous_boot_id) {
- _cleanup_set_free_ Set *fields = NULL;
dual_timestamp ts = DUAL_TIMESTAMP_NULL;
sd_id128_t boot_id = SD_ID128_NULL;
int r;
if (n_columns <= 0)
n_columns = columns();
- r = set_put_strdupv(&fields, output_fields);
- if (r < 0)
- return r;
-
r = get_dual_timestamp(j, &ts, &boot_id);
if (r == -EBADMSG) {
log_debug_errno(r, "Skipping message we can't read: %m");
if (r < 0)
return log_error_errno(r, "Failed to get journal fields: %m");
- r = output_funcs[mode](f, j, mode, n_columns, flags, fields, highlight, &ts, &boot_id, previous_ts, previous_boot_id);
+ r = output_funcs[mode](f, j, mode, n_columns, flags, output_fields, highlight, &ts, &boot_id, previous_ts, previous_boot_id);
/* Store timestamp and boot ID for next iteration */
*previous_ts = ts;
/* -ESTALE is returned if the timestamp is not from this boot */
if (r == -ESTALE)
continue;
- else if (r < 0)
+ if (r < 0)
return log_error_errno(r, "Failed to get journal time: %m");
if (usec < not_before)
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
- char **output_fields,
+ Set *output_fields,
const size_t highlight[2],
bool *ellipsized,
dual_timestamp *previous_ts,
#include "data-fd-util.h"
#include "device-util.h"
#include "devnum-util.h"
+#include "dissect-image.h"
#include "env-util.h"
#include "errno-util.h"
#include "fd-util.h"
assert(c);
if (c->block_size != 0) {
- int z;
+ uint32_t ssz;
- if (ioctl(fd, BLKSSZGET, &z) < 0)
- return -errno;
+ r = blockdev_get_sector_size(fd, &ssz);
+ if (r < 0)
+ return r;
- assert(z >= 0);
- if ((uint32_t) z != c->block_size)
- log_debug("LOOP_CONFIGURE didn't honour requested block size %u, got %i instead. Ignoring.", c->block_size, z);
+ if (ssz != c->block_size) {
+ log_debug("LOOP_CONFIGURE didn't honour requested block size %" PRIu32 ", got %" PRIu32 " instead. Ignoring.", c->block_size, ssz);
+ broken = true;
+ }
}
if (c->info.lo_sizelimit != 0) {
static int loop_configure_fallback(int fd, const struct loop_config *c) {
struct loop_info64 info_copy;
+ int r;
assert(fd >= 0);
assert(c);
if (ioctl(fd, BLKFLSBUF, 0) < 0)
log_debug_errno(errno, "Failed to issue BLKFLSBUF ioctl, ignoring: %m");
+ /* If a block size is requested then try to configure it. If that doesn't work, ignore errors, but
+ * afterwards, let's validate what is in effect, and if it doesn't match what we want, fail */
+ if (c->block_size != 0) {
+ uint32_t ssz;
+
+ if (ioctl(fd, LOOP_SET_BLOCK_SIZE, (unsigned long) c->block_size) < 0)
+ log_debug_errno(errno, "Failed to set sector size, ignoring: %m");
+
+ r = blockdev_get_sector_size(fd, &ssz);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read sector size: %m");
+ if (ssz != c->block_size)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Sector size of loopback device doesn't match what we requested, refusing.");
+ }
+
/* LO_FLAGS_DIRECT_IO is a flags we need to configure via explicit ioctls. */
if (FLAGS_SET(c->info.lo_flags, LO_FLAGS_DIRECT_IO))
if (ioctl(fd, LOOP_SET_DIRECT_IO, 1UL) < 0)
.diskseq = diskseq,
.uevent_seqnum_not_before = seqnum,
.timestamp_not_before = timestamp,
+ .sector_size = c->block_size,
};
*ret = TAKE_PTR(d);
int open_flags,
uint64_t offset,
uint64_t size,
- uint32_t block_size,
+ uint32_t sector_size,
uint32_t loop_flags,
int lock_op,
LoopDevice **ret) {
if (control < 0)
return -errno;
+ if (sector_size == 0)
+ /* If no sector size is specified, default to the classic default */
+ sector_size = 512;
+ else if (sector_size == UINT32_MAX) {
+
+ if (S_ISBLK(st.st_mode))
+ /* If the sector size is specified as UINT32_MAX we'll propagate the sector size of
+ * the underlying block device. */
+ r = blockdev_get_sector_size(fd, §or_size);
+ else {
+ _cleanup_close_ int non_direct_io_fd = -1;
+ int probe_fd;
+
+ assert(S_ISREG(st.st_mode));
+
+ /* If sector size is specified as UINT32_MAX, we'll try to probe the right sector
+ * size of the image in question by looking for the GPT partition header at various
+ * offsets. This of course only works if the image already has a disk label.
+ *
+ * So here we actually want to read the file contents ourselves. This is quite likely
+ * not going to work if we managed to enable O_DIRECT, because in such a case there
+ * are some pretty strict alignment requirements to offset, size and target, but
+ * there's no way to query what alignment specifically is actually required. Hence,
+ * let's avoid the mess, and temporarily open an fd without O_DIRECT for the probing
+ * logic. */
+
+ if (FLAGS_SET(loop_flags, LO_FLAGS_DIRECT_IO)) {
+ non_direct_io_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (non_direct_io_fd < 0)
+ return non_direct_io_fd;
+
+ probe_fd = non_direct_io_fd;
+ } else
+ probe_fd = fd;
+
+ r = probe_sector_size(probe_fd, §or_size);
+ }
+ if (r < 0)
+ return r;
+ }
+
config = (struct loop_config) {
.fd = fd,
- .block_size = block_size,
+ .block_size = sector_size,
.info = {
/* Use the specified flags, but configure the read-only flag from the open flags, and force autoclear */
.lo_flags = (loop_flags & ~LO_FLAGS_READ_ONLY) | ((open_flags & O_ACCMODE) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | LO_FLAGS_AUTOCLEAR,
int open_flags,
uint64_t offset,
uint64_t size,
- uint32_t block_size,
+ uint32_t sector_size,
uint32_t loop_flags,
int lock_op,
LoopDevice **ret) {
open_flags,
offset,
size,
- block_size,
+ sector_size,
loop_flags_mangle(loop_flags),
lock_op,
ret);
int loop_device_make_by_path(
const char *path,
int open_flags,
+ uint32_t sector_size,
uint32_t loop_flags,
int lock_op,
LoopDevice **ret) {
direct ? "enabled" : "disabled",
direct != (direct_flags != 0) ? " (O_DIRECT was requested but not supported)" : "");
- return loop_device_make_internal(path, fd, open_flags, 0, 0, 0, loop_flags, lock_op, ret);
+ return loop_device_make_internal(path, fd, open_flags, 0, 0, sector_size, loop_flags, lock_op, ret);
}
int loop_device_make_by_path_memory(
const char *path,
int open_flags,
+ uint32_t sector_size,
uint32_t loop_flags,
int lock_op,
LoopDevice **ret) {
fd = safe_close(fd); /* Let's close the original early */
- return loop_device_make_internal(NULL, mfd, open_flags, 0, 0, 0, loop_flags, lock_op, ret);
+ return loop_device_make_internal(NULL, mfd, open_flags, 0, 0, sector_size, loop_flags, lock_op, ret);
}
static LoopDevice* loop_device_free(LoopDevice *d) {
if (r < 0 && r != -EOPNOTSUPP)
return r;
+ uint32_t sector_size;
+ r = blockdev_get_sector_size(fd, §or_size);
+ if (r < 0)
+ return r;
+
r = sd_device_get_devnum(dev, &devnum);
if (r < 0)
return r;
.diskseq = diskseq,
.uevent_seqnum_not_before = UINT64_MAX,
.timestamp_not_before = USEC_INFINITY,
+ .sector_size = sector_size,
};
*ret = d;
uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach, or 0 if we don't know */
uint64_t uevent_seqnum_not_before; /* uevent sequm right before we attached the loopback device, or UINT64_MAX if we don't know */
usec_t timestamp_not_before; /* CLOCK_MONOTONIC timestamp taken immediately before attaching the loopback device, or USEC_INFINITY if we don't know */
+ uint32_t sector_size;
};
/* Returns true if LoopDevice object is not actually a loopback device but some other block device we just wrap */
#define LOOP_DEVICE_IS_FOREIGN(d) ((d)->nr < 0)
-int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t block_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
-int loop_device_make_by_path(const char *path, int open_flags, uint32_t loop_flags, int lock_op, LoopDevice **ret);
-int loop_device_make_by_path_memory(const char *path, int open_flags, uint32_t loop_flags, int lock_op, LoopDevice **ret);
+int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
+int loop_device_make_by_path(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
+int loop_device_make_by_path_memory(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
int loop_device_open(sd_device *dev, int open_flags, int lock_op, LoopDevice **ret);
int loop_device_open_from_fd(int fd, int open_flags, int lock_op, LoopDevice **ret);
int loop_device_open_from_path(const char *path, int open_flags, int lock_op, LoopDevice **ret);
continue;
}
- r = strv_consume(&argv, TAKE_PTR(p));
- if (r < 0)
+ if (strv_consume(&argv, TAKE_PTR(p)) < 0)
return log_oom();
}
- r = strv_extend(&argv, "::");
- if (r < 0)
+ if (strv_extend(&argv, "::") < 0)
return log_oom();
if (fstat(rfd, &st) < 0)
const char *root,
sd_id128_t uuid,
bool discard,
+ uint64_t sector_size,
char * const *extra_mkfs_args) {
_cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
"-I", "256",
"-m", "0",
"-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
+ "-b", "4096",
node);
if (!argv)
return log_oom();
- if (root) {
- r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
- if (r < 0)
- return log_oom();
- }
+ if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
+ return log_oom();
} else if (STR_IN_SET(fstype, "ext3", "ext4")) {
argv = strv_new(mkfs,
"-O", "has_journal",
"-m", "0",
"-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
+ "-b", "4096",
node);
- if (root) {
- r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
- if (r < 0)
- return log_oom();
- }
+ if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
+ return log_oom();
} else if (streq(fstype, "btrfs")) {
argv = strv_new(mkfs,
if (!argv)
return log_oom();
- if (!discard) {
- r = strv_extend(&argv, "--nodiscard");
- if (r < 0)
- return log_oom();
- }
+ if (!discard && strv_extend(&argv, "--nodiscard") < 0)
+ return log_oom();
- if (root) {
- r = strv_extend_strv(&argv, STRV_MAKE("-r", root), false);
- if (r < 0)
- return log_oom();
- }
+ if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0)
+ return log_oom();
} else if (streq(fstype, "f2fs")) {
argv = strv_new(mkfs,
if (!argv)
return log_oom();
- if (!discard) {
- r = strv_extend(&argv, "-K");
- if (r < 0)
- return log_oom();
- }
+ if (!discard && strv_extend(&argv, "-K") < 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)
+ if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile), false) < 0)
+ return log_oom();
+ }
+
+ if (sector_size > 0) {
+ if (strv_extend(&argv, "-s") < 0)
+ return log_oom();
+
+ if (strv_extendf(&argv, "size=%"PRIu64, sector_size) < 0)
return log_oom();
}
- } else if (streq(fstype, "vfat"))
+ } else if (streq(fstype, "vfat")) {
argv = strv_new(mkfs,
"-i", vol_id,
"-F", "32", /* yes, we force FAT32 here */
node);
- else if (streq(fstype, "swap"))
+ if (sector_size > 0) {
+ if (strv_extend(&argv, "-S") < 0)
+ return log_oom();
+
+ if (strv_extendf(&argv, "%"PRIu64, sector_size) < 0)
+ return log_oom();
+ }
+
+ } else if (streq(fstype, "swap"))
/* TODO: add --quiet here if
* https://github.com/util-linux/util-linux/issues/1499 resolved. */
if (!argv)
return log_oom();
- if (extra_mkfs_args) {
- r = strv_extend_strv(&argv, extra_mkfs_args, false);
- if (r < 0)
- return log_oom();
- }
+ if (extra_mkfs_args && strv_extend_strv(&argv, extra_mkfs_args, false) < 0)
+ return log_oom();
if (root && stat(root, &st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", root);
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, char * const *extra_mkfs_args);
+int make_filesystem(
+ const char *node,
+ const char *fstype,
+ const char *label,
+ const char *root,
+ sd_id128_t uuid,
+ bool discard,
+ uint64_t sector_size,
+ char * const *extra_mkfs_args);
}
if (isempty(of->fdname)) {
- free(of->fdname);
+ of->fdname = mfree(of->fdname);
r = path_extract_filename(of->path, &of->fdname);
if (r < 0)
return r;
#include "format-table.h"
#include "fs-util.h"
#include "hexdecoct.h"
+#include "hmac.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "parse-util.h"
return 0;
}
+int tpm2_get_good_pcr_banks_strv(
+ ESYS_CONTEXT *c,
+ uint32_t pcr_mask,
+ char ***ret) {
+
+ _cleanup_free_ TPMI_ALG_HASH *algs = NULL;
+ _cleanup_strv_free_ char **l = NULL;
+ int n_algs;
+
+ assert(c);
+ assert(ret);
+
+ n_algs = tpm2_get_good_pcr_banks(c, pcr_mask, &algs);
+ if (n_algs < 0)
+ return n_algs;
+
+ for (int i = 0; i < n_algs; i++) {
+ _cleanup_free_ char *n = NULL;
+ const EVP_MD *implementation;
+ const char *salg;
+
+ salg = tpm2_pcr_bank_to_string(algs[i]);
+ if (!salg)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure.");
+
+ implementation = EVP_get_digestbyname(salg);
+ if (!implementation)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure.");
+
+ n = strdup(ASSERT_PTR(EVP_MD_name(implementation)));
+ if (!n)
+ return log_oom();
+
+ ascii_strlower(n); /* OpenSSL uses uppercase digest names, we prefer them lower case. */
+
+ if (strv_consume(&l, TAKE_PTR(n)) < 0)
+ return log_oom();
+ }
+
+ *ret = TAKE_PTR(l);
+ return 0;
+}
+
static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) {
struct sha256_ctx hash;
assert(auth);
assert(pin);
+
auth->size = SHA256_DIGEST_SIZE;
+ CLEANUP_ERASE(hash);
+
sha256_init_ctx(&hash);
sha256_process_bytes(pin, len, &hash);
sha256_finish_ctx(&hash, auth->buffer);
-
- explicit_bzero_safe(&hash, sizeof(hash));
}
static int tpm2_make_encryption_session(
if (pin) {
TPM2B_AUTH auth = {};
+ CLEANUP_ERASE(auth);
+
hash_pin(pin, strlen(pin), &auth);
rc = sym_Esys_TR_SetAuth(c, bind_key, &auth);
- /* ESAPI knows about it, so clear it from our memory */
- explicit_bzero_safe(&auth, sizeof(auth));
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
static const TPML_PCR_SELECTION creation_pcr = {};
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
- TPM2B_SENSITIVE_CREATE hmac_sensitive;
ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE;
+ TPM2B_SENSITIVE_CREATE hmac_sensitive;
TPMI_ALG_PUBLIC primary_alg;
TPM2B_PUBLIC hmac_template;
TPMI_ALG_HASH pcr_bank;
start = now(CLOCK_MONOTONIC);
+ CLEANUP_ERASE(hmac_sensitive);
+
r = tpm2_context_init(device, &c);
if (r < 0)
return r;
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT,
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
- .unique.keyedHash.size = 32,
+ .unique.keyedHash.size = SHA256_DIGEST_SIZE,
.authPolicy = *policy_digest,
},
};
}
secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
- explicit_bzero_safe(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
if (!secret) {
r = log_oom();
goto finish;
r = 0;
finish:
- explicit_bzero_safe(&hmac_sensitive, sizeof(hmac_sensitive));
primary = tpm2_flush_context_verbose(c.esys_context, primary);
session = tpm2_flush_context_verbose(c.esys_context, session);
return r;
#endif
}
+#if HAVE_TPM2
+int tpm2_extend_bytes(
+ ESYS_CONTEXT *c,
+ char **banks,
+ unsigned pcr_index,
+ const void *data,
+ size_t data_size,
+ const void *secret,
+ size_t secret_size) {
+
+#if HAVE_OPENSSL
+ TPML_DIGEST_VALUES values = {};
+ TSS2_RC rc;
+
+ assert(c);
+ assert(data || data_size == 0);
+ assert(secret || secret_size == 0);
+
+ if (data_size == SIZE_MAX)
+ data_size = strlen(data);
+ if (secret_size == SIZE_MAX)
+ secret_size = strlen(secret);
+
+ if (pcr_index >= TPM2_PCRS_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Can't measure into unsupported PCR %u, refusing.", pcr_index);
+
+ if (strv_isempty(banks))
+ return 0;
+
+ STRV_FOREACH(bank, banks) {
+ const EVP_MD *implementation;
+ int id;
+
+ assert_se(implementation = EVP_get_digestbyname(*bank));
+
+ if (values.count >= ELEMENTSOF(values.digests))
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected.");
+
+ if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest))
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2.");
+
+ id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation));
+ if (id < 0)
+ return log_error_errno(id, "Can't map hash name to TPM2.");
+
+ values.digests[values.count].hashAlg = id;
+
+ /* So here's a twist: sometimes we want to measure secrets (e.g. root file system volume
+ * key), but we'd rather not leak a literal hash of the secret to the TPM (given that the
+ * wire is unprotected, and some other subsystem might use the simple, literal hash of the
+ * secret for other purposes, maybe because it needs a shorter secret derived from it for
+ * some unrelated purpose, who knows). Hence we instead measure an HMAC signature of a
+ * private non-secret string instead. */
+ if (secret_size > 0) {
+ if (!HMAC(implementation, secret, secret_size, data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure.");
+ } else if (EVP_Digest(data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure.");
+
+ values.count++;
+ }
+
+ rc = sym_Esys_PCR_Extend(
+ c,
+ ESYS_TR_PCR0 + pcr_index,
+ ESYS_TR_PASSWORD,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &values);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(
+ SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to measure into PCR %u: %s",
+ pcr_index,
+ sym_Tss2_RC_Decode(rc));
+
+ return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "OpenSSL not supported on this build.");
+#endif
+}
+#endif
+
int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
const char *p = ASSERT_PTR(s);
uint32_t mask = 0;
size_t blob_size,
const void *policy_hash,
size_t policy_hash_size,
+ const void *salt,
+ size_t salt_size,
TPM2Flags flags,
JsonVariant **ret) {
JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)),
JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
- JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size))));
+ JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
+ JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size))));
if (r < 0)
return r;
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
+ void **ret_salt,
+ size_t *ret_salt_size,
TPM2Flags *ret_flags) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL;
- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0;
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
+ size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0;
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
SET_FLAG(flags, TPM2_FLAGS_USE_PIN, json_variant_boolean(w));
}
+ w = json_variant_by_key(v, "tpm2_salt");
+ if (w) {
+ r = json_variant_unbase64(w, &salt, &salt_size);
+ if (r < 0)
+ return log_debug_errno(r, "Invalid base64 data in 'tpm2_salt' field.");
+ }
+
w = json_variant_by_key(v, "tpm2_pubkey_pcrs");
if (w) {
r = tpm2_parse_pcr_json_array(w, &pubkey_pcr_mask);
*ret_policy_hash = TAKE_PTR(policy_hash);
if (ret_policy_hash_size)
*ret_policy_hash_size = policy_hash_size;
+ if (ret_salt)
+ *ret_salt = TAKE_PTR(salt);
+ if (ret_salt_size)
+ *ret_salt_size = salt_size;
if (ret_flags)
*ret_flags = flags;
*ret = TAKE_PTR(buf);
return 0;
}
+
+#define PBKDF2_HMAC_SHA256_ITERATIONS 10000
+
+/*
+ * Implements PBKDF2 HMAC SHA256 for a derived keylen of 32
+ * bytes and for PBKDF2_HMAC_SHA256_ITERATIONS count.
+ * I found the wikipedia entry relevant and it contains links to
+ * relevant RFCs:
+ * - https://en.wikipedia.org/wiki/PBKDF2
+ * - https://www.rfc-editor.org/rfc/rfc2898#section-5.2
+ */
+int tpm2_util_pbkdf2_hmac_sha256(const void *pass,
+ size_t passlen,
+ const void *salt,
+ size_t saltlen,
+ uint8_t ret_key[static SHA256_DIGEST_SIZE]) {
+
+ uint8_t _cleanup_(erase_and_freep) *buffer = NULL;
+ uint8_t u[SHA256_DIGEST_SIZE];
+
+ /* To keep this simple, since derived KeyLen (dkLen in docs)
+ * Is the same as the hash output, we don't need multiple
+ * blocks. Part of the algorithm is to add the block count
+ * in, but this can be hardcoded to 1.
+ */
+ static const uint8_t block_cnt[] = { 0, 0, 0, 1 };
+
+ assert (saltlen > 0);
+ assert (saltlen <= (SIZE_MAX - sizeof(block_cnt)));
+ assert (passlen > 0);
+
+ /*
+ * Build a buffer of salt + block_cnt and hmac_sha256 it we
+ * do this as we don't have a context builder for HMAC_SHA256.
+ */
+ buffer = malloc(saltlen + sizeof(block_cnt));
+ if (!buffer)
+ return -ENOMEM;
+
+ memcpy(buffer, salt, saltlen);
+ memcpy(&buffer[saltlen], block_cnt, sizeof(block_cnt));
+
+ hmac_sha256(pass, passlen, buffer, saltlen + sizeof(block_cnt), u);
+
+ /* dk needs to be an unmodified u as u gets modified in the loop */
+ memcpy(ret_key, u, SHA256_DIGEST_SIZE);
+ uint8_t *dk = ret_key;
+
+ for (size_t i = 1; i < PBKDF2_HMAC_SHA256_ITERATIONS; i++) {
+ hmac_sha256(pass, passlen, u, sizeof(u), u);
+
+ for (size_t j=0; j < sizeof(u); j++)
+ dk[j] ^= u[j];
+ }
+
+ return 0;
+}
#include "json.h"
#include "macro.h"
+#include "sha256.h"
typedef enum TPM2Flags {
TPM2_FLAGS_USE_PIN = 1 << 0,
}
int tpm2_get_good_pcr_banks(ESYS_CONTEXT *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks);
+int tpm2_get_good_pcr_banks_strv(ESYS_CONTEXT *c, uint32_t pcr_mask, char ***ret);
+
+int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size);
#else
struct tpm2_context;
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret);
-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, TPM2Flags *ret_flags);
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret);
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags);
#define TPM2_PCRS_MAX 24U
int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size);
int pcr_mask_to_string(uint32_t mask, char **ret);
+
+int tpm2_util_pbkdf2_hmac_sha256(const void *pass,
+ size_t passlen,
+ const void *salt,
+ size_t saltlen,
+ uint8_t res[static SHA256_DIGEST_SIZE]);
r = loop_device_make_by_path(
img->path,
O_RDONLY,
+ /* sector_size= */ UINT32_MAX,
FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
LOCK_SH,
&d);
typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
STRV_FOREACH(c, deps) {
+ _cleanup_free_ char *load_state = NULL, *sub_state = NULL;
+ UnitActiveState active_state;
+
if (strv_contains(*units, *c)) {
if (!arg_plain) {
printf(" ");
- r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1);
+ r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), /* last = */ true);
if (r < 0)
return r;
}
continue;
}
+ if (arg_types && !strv_contains(arg_types, unit_type_suffix(*c)))
+ continue;
+
+ r = get_state_one_unit(bus, *c, &active_state);
+ if (r < 0)
+ return r;
+
+ if (arg_states) {
+ r = unit_load_state(bus, *c, &load_state);
+ if (r < 0)
+ return r;
+
+ r = get_sub_state_one_unit(bus, *c, &sub_state);
+ if (r < 0)
+ return r;
+
+ if (!strv_overlap(arg_states, STRV_MAKE(unit_active_state_to_string(active_state), load_state, sub_state)))
+ continue;
+ }
+
if (arg_plain)
printf(" ");
else {
- UnitActiveState active_state = _UNIT_ACTIVE_STATE_INVALID;
const char *on;
- (void) get_state_one_unit(bus, *c, &active_state);
-
switch (active_state) {
case UNIT_ACTIVE:
case UNIT_RELOADING:
sd_bus *bus;
int r;
+ /* We won't be able to preserve the tree structure if --type= or --state= is used */
+ arg_plain = arg_plain || arg_types || arg_states;
+
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
#include "errno-util.h"
#include "exec-util.h"
#include "exit-status.h"
+#include "fd-util.h"
#include "format-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
if (!info)
return -ENOMEM;
- LIST_FIND_TAIL(exec_status_info_list, i->exec_status_info_list, last);
+ last = LIST_FIND_TAIL(exec_status_info_list, i->exec_status_info_list);
while ((r = exec_status_info_deserialize(m, info, is_ex_prop)) > 0) {
return 0;
}
-static int get_unit_dbus_path_by_pid(
+static int get_unit_dbus_path_by_pid_fallback(
sd_bus *bus,
uint32_t pid,
- char **unit) {
+ char **ret_path,
+ char **ret_unit) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- char *u;
+ _cleanup_free_ char *path = NULL, *unit = NULL;
+ char *p;
int r;
+ assert(bus);
+ assert(ret_path);
+ assert(ret_unit);
+
r = bus_call_method(bus, bus_systemd_mgr, "GetUnitByPID", &error, &reply, "u", pid);
if (r < 0)
return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r));
- r = sd_bus_message_read(reply, "o", &u);
+ r = sd_bus_message_read(reply, "o", &p);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ path = strdup(p);
+ if (!path)
+ return log_oom();
+
+ r = unit_name_from_dbus_path(path, &unit);
+ if (r < 0)
+ return log_oom();
+
+ *ret_unit = TAKE_PTR(unit);
+ *ret_path = TAKE_PTR(path);
+
+ return 0;
+}
+
+static int get_unit_dbus_path_by_pid(
+ sd_bus *bus,
+ uint32_t pid,
+ char **ret_path,
+ char **ret_unit) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_free_ char *path = NULL, *unit = NULL;
+ _cleanup_close_ int pidfd = -EBADF;
+ char *p, *u;
+ int r;
+
+ assert(bus);
+ assert(ret_path);
+ assert(ret_unit);
+
+ /* First, try to send a PIDFD across the wire, so that we can pin the process and there's no race
+ * condition possible while we wait for the D-Bus reply. If we either don't have PIDFD support in
+ * the kernel or the new D-Bus method is not available, then fallback to the older method that
+ * sends the numeric PID. */
+
+ pidfd = pidfd_open(pid, 0);
+ if (pidfd < 0 && ERRNO_IS_NOT_SUPPORTED(errno))
+ return get_unit_dbus_path_by_pid_fallback(bus, pid, ret_path, ret_unit);
+ if (pidfd < 0)
+ return log_error_errno(errno, "Failed to open PID %"PRIu32": %m", pid);
+
+ r = bus_call_method(bus, bus_systemd_mgr, "GetUnitByPIDFD", &error, &reply, "h", pidfd);
+ if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD))
+ return get_unit_dbus_path_by_pid_fallback(bus, pid, ret_path, ret_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "os", &p, &u);
if (r < 0)
return bus_log_parse_error(r);
- u = strdup(u);
- if (!u)
+ path = strdup(p);
+ if (!path)
+ return log_oom();
+
+ unit = strdup(u);
+ if (!unit)
return log_oom();
- *unit = u;
+ *ret_unit = TAKE_PTR(unit);
+ *ret_path = TAKE_PTR(path);
+
return 0;
}
} else {
/* Interpret as PID */
- r = get_unit_dbus_path_by_pid(bus, id, &path);
+ r = get_unit_dbus_path_by_pid(bus, id, &path, &unit);
if (r < 0) {
ret = r;
continue;
}
-
- r = unit_name_from_dbus_path(path, &unit);
- if (r < 0)
- return log_oom();
}
r = show_one(bus, path, unit, show_mode, &new_line, &ellipsized);
UnitActiveState state;
int r;
+ assert(bus);
assert(unit);
assert(ret_active_state);
return 0;
}
+int get_sub_state_one_unit(sd_bus *bus, const char *unit, char **ret_sub_state) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *sub_state = NULL, *dbus_path = NULL;
+ int r;
+
+ assert(bus);
+ assert(unit);
+ assert(ret_sub_state);
+
+ dbus_path = unit_dbus_path_from_name(unit);
+ if (!dbus_path)
+ return log_oom();
+
+ r = sd_bus_get_property_string(
+ bus,
+ "org.freedesktop.systemd1",
+ dbus_path,
+ "org.freedesktop.systemd1.Unit",
+ "SubState",
+ &error,
+ &sub_state);
+ if (r < 0)
+ return log_error_errno(r, "Failed to retrieve unit sub state: %s", bus_error_message(&error, r));
+
+ *ret_sub_state = TAKE_PTR(sub_state);
+ return 0;
+}
+
int get_unit_list(
sd_bus *bus,
const char *machine,
int translate_bus_error_to_exit_status(int r, const sd_bus_error *error);
-int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *ret_active_state);
+int get_state_one_unit(sd_bus *bus, const char *unit, UnitActiveState *ret_active_state);
+int get_sub_state_one_unit(sd_bus *bus, const char *unit, char **ret_sub_state);
int get_unit_list(sd_bus *bus, const char *machine, char **patterns, UnitInfo **unit_infos, int c, sd_bus_message **ret_reply);
int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret, bool *ret_expanded);
" kexec, suspend, hibernate, suspend-then-hibernate,\n"
" hybrid-sleep, default, rescue, emergency, and exit.\n"
" -q --quiet Suppress output\n"
- " --no-warn Don't generate warning when trying to enable/disable\n"
- " units without install information\n"
+ " --no-warn Suppress several warnings shown by default\n"
" --wait For (re)start, wait until service stopped again\n"
" For is-system-running, wait until startup is completed\n"
" --no-block Do not wait until operation finished\n"
goto finish;
if (proc_mounted() == 0)
- log_warning("%s%s/proc/ is not mounted. This is not a supported mode of operation. Please fix\n"
- "your invocation environment to mount /proc/ and /sys/ properly. Proceeding anyway.\n"
- "Your mileage may vary.",
- emoji_enabled() ? special_glyph(SPECIAL_GLYPH_WARNING_SIGN) : "",
- emoji_enabled() ? " " : "");
+ log_full(arg_no_warn ? LOG_DEBUG : LOG_WARNING,
+ "%s%s/proc/ is not mounted. This is not a supported mode of operation. Please fix\n"
+ "your invocation environment to mount /proc/ and /sys/ properly. Proceeding anyway.\n"
+ "Your mileage may vary.",
+ emoji_enabled() ? special_glyph(SPECIAL_GLYPH_WARNING_SIGN) : "",
+ emoji_enabled() ? " " : "");
if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) {
if (!arg_quiet)
int sd_dhcp_client_set_service_type(
sd_dhcp_client *client,
int type);
+int sd_dhcp_client_set_socket_priority(
+ sd_dhcp_client *client,
+ int so_priority);
int sd_dhcp_client_set_fallback_lease_lifetime(
sd_dhcp_client *client,
uint32_t fallback_lease_lifetime);
int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
sd_dhcp6_option *v);
int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable);
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable);
int sd_dhcp6_client_get_lease(
sd_dhcp6_client *client,
* hierarchy. */
int sd_pid_get_cgroup(pid_t pid, char **cgroup);
+/* Equivalent to the corresponding sd_pid_get* functions, but take a
+ * PIDFD instead of a PID, to ensure there can be no possible PID
+ * recycle issues before/after the calls. */
+int sd_pidfd_get_session(pid_t pid, char **session);
+int sd_pidfd_get_owner_uid(pid_t pid, uid_t *uid);
+int sd_pidfd_get_unit(pid_t pid, char **unit);
+int sd_pidfd_get_user_unit(pid_t pid, char **unit);
+int sd_pidfd_get_slice(pid_t pid, char **slice);
+int sd_pidfd_get_user_slice(pid_t pid, char **slice);
+int sd_pidfd_get_machine_name(pid_t pid, char **machine);
+int sd_pidfd_get_cgroup(pid_t pid, char **cgroup);
+
/* Similar to sd_pid_get_session(), but retrieves data about the peer
* of a connected AF_UNIX socket */
int sd_peer_get_session(int fd, char **session);
#define SD_MESSAGE_TIME_SYNC SD_ID128_MAKE(7c,8a,41,f3,7b,76,49,41,a0,e1,78,0b,1b,e2,f0,37)
#define SD_MESSAGE_TIME_SYNC_STR SD_ID128_MAKE_STR(7c,8a,41,f3,7b,76,49,41,a0,e1,78,0b,1b,e2,f0,37)
+#define SD_MESSAGE_TIME_BUMP SD_ID128_MAKE(7d,b7,3c,8a,f0,d9,4e,eb,82,2a,e0,43,23,fe,6a,b6)
+#define SD_MESSAGE_TIME_BUMP_STR SD_ID128_MAKE_STR(7d,b7,3c,8a,f0,d9,4e,eb,82,2a,e0,43,23,fe,6a,b6)
+
#define SD_MESSAGE_SHUTDOWN_SCHEDULED SD_ID128_MAKE(9e,70,66,27,9d,c8,40,3d,a7,9c,e4,b1,a6,90,64,b2)
#define SD_MESSAGE_SHUTDOWN_SCHEDULED_STR SD_ID128_MAKE_STR(9e,70,66,27,9d,c8,40,3d,a7,9c,e4,b1,a6,90,64,b2)
/* Get path to .network file applied to link */
int sd_network_link_get_network_file(int ifindex, char **ret);
+/* Get paths to .network file dropins applied to link */
+int sd_network_link_get_network_file_dropins(int ifindex, char ***ret);
+
/* Get DNS entries for a given link. These are string representations of
* IP addresses */
int sd_network_link_get_dns(int ifindex, char ***ret);
/* systemd-networkd search paths */
SD_PATH_SYSTEMD_SEARCH_NETWORK,
+ /* systemd environment generators */
+ SD_PATH_SYSTEMD_SYSTEM_ENVIRONMENT_GENERATOR,
+ SD_PATH_SYSTEMD_USER_ENVIRONMENT_GENERATOR,
+ SD_PATH_SYSTEMD_SEARCH_SYSTEM_ENVIRONMENT_GENERATOR,
+ SD_PATH_SYSTEMD_SEARCH_USER_ENVIRONMENT_GENERATOR,
+
_SD_PATH_MAX
};
test_env = environment()
test_env.set('SYSTEMD_LANGUAGE_FALLBACK_MAP', language_fallback_map)
test_env.set('PATH', project_build_root + ':' + path)
+test_env.set('PROJECT_BUILD_ROOT', project_build_root)
############################################################
assert_se(sd_id128_equal(a, b));
}
+ assert_se(lstat(p, &st) >= 0);
+ r = chase_symlinks_and_unlink(p, NULL, 0, 0, &result);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+ assert_se(r == 0);
+ assert_se(lstat(p, &st) == -1 && errno == ENOENT);
+
/* Test CHASE_NOFOLLOW */
p = strjoina(temp, "/target");
assert_se(decoded_size == n);
assert_se(memcmp(data, decoded, n) == 0);
+ /* Also try in secure mode */
+ decoded = mfree(decoded);
+ decoded_size = 0;
+ assert_se(unbase64mem_full(encoded, SIZE_MAX, /* secure= */ true, &decoded, &decoded_size) >= 0);
+ assert_se(decoded_size == n);
+ assert_se(memcmp(data, decoded, n) == 0);
+
for (size_t j = 0; j < (size_t) l; j++)
assert_se((encoded[j] == '\n') == (j % (m + 1) == m));
}
size_t size = 0;
assert_se(unbase64mem(input, SIZE_MAX, &buffer, &size) == ret);
+ if (ret >= 0) {
+ assert_se(size == strlen(output));
+ assert_se(memcmp(buffer, output, size) == 0);
+ assert_se(((char*) buffer)[size] == 0);
+ }
+
+ /* also try in secure mode */
+ buffer = mfree(buffer);
+ size = 0;
+ assert_se(unbase64mem_full(input, SIZE_MAX, /* secure=*/ true, &buffer, &size) == ret);
if (ret >= 0) {
assert_se(size == strlen(output));
assert_se(memcmp(buffer, output, size) == 0);
const union in_addr_union *u,
unsigned char prefixlen,
int ret_refuse,
- unsigned char prefixlen_refuse,
- int ret_legacy,
- unsigned char prefixlen_legacy) {
+ unsigned char prefixlen_refuse) {
union in_addr_union q;
unsigned char l;
assert_se(in_addr_equal(family, &q, u));
assert_se(l == prefixlen_refuse);
}
-
- r = in_addr_prefix_from_string_auto_internal(p, PREFIXLEN_LEGACY, &f, &q, &l);
- assert_se(r == ret_legacy);
-
- if (r >= 0) {
- assert_se(f == family);
- assert_se(in_addr_equal(family, &q, u));
- assert_se(l == prefixlen_legacy);
- }
}
TEST(in_addr_prefix_from_string) {
- test_in_addr_prefix_from_string_one("", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0);
- test_in_addr_prefix_from_string_one("/", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0);
- test_in_addr_prefix_from_string_one("/8", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0);
- test_in_addr_prefix_from_string_one("1.2.3.4", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32, -ENOANO, 0, 0, 8);
- test_in_addr_prefix_from_string_one("1.2.3.4/0", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 0, 0, 0, 0, 0);
- test_in_addr_prefix_from_string_one("1.2.3.4/1", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 1, 0, 1, 0, 1);
- test_in_addr_prefix_from_string_one("1.2.3.4/2", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 2, 0, 2, 0, 2);
- test_in_addr_prefix_from_string_one("1.2.3.4/32", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32, 0, 32, 0, 32);
- test_in_addr_prefix_from_string_one("1.2.3.4/33", AF_INET, -ERANGE, NULL, 0, -ERANGE, 0, -ERANGE, 0);
- test_in_addr_prefix_from_string_one("1.2.3.4/-1", AF_INET, -ERANGE, NULL, 0, -ERANGE, 0, -ERANGE, 0);
- test_in_addr_prefix_from_string_one("::1", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0);
-
- test_in_addr_prefix_from_string_one("", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0);
- test_in_addr_prefix_from_string_one("/", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0);
- test_in_addr_prefix_from_string_one("/8", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0, -EINVAL, 0);
- test_in_addr_prefix_from_string_one("::1", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128, -ENOANO, 0, 0, 0);
- test_in_addr_prefix_from_string_one("::1/0", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0, 0, 0, 0, 0);
- test_in_addr_prefix_from_string_one("::1/1", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 1, 0, 1, 0, 1);
- test_in_addr_prefix_from_string_one("::1/2", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 2, 0, 2, 0, 2);
- test_in_addr_prefix_from_string_one("::1/32", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 32, 0, 32, 0, 32);
- test_in_addr_prefix_from_string_one("::1/33", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 33, 0, 33, 0, 33);
- test_in_addr_prefix_from_string_one("::1/64", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 64, 0, 64, 0, 64);
- test_in_addr_prefix_from_string_one("::1/128", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128, 0, 128, 0, 128);
- test_in_addr_prefix_from_string_one("::1/129", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0, -ERANGE, 0);
- test_in_addr_prefix_from_string_one("::1/-1", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0, -ERANGE, 0);
+ test_in_addr_prefix_from_string_one("", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0);
+ test_in_addr_prefix_from_string_one("/", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0);
+ test_in_addr_prefix_from_string_one("/8", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0);
+ test_in_addr_prefix_from_string_one("1.2.3.4", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32, -ENOANO, 0);
+ test_in_addr_prefix_from_string_one("1.2.3.4/0", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 0, 0, 0);
+ test_in_addr_prefix_from_string_one("1.2.3.4/1", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 1, 0, 1);
+ test_in_addr_prefix_from_string_one("1.2.3.4/2", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 2, 0, 2);
+ test_in_addr_prefix_from_string_one("1.2.3.4/32", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32, 0, 32);
+ test_in_addr_prefix_from_string_one("1.2.3.4/33", AF_INET, -ERANGE, NULL, 0, -ERANGE, 0);
+ test_in_addr_prefix_from_string_one("1.2.3.4/-1", AF_INET, -ERANGE, NULL, 0, -ERANGE, 0);
+ test_in_addr_prefix_from_string_one("::1", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0);
+
+ test_in_addr_prefix_from_string_one("", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0);
+ test_in_addr_prefix_from_string_one("/", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0);
+ test_in_addr_prefix_from_string_one("/8", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0);
+ test_in_addr_prefix_from_string_one("::1", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128, -ENOANO, 0);
+ test_in_addr_prefix_from_string_one("::1/0", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0, 0, 0);
+ test_in_addr_prefix_from_string_one("::1/1", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 1, 0, 1);
+ test_in_addr_prefix_from_string_one("::1/2", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 2, 0, 2);
+ test_in_addr_prefix_from_string_one("::1/32", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 32, 0, 32);
+ test_in_addr_prefix_from_string_one("::1/33", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 33, 0, 33);
+ test_in_addr_prefix_from_string_one("::1/64", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 64, 0, 64);
+ test_in_addr_prefix_from_string_one("::1/128", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128, 0, 128);
+ test_in_addr_prefix_from_string_one("::1/129", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0);
+ test_in_addr_prefix_from_string_one("::1/-1", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0);
}
static void test_in_addr_prefix_to_string_valid(int family, const char *p) {
for (i = 0; i < ELEMENTSOF(items); i++) {
LIST_INIT(item_list, &items[i]);
assert_se(LIST_JUST_US(item_list, &items[i]));
- LIST_PREPEND(item_list, head, &items[i]);
+ assert_se(LIST_PREPEND(item_list, head, &items[i]) == &items[i]);
}
i = 0;
assert_se(items[2].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- list_item *cursor;
- LIST_FIND_HEAD(item_list, &items[0], cursor);
+ list_item *cursor = LIST_FIND_HEAD(item_list, &items[0]);
assert_se(cursor == &items[3]);
- LIST_FIND_TAIL(item_list, &items[3], cursor);
+ cursor = LIST_FIND_TAIL(item_list, &items[3]);
assert_se(cursor == &items[0]);
- LIST_REMOVE(item_list, head, &items[1]);
+ assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]);
assert_se(LIST_JUST_US(item_list, &items[1]));
assert_se(items[0].item_list_next == NULL);
assert_se(items[2].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_INSERT_AFTER(item_list, head, &items[3], &items[1]);
+ assert_se(LIST_INSERT_AFTER(item_list, head, &items[3], &items[1]) == &items[1]);
assert_se(items[0].item_list_next == NULL);
assert_se(items[2].item_list_next == &items[0]);
assert_se(items[1].item_list_next == &items[2]);
assert_se(items[1].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_REMOVE(item_list, head, &items[1]);
+ assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]);
assert_se(LIST_JUST_US(item_list, &items[1]));
assert_se(items[0].item_list_next == NULL);
assert_se(items[2].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_INSERT_BEFORE(item_list, head, &items[2], &items[1]);
+ assert_se(LIST_INSERT_BEFORE(item_list, head, &items[2], &items[1]) == &items[1]);
assert_se(items[0].item_list_next == NULL);
assert_se(items[2].item_list_next == &items[0]);
assert_se(items[1].item_list_next == &items[2]);
assert_se(items[1].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_REMOVE(item_list, head, &items[0]);
+ assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]);
assert_se(LIST_JUST_US(item_list, &items[0]));
assert_se(items[2].item_list_next == NULL);
assert_se(items[1].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_INSERT_BEFORE(item_list, head, &items[3], &items[0]);
+ assert_se(LIST_INSERT_BEFORE(item_list, head, &items[3], &items[0]) == &items[0]);
assert_se(items[2].item_list_next == NULL);
assert_se(items[1].item_list_next == &items[2]);
assert_se(items[3].item_list_next == &items[1]);
assert_se(items[0].item_list_prev == NULL);
assert_se(head == &items[0]);
- LIST_REMOVE(item_list, head, &items[0]);
+ assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]);
assert_se(LIST_JUST_US(item_list, &items[0]));
assert_se(items[2].item_list_next == NULL);
assert_se(items[1].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_INSERT_BEFORE(item_list, head, NULL, &items[0]);
+ assert_se(LIST_INSERT_BEFORE(item_list, head, NULL, &items[0]) == &items[0]);
assert_se(items[0].item_list_next == NULL);
assert_se(items[2].item_list_next == &items[0]);
assert_se(items[1].item_list_next == &items[2]);
assert_se(items[1].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_REMOVE(item_list, head, &items[0]);
+ assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]);
assert_se(LIST_JUST_US(item_list, &items[0]));
assert_se(items[2].item_list_next == NULL);
assert_se(items[1].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_REMOVE(item_list, head, &items[1]);
+ assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]);
assert_se(LIST_JUST_US(item_list, &items[1]));
assert_se(items[2].item_list_next == NULL);
assert_se(items[2].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_REMOVE(item_list, head, &items[2]);
+ assert_se(LIST_REMOVE(item_list, head, &items[2]) == &items[2]);
assert_se(LIST_JUST_US(item_list, &items[2]));
assert_se(LIST_JUST_US(item_list, head));
- LIST_REMOVE(item_list, head, &items[3]);
+ assert_se(LIST_REMOVE(item_list, head, &items[3]) == &items[3]);
assert_se(LIST_JUST_US(item_list, &items[3]));
assert_se(head == NULL);
for (i = 0; i < ELEMENTSOF(items); i++) {
assert_se(LIST_JUST_US(item_list, &items[i]));
- LIST_APPEND(item_list, head, &items[i]);
+ assert_se(LIST_APPEND(item_list, head, &items[i]) == &items[i]);
}
assert_se(!LIST_JUST_US(item_list, head));
assert_se(items[3].item_list_prev == &items[2]);
for (i = 0; i < ELEMENTSOF(items); i++)
- LIST_REMOVE(item_list, head, &items[i]);
+ assert_se(LIST_REMOVE(item_list, head, &items[i]) == &items[i]);
assert_se(head == NULL);
for (i = 0; i < ELEMENTSOF(items) / 2; i++) {
LIST_INIT(item_list, &items[i]);
assert_se(LIST_JUST_US(item_list, &items[i]));
- LIST_PREPEND(item_list, head, &items[i]);
+ assert_se(LIST_PREPEND(item_list, head, &items[i]) == &items[i]);
}
for (i = ELEMENTSOF(items) / 2; i < ELEMENTSOF(items); i++) {
LIST_INIT(item_list, &items[i]);
assert_se(LIST_JUST_US(item_list, &items[i]));
- LIST_PREPEND(item_list, head2, &items[i]);
+ assert_se(LIST_PREPEND(item_list, head2, &items[i]) == &items[i]);
}
assert_se(items[0].item_list_next == NULL);
assert_se(items[2].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_JOIN(item_list, head2, head);
+ assert_se(LIST_JOIN(item_list, head2, head) == head2);
assert_se(head == NULL);
assert_se(items[0].item_list_next == NULL);
assert_se(items[2].item_list_prev == &items[3]);
assert_se(items[3].item_list_prev == NULL);
- LIST_JOIN(item_list, head, head2);
+ assert_se(LIST_JOIN(item_list, head, head2) == head);
assert_se(head2 == NULL);
assert_se(head);
for (i = 0; i < ELEMENTSOF(items); i++)
- LIST_REMOVE(item_list, head, &items[i]);
+ assert_se(LIST_REMOVE(item_list, head, &items[i]) == &items[i]);
assert_se(head == NULL);
- LIST_PREPEND(item_list, head, items + 0);
- LIST_PREPEND(item_list, head, items + 1);
- LIST_PREPEND(item_list, head, items + 2);
+ assert_se(LIST_PREPEND(item_list, head, items + 0) == items + 0);
+ assert_se(LIST_PREPEND(item_list, head, items + 1) == items + 1);
+ assert_se(LIST_PREPEND(item_list, head, items + 2) == items + 2);
assert_se(LIST_POP(item_list, head) == items + 2);
assert_se(LIST_POP(item_list, head) == items + 1);
TEST(config_parse_log_filter_patterns) {
ExecContext c = {};
- int r;
static const struct {
const char *regex;
return (void) log_tests_skipped("PCRE2 support is not available");
for (size_t i = 0; i < ELEMENTSOF(regex_tests); i++) {
- r = config_parse_log_filter_patterns(NULL, "fake", 1, "section", 1, "LogFilterPatterns", 1,
- regex_tests[i].regex, &c, NULL);
- assert_se(r >= 0);
+ assert_se(config_parse_log_filter_patterns(NULL, "fake", 1, "section", 1, "LogFilterPatterns", 1,
+ regex_tests[i].regex, &c, NULL) >= 0);
assert_se(set_size(c.log_filter_allowed_patterns) == regex_tests[i].allowed_patterns_count);
assert_se(set_size(c.log_filter_denied_patterns) == regex_tests[i].denied_patterns_count);
SET_FOREACH(p, c.log_filter_denied_patterns)
assert_se(p && p[0] != '~');
}
+
+ exec_context_done(&c);
}
TEST(config_parse_open_file) {
#include "log.h"
#include "process-util.h"
#include "string-util.h"
+#include "strv.h"
assert_cc(IS_SYNTHETIC_ERRNO(SYNTHETIC_ERRNO(EINVAL)));
assert_cc(!IS_SYNTHETIC_ERRNO(EINVAL));
assert_se(log_syntax("unit", LOG_ERR, "filename", 10, SYNTHETIC_ERRNO(ENOTTY), "ENOTTY: %s: %m", "hogehoge") == -ENOTTY);
}
+static void test_log_context(void) {
+ {
+ char **strv = STRV_MAKE("FIRST=abc", "SECOND=qrs");
+
+ LOG_CONTEXT_PUSH("THIRD=pfs");
+ LOG_CONTEXT_PUSH("FOURTH=def");
+ LOG_CONTEXT_PUSH_STRV(strv);
+ LOG_CONTEXT_PUSH_STRV(strv);
+
+ /* Test that the log context was set up correctly. */
+ assert_se(log_context_num_contexts() == 4);
+ assert_se(log_context_num_fields() == 6);
+
+ /* Test that everything still works with modifications to the log context. */
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+
+ {
+ LOG_CONTEXT_PUSH("FIFTH=123");
+ LOG_CONTEXT_PUSH_STRV(strv);
+
+ /* Check that our nested fields got added correctly. */
+ assert_se(log_context_num_contexts() == 6);
+ assert_se(log_context_num_fields() == 9);
+
+ /* Test that everything still works in a nested block. */
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+ }
+
+ /* Check that only the fields from the nested block got removed. */
+ assert_se(log_context_num_contexts() == 4);
+ assert_se(log_context_num_fields() == 6);
+ }
+
+ assert_se(log_context_num_contexts() == 0);
+ assert_se(log_context_num_fields() == 0);
+
+ {
+ _cleanup_(log_context_freep) LogContext *ctx = NULL;
+
+ char **strv = STRV_MAKE("SIXTH=ijn", "SEVENTH=PRP");
+ assert_se(ctx = log_context_new(strv, /*owned=*/ false));
+
+ assert_se(log_context_num_contexts() == 1);
+ assert_se(log_context_num_fields() == 2);
+
+ /* Test that everything still works with a manually configured log context. */
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+ }
+
+ {
+ char **strv = NULL;
+
+ assert_se(strv = strv_new("ABC", "DEF"));
+ LOG_CONTEXT_CONSUME_STRV(strv);
+
+ assert_se(log_context_num_contexts() == 1);
+ assert_se(log_context_num_fields() == 2);
+ }
+
+ assert_se(log_context_num_contexts() == 0);
+ assert_se(log_context_num_fields() == 0);
+}
+
int main(int argc, char* argv[]) {
test_file();
test_log_struct();
test_long_lines();
test_log_syntax();
+ test_log_context();
}
return 0;
assert_se(r >= 0);
assert_se(sd_id128_randomize(&id) >= 0);
- assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, true, NULL) >= 0);
+ assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, true, 0, NULL) >= 0);
assert_se(sd_id128_randomize(&id) >= 0);
- assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, true, NULL) >= 0);
+ assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, true, 0, NULL) >= 0);
assert_se(sd_id128_randomize(&id) >= 0);
- assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, true, NULL) >= 0);
+ assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, true, 0, NULL) >= 0);
assert_se(sd_id128_randomize(&id) >= 0);
- assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, true, NULL) >= 0);
+ assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, true, 0, NULL) >= 0);
dissected = dissected_image_unref(dissected);
assert_se(!streq_skip_trailing_chars("", "f", NULL));
}
+#define TEST_MAKE_CSTRING_ONE(x, ret, mode, expect) \
+ do { \
+ _cleanup_free_ char *b = NULL; \
+ assert_se(make_cstring((x), ELEMENTSOF(x), (mode), &b) == (ret)); \
+ assert_se(streq_ptr(b, (expect))); \
+ } while(false)
+
+TEST(make_cstring) {
+ static const char test1[] = "this is a test",
+ test2[] = "",
+ test3[] = "a",
+ test4[] = "aa\0aa",
+ test5[] = { 'b', 'b', 0, 'b' , 'b' },
+ test6[] = {},
+ test7[] = { 'x' },
+ test8[] = { 'x', 'y', 'z' };
+
+ TEST_MAKE_CSTRING_ONE(test1, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL);
+ TEST_MAKE_CSTRING_ONE(test1, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "this is a test");
+ TEST_MAKE_CSTRING_ONE(test1, 0, MAKE_CSTRING_REQUIRE_TRAILING_NUL, "this is a test");
+
+ TEST_MAKE_CSTRING_ONE(test2, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL);
+ TEST_MAKE_CSTRING_ONE(test2, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "");
+ TEST_MAKE_CSTRING_ONE(test2, 0, MAKE_CSTRING_REQUIRE_TRAILING_NUL, "");
+
+ TEST_MAKE_CSTRING_ONE(test3, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL);
+ TEST_MAKE_CSTRING_ONE(test3, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "a");
+ TEST_MAKE_CSTRING_ONE(test3, 0, MAKE_CSTRING_REQUIRE_TRAILING_NUL, "a");
+
+ TEST_MAKE_CSTRING_ONE(test4, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL);
+ TEST_MAKE_CSTRING_ONE(test4, -EINVAL, MAKE_CSTRING_ALLOW_TRAILING_NUL, NULL);
+ TEST_MAKE_CSTRING_ONE(test4, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL);
+
+ TEST_MAKE_CSTRING_ONE(test5, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL);
+ TEST_MAKE_CSTRING_ONE(test5, -EINVAL, MAKE_CSTRING_ALLOW_TRAILING_NUL, NULL);
+ TEST_MAKE_CSTRING_ONE(test5, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL);
+
+ TEST_MAKE_CSTRING_ONE(test6, 0, MAKE_CSTRING_REFUSE_TRAILING_NUL, "");
+ TEST_MAKE_CSTRING_ONE(test6, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "");
+ TEST_MAKE_CSTRING_ONE(test6, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL);
+
+ TEST_MAKE_CSTRING_ONE(test7, 0, MAKE_CSTRING_REFUSE_TRAILING_NUL, "x");
+ TEST_MAKE_CSTRING_ONE(test7, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "x");
+ TEST_MAKE_CSTRING_ONE(test7, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL);
+
+ TEST_MAKE_CSTRING_ONE(test8, 0, MAKE_CSTRING_REFUSE_TRAILING_NUL, "xyz");
+ TEST_MAKE_CSTRING_ONE(test8, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "xyz");
+ TEST_MAKE_CSTRING_ONE(test8, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
assert_se(pos == 1);
}
+TEST(strv_extend_join) {
+ _cleanup_strv_free_ char **v = NULL;
+
+ assert_se(strv_extend_assignment(&v, "MESSAGE", "ABC") >= 0);
+ assert_se(strv_extend_assignment(&v, "ABC", "QER") >= 0);
+ assert_se(strv_extend_assignment(&v, "MISSING", NULL) >= 0);
+
+ assert_se(strv_length(v) == 2);
+ assert_se(streq(v[0], "MESSAGE=ABC"));
+ assert_se(streq(v[1], "ABC=QER"));
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
assert_se(parse_timestamp(buf, &y) >= 0);
assert_se(x == y);
+ assert_se(format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_DATE));
+ log_debug("%s", buf);
+ assert_se(parse_timestamp(buf, &y) >= 0);
+ assert_se(y > usec_sub_unsigned(x, 2 * USEC_PER_DAY) && y < usec_add(x, 2* USEC_PER_DAY));
+
assert_se(format_timestamp_relative(buf, sizeof(buf), x));
log_debug("%s", buf);
assert_se(parse_timestamp(buf, &y) >= 0);
assert_se(streq(buf, "2 weeks 2 days ago"));
}
-static void test_format_timestamp_utc_one(usec_t val, const char *result) {
+static void test_format_timestamp_one(usec_t val, TimestampStyle style, const char *result) {
char buf[FORMAT_TIMESTAMP_MAX];
const char *t;
- t = format_timestamp_style(buf, sizeof(buf), val, TIMESTAMP_UTC);
+ t = format_timestamp_style(buf, sizeof(buf), val, style);
assert_se(streq_ptr(t, result));
}
-TEST(format_timestamp_utc) {
- test_format_timestamp_utc_one(0, NULL);
- test_format_timestamp_utc_one(1, "Thu 1970-01-01 00:00:00 UTC");
- test_format_timestamp_utc_one(USEC_PER_SEC, "Thu 1970-01-01 00:00:01 UTC");
+TEST(format_timestamp_range) {
+ test_format_timestamp_one(0, TIMESTAMP_UTC, NULL);
+ test_format_timestamp_one(0, TIMESTAMP_DATE, NULL);
+ test_format_timestamp_one(0, TIMESTAMP_US_UTC, NULL);
+
+ test_format_timestamp_one(1, TIMESTAMP_UTC, "Thu 1970-01-01 00:00:00 UTC");
+ test_format_timestamp_one(1, TIMESTAMP_DATE, "Thu 1970-01-01");
+ test_format_timestamp_one(1, TIMESTAMP_US_UTC, "Thu 1970-01-01 00:00:00.000001 UTC");
+
+ test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_UTC, "Thu 1970-01-01 00:00:01 UTC");
+ test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_DATE, "Thu 1970-01-01");
+ test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_US_UTC, "Thu 1970-01-01 00:00:01.000000 UTC");
#if SIZEOF_TIME_T == 8
- test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Thu 9999-12-30 23:59:59 UTC");
- test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Thu 9999-12-30 23:59:59 UTC");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Thu 9999-12-30");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_UTC, "--- XXXX-XX-XX XX:XX:XX UTC");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX");
#elif SIZEOF_TIME_T == 4
- test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Tue 2038-01-19 03:14:07 UTC");
- test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Tue 2038-01-19 03:14:07 UTC");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Tue 2038-01-19");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_UTC, "--- XXXX-XX-XX XX:XX:XX UTC");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC");
+ test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX");
#endif
- test_format_timestamp_utc_one(USEC_INFINITY, NULL);
+ test_format_timestamp_one(USEC_INFINITY, TIMESTAMP_UTC, NULL);
}
TEST(deserialize_dual_timestamp) {
test_tpm2_parse_pcrs_one("foo", 0, -EINVAL);
}
+TEST(tpm2_util_pbkdf2_hmac_sha256) {
+
+ /*
+ * The test vectors from RFC 6070 [1] are for dkLen of 20 as it's SHA1
+ * other RFCs I bumped into had various differing dkLen and iter counts,
+ * so this was generated using Python's hmacmodule.
+ *
+ * 1. https://www.rfc-editor.org/rfc/rfc6070.html#page-2
+ */
+ static const struct {
+ const uint8_t pass[256];
+ size_t passlen;
+ const uint8_t salt[256];
+ size_t saltlen;
+ uint8_t expected[SHA256_DIGEST_SIZE];
+ } test_vectors[] = {
+ { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0xCB, 0xEA, 0x27, 0x23, 0x9A, 0x65, 0x99, 0xF6, 0x8C, 0x26, 0x54, 0x80, 0x5C, 0x63, 0x61, 0xD2, 0x91, 0x0A, 0x60, 0x3F, 0xC2, 0xF5, 0xF0, 0xAB, 0x55, 0x8B, 0x46, 0x07, 0x60, 0x93, 0xAB, 0xCB} },
+ { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x2B, 0xDF, 0x52, 0x29, 0x48, 0x3F, 0x98, 0x25, 0x01, 0x19, 0xB4, 0x42, 0xBC, 0xA7, 0x38, 0x5D, 0xCD, 0x08, 0xBD, 0xDC, 0x33, 0xBF, 0x32, 0x5E, 0x31, 0x87, 0x54, 0xFF, 0x2C, 0x23, 0x68, 0xFF} },
+ { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0x7C, 0x24, 0xB4, 0x4D, 0x30, 0x11, 0x53, 0x24, 0x87, 0x56, 0x24, 0x10, 0xBA, 0x9F, 0xF2, 0x4E, 0xBB, 0xF5, 0x03, 0x56, 0x2B, 0xB1, 0xA1, 0x92, 0x8B, 0x5F, 0x32, 0x02, 0x23, 0x1F, 0x79, 0xE6} },
+ { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0xE9, 0x53, 0xB7, 0x1D, 0xAB, 0xD1, 0xC1, 0xF3, 0xC4, 0x7F, 0x18, 0x96, 0xDD, 0xD7, 0x6B, 0xC6, 0x6A, 0xBD, 0xFB, 0x12, 0x7C, 0xF8, 0x68, 0xDC, 0x6E, 0xEF, 0x29, 0xCC, 0x1B, 0x30, 0x5B, 0x74} },
+ { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x51, 0xA3, 0x82, 0xA5, 0x2F, 0x48, 0x84, 0xB3, 0x02, 0x0D, 0xC2, 0x42, 0x9A, 0x8F, 0x86, 0xCC, 0x66, 0xFD, 0x65, 0x87, 0x89, 0x07, 0x2B, 0x07, 0x82, 0x42, 0xD6, 0x6D, 0x43, 0xB8, 0xFD, 0xCF} },
+ { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0xEC, 0xFB, 0x5D, 0x5F, 0xF6, 0xA6, 0xE0, 0x79, 0x50, 0x64, 0x36, 0x64, 0xA3, 0x9A, 0x5C, 0xF3, 0x7A, 0x87, 0x0B, 0x64, 0x51, 0x59, 0x75, 0x64, 0x8B, 0x78, 0x2B, 0x62, 0x8F, 0x68, 0xD9, 0xCC} },
+ { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0x8A, 0x9A, 0x47, 0x9A, 0x91, 0x22, 0x2F, 0x56, 0x29, 0x4F, 0x26, 0x00, 0xE7, 0xB3, 0xEB, 0x63, 0x6D, 0x51, 0xF2, 0x60, 0x17, 0x08, 0x20, 0x70, 0x82, 0x8F, 0xA3, 0xD7, 0xBE, 0x2B, 0xD5, 0x5D} },
+ { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x72, 0x3A, 0xF5, 0xF7, 0xCD, 0x6C, 0x12, 0xDD, 0x53, 0x28, 0x46, 0x0C, 0x19, 0x0E, 0xF2, 0x91, 0xDE, 0xEA, 0xF9, 0x6F, 0x74, 0x32, 0x34, 0x3F, 0x84, 0xED, 0x8D, 0x2A, 0xDE, 0xC9, 0xC6, 0x34} },
+ { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0xE3, 0x07, 0x12, 0xBE, 0xEE, 0xF5, 0x5D, 0x18, 0x72, 0xF4, 0xCF, 0xF1, 0x20, 0x6B, 0xD6, 0x66, 0xCD, 0x7C, 0xE7, 0x4F, 0xC2, 0x16, 0x70, 0x5B, 0x9B, 0x2F, 0x7D, 0xE2, 0x3B, 0x42, 0x3A, 0x1B} },
+ };
+
+ uint8_t res[SHA256_DIGEST_SIZE];
+ for(size_t i = 0; i < sizeof(test_vectors)/sizeof(test_vectors[0]); i++) {
+
+ int rc = tpm2_util_pbkdf2_hmac_sha256(
+ test_vectors[i].pass,
+ test_vectors[i].passlen,
+ test_vectors[i].salt,
+ test_vectors[i].saltlen,
+ res);
+ assert_se(rc == 0);
+ assert_se(memcmp(test_vectors[i].expected, res, SHA256_DIGEST_SIZE) == 0);
+ }
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
*user, *group, *uid, *gid, *home, *shell,
*tmp_dir, *var_tmp_dir;
_cleanup_(manager_freep) Manager *m = NULL;
+ _cleanup_close_ int fd = -EBADF;
Unit *u;
int r;
_cleanup_(unlink_tempfilep) char filename[] = "/tmp/test-unit_printf.XXXXXX";
- assert_se(mkostemp_safe(filename) >= 0);
+ fd = mkostemp_safe(filename);
+ assert_se(fd >= 0);
/* Using the specifier functions is admittedly a bit circular, but we don't want to reimplement the
* logic a second time. We're at least testing that the hookup works. */
memcpy(&a->sockaddr, sockaddr, socklen);
- LIST_FIND_TAIL(addresses, n->addresses, tail);
+ tail = LIST_FIND_TAIL(addresses, n->addresses);
LIST_INSERT_AFTER(addresses, n->addresses, tail, a);
if (ret)
#include "sd-daemon.h"
#include "sd-event.h"
+#include "sd-messages.h"
#include "capability-util.h"
#include "clock-util.h"
#include "timesyncd-manager.h"
#include "user-util.h"
+static int advance_tstamp(int fd, const struct stat *st) {
+ assert_se(fd >= 0);
+ assert_se(st);
+
+ /* So here's the problem: whenever we read the timestamp we'd like to ensure the next time we won't
+ * restore the exact same time again, but one at least one step further (so that comparing mtimes of
+ * the timestamp file is a reliable check that timesync did its thing). But file systems have
+ * different timestamp accuracy: traditional fat has 2s granularity, and even ext2 and friends expose
+ * different granularity depending on selected inode size during formatting! Hence, to ensure the
+ * timestamp definitely is increased, here's what we'll do: we'll first try to increase the timestamp
+ * by 1µs, write that and read it back. If it was updated, great. But if it was not, we'll instead
+ * increase the timestamp by 10µs, and do the same, then 100µs, then 1ms, and so on, until it works,
+ * or we reach 10s. If it still didn't work then, the fs is just broken and we give up. */
+
+ usec_t target = MAX3(now(CLOCK_REALTIME),
+ TIME_EPOCH * USEC_PER_SEC,
+ timespec_load(&st->st_mtim));
+
+ for (usec_t a = 1; a <= 10 * USEC_PER_SEC; a *= 10) { /* 1µs, 10µs, 100µs, 1ms, … 10s */
+ struct timespec ts[2];
+ struct stat new_st;
+
+ /* Bump to the maximum of the old timestamp advanced by the specified unit, */
+ usec_t c = usec_add(target, a);
+
+ timespec_store(&ts[0], c);
+ ts[1] = ts[0];
+
+ if (futimens(fd, ts) < 0) {
+ /* If this doesn't work at all, log, don't fail but give up */
+ log_warning_errno(errno, "Unable to update mtime of timestamp file, ignoring: %m");
+ return 0;
+ }
+
+ if (fstat(fd, &new_st) < 0)
+ return log_error_errno(errno, "Failed to stat timestamp file: %m");
+
+ if (timespec_load(&new_st.st_mtim) > target) {
+ log_debug("Successfully bumped timestamp file.");
+ return 1;
+ }
+
+ log_debug("Tried to advance timestamp file by " USEC_FMT ", but this didn't work, file system timestamp granularity too coarse?", a);
+ }
+
+ log_debug("Gave up trying to advance timestamp file.");
+ return 0;
+}
+
static int load_clock_timestamp(uid_t uid, gid_t gid) {
usec_t min = TIME_EPOCH * USEC_PER_SEC, ct;
_cleanup_close_ int fd = -EBADF;
int r;
- /* Let's try to make sure that the clock is always
- * monotonically increasing, by saving the clock whenever we
- * have a new NTP time, or when we shut down, and restoring it
- * when we start again. This is particularly helpful on
- * systems lacking a battery backed RTC. We also will adjust
- * the time to at least the build time of systemd. */
+ /* Let's try to make sure that the clock is always monotonically increasing, by saving the clock
+ * whenever we have a new NTP time, or when we shut down, and restoring it when we start again. This
+ * is particularly helpful on systems lacking a battery backed RTC. We also will adjust the time to
+ * at least the build time of systemd. */
fd = open(CLOCK_FILE, O_RDWR|O_CLOEXEC, 0644);
- if (fd >= 0) {
- struct stat st;
- usec_t stamp;
+ if (fd < 0) {
+ if (errno != ENOENT)
+ log_debug_errno(errno, "Unable to open timestamp file '" CLOCK_FILE "', ignoring: %m");
- /* check if the recorded time is later than the compiled-in one */
- if (fstat(fd, &st) >= 0) {
- stamp = timespec_load(&st.st_mtim);
- if (stamp > min)
- min = stamp;
- }
-
- if (geteuid() == 0) {
- /* Try to fix the access mode, so that we can still
- touch the file after dropping privileges */
- r = fchmod_and_chown(fd, 0644, uid, gid);
- if (r < 0)
- log_warning_errno(r, "Failed to chmod or chown %s, ignoring: %m", CLOCK_FILE);
- }
-
- } else {
r = mkdir_safe_label(STATE_DIR, 0755, uid, gid,
MKDIR_FOLLOW_SYMLINK | MKDIR_WARN_MODE);
- if (r < 0) {
+ if (r < 0)
log_debug_errno(r, "Failed to create state directory, ignoring: %m");
- goto settime;
- }
/* create stamp file with the compiled-in date */
r = touch_file(CLOCK_FILE, /* parents= */ false, min, uid, gid, 0644);
if (r < 0)
log_debug_errno(r, "Failed to create %s, ignoring: %m", CLOCK_FILE);
- }
+ } else {
+ struct stat st;
+ usec_t stamp;
-settime:
- ct = now(CLOCK_REALTIME);
- if (ct < min) {
- char date[FORMAT_TIMESTAMP_MAX];
+ /* check if the recorded time is later than the compiled-in one */
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Unable to stat timestamp file '" CLOCK_FILE "': %m");
- log_info("System clock time unset or jumped backwards, restoring from recorded timestamp: %s",
- format_timestamp(date, sizeof(date), min));
+ stamp = timespec_load(&st.st_mtim);
+ if (stamp > min)
+ min = stamp;
+
+ /* Try to fix the access mode, so that we can still touch the file after dropping
+ * privileges */
+ r = fchmod_and_chown(fd, 0644, uid, gid);
+ if (r < 0)
+ log_full_errno(ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r,
+ "Failed to chmod or chown %s, ignoring: %m", CLOCK_FILE);
- if (clock_settime(CLOCK_REALTIME, TIMESPEC_STORE(min)) < 0)
- log_error_errno(errno, "Failed to restore system clock, ignoring: %m");
+ (void) advance_tstamp(fd, &st);
+ }
+
+ ct = now(CLOCK_REALTIME);
+ if (ct > min)
+ return 0;
+
+ /* Not that it matters much, but we actually restore the clock to n+1 here rather than n, simply
+ * because we read n as time previously already and we want to progress here, i.e. not report the
+ * same time again. */
+ if (clock_settime(CLOCK_REALTIME, TIMESPEC_STORE(min+1)) < 0) {
+ log_warning_errno(errno, "Failed to restore system clock, ignoring: %m");
+ return 0;
}
+ log_struct(LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_TIME_BUMP_STR,
+ "REALTIME_USEC=" USEC_FMT, min+1,
+ LOG_MESSAGE("System clock time unset or jumped backwards, restored from recorded timestamp: %s",
+ FORMAT_TIMESTAMP(min+1)));
return 0;
}
return NULL;
free(config->filename);
+ strv_free(config->dropins);
net_match_clear(&config->match);
condition_free_list(config->conditions);
"SR-IOV\0",
config_item_perf_lookup, link_config_gperf_lookup,
CONFIG_PARSE_WARN, config, &stats_by_path,
- NULL);
+ &config->dropins);
if (r < 0)
return r; /* config_parse_many() logs internally. */
struct LinkConfig {
char *filename;
+ char **dropins;
NetMatch match;
LIST_HEAD(Condition, conditions);
#include "alloc-util.h"
#include "device-util.h"
+#include "escape.h"
#include "errno-util.h"
#include "link-config.h"
#include "log.h"
#include "string-util.h"
+#include "strv.h"
#include "udev-builtin.h"
static LinkConfigContext *ctx = NULL;
static int builtin_net_setup_link(sd_device *dev, sd_netlink **rtnl, int argc, char **argv, bool test) {
_cleanup_(link_freep) Link *link = NULL;
+ _cleanup_free_ char *joined = NULL;
int r;
if (argc > 1)
if (link->new_name)
udev_builtin_add_property(dev, test, "ID_NET_NAME", link->new_name);
+ STRV_FOREACH(d, link->config->dropins) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = xescape(*d, ":");
+ if (!escaped)
+ return log_oom();
+
+ if (!strextend_with_separator(&joined, ":", escaped))
+ return log_oom();
+ }
+
+ udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE_DROPINS", joined);
+
return 0;
}
#include "dirent-util.h"
#include "escape.h"
#include "fd-util.h"
+#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
return 0;
}
-static int stack_directory_read_one(int dirfd, const char *id, bool is_symlink, char **devnode, int *priority) {
+static int stack_directory_read_one(int dirfd, const char *id, char **devnode, int *priority) {
+ _cleanup_free_ char *buf = NULL;
int tmp_prio, r;
assert(dirfd >= 0);
assert(devnode);
assert(priority);
- if (is_symlink) {
- _cleanup_free_ char *buf = NULL;
- char *colon;
+ /* First, let's try to read the entry with the new format, which should replace the old format pretty
+ * quickly. */
- /* New format. The devnode and priority can be obtained from symlink. */
+ r = readlinkat_malloc(dirfd, id, &buf);
+ if (r >= 0) {
+ char *colon;
- r = readlinkat_malloc(dirfd, id, &buf);
- if (r < 0)
- return r;
+ /* With the new format, the devnode and priority can be obtained from symlink itself. */
colon = strchr(buf, ':');
if (!colon || colon == buf)
* symlink will be removed during processing the event. The check is just for shortening the
* timespan that the symlink points to a non-existing device node. */
if (access(colon + 1, F_OK) < 0)
- return -errno;
+ return -ENODEV;
r = safe_atoi(buf, &tmp_prio);
if (r < 0)
if (r < 0)
return r;
- } else {
+ } else if (r == -EINVAL) { /* Not a symlink ? try the old format */
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
const char *val;
r = free_and_strdup(devnode, val);
if (r < 0)
return r;
- }
+
+ } else
+ return r == -ENOENT ? -ENODEV : r;
*priority = tmp_prio;
return 1; /* Updated */
}
-static int stack_directory_find_prioritized_devnode(sd_device *dev, const char *dirname, bool add, char **ret) {
+static int stack_directory_find_prioritized_devnode(sd_device *dev, int dirfd, bool add, char **ret) {
_cleanup_closedir_ DIR *dir = NULL;
_cleanup_free_ char *devnode = NULL;
int r, priority = 0;
const char *id;
assert(dev);
- assert(dirname);
+ assert(dirfd >= 0);
assert(ret);
/* Find device node of device with highest priority. This returns 1 if a device found, 0 if no
return -ENOMEM;
}
- dir = opendir(dirname);
+ dir = xopendirat(dirfd, ".", O_NOFOLLOW);
if (!dir)
return -errno;
if (r < 0)
return r;
- FOREACH_DIRENT_ALL(de, dir, break) {
- if (de->d_name[0] == '.')
- continue;
+ FOREACH_DIRENT(de, dir, break) {
/* skip ourself */
if (streq(de->d_name, id))
continue;
- if (!IN_SET(de->d_type, DT_LNK, DT_REG))
- continue;
-
- r = stack_directory_read_one(dirfd(dir), de->d_name, /* is_symlink = */ de->d_type == DT_LNK, &devnode, &priority);
- if (r < 0) {
- log_debug_errno(r, "Failed to read '%s/%s', ignoring: %m", dirname, de->d_name);
- continue;
- }
+ r = stack_directory_read_one(dirfd, de->d_name, &devnode, &priority);
+ if (r < 0 && r != -ENODEV)
+ log_debug_errno(r, "Failed to read '%s', ignoring: %m", de->d_name);
}
*ret = TAKE_PTR(devnode);
return 1; /* Updated. */
}
-static int stack_directory_open(const char *dirname) {
- _cleanup_close_ int fd = -EBADF;
- int r;
-
- assert(dirname);
-
- r = mkdir_parents(dirname, 0755);
- if (r < 0)
- return r;
-
- fd = open_mkdir_at(AT_FDCWD, dirname, O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW | O_RDONLY, 0755);
- if (fd < 0)
- return fd;
-
- return TAKE_FD(fd);
-}
-
-static int stack_directory_lock(int dirfd) {
- _cleanup_close_ int fd = -EBADF;
-
- assert(dirfd >= 0);
-
- fd = openat(dirfd, ".lock", O_CLOEXEC | O_NOFOLLOW | O_RDONLY | O_CREAT, 0600);
- if (fd < 0)
- return -errno;
-
- if (flock(fd, LOCK_EX) < 0)
- return -errno;
-
- return TAKE_FD(fd);
-}
-
size_t udev_node_escape_path(const char *src, char *dest, size_t size) {
size_t i, j;
uint64_t h;
return 0;
}
-static int link_update(sd_device *dev, const char *slink, bool add) {
- _cleanup_free_ char *dirname = NULL, *devnode = NULL;
+static int stack_directory_open(sd_device *dev, const char *slink, int *ret_dirfd, int *ret_lockfd) {
_cleanup_close_ int dirfd = -EBADF, lockfd = -EBADF;
+ _cleanup_free_ char *dirname = NULL;
int r;
assert(dev);
assert(slink);
+ assert(ret_dirfd);
+ assert(ret_lockfd);
r = stack_directory_get_name(slink, &dirname);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to build stack directory name for '%s': %m", slink);
- dirfd = stack_directory_open(dirname);
+ r = mkdir_parents(dirname, 0755);
+ if (r < 0)
+ return log_device_debug_errno(dev, r, "Failed to create stack directory '%s': %m", dirname);
+
+ dirfd = open_mkdir_at(AT_FDCWD, dirname, O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW | O_RDONLY, 0755);
if (dirfd < 0)
return log_device_debug_errno(dev, dirfd, "Failed to open stack directory '%s': %m", dirname);
- lockfd = stack_directory_lock(dirfd);
+ lockfd = openat(dirfd, ".lock", O_CLOEXEC | O_NOFOLLOW | O_RDONLY | O_CREAT, 0600);
if (lockfd < 0)
- return log_device_debug_errno(dev, lockfd, "Failed to lock stack directory '%s': %m", dirname);
+ return log_device_debug_errno(dev, errno, "Failed to create lock file for stack directory '%s': %m", dirname);
+
+ if (flock(lockfd, LOCK_EX) < 0)
+ return log_device_debug_errno(dev, errno, "Failed to place a lock on lock file for %s: %m", dirname);
+
+ *ret_dirfd = TAKE_FD(dirfd);
+ *ret_lockfd = TAKE_FD(lockfd);
+ return 0;
+}
+
+static int link_update(sd_device *dev, const char *slink, bool add) {
+ _cleanup_close_ int dirfd = -EBADF, lockfd = -EBADF;
+ _cleanup_free_ char *devnode = NULL;
+ int r;
+
+ assert(dev);
+ assert(slink);
+
+ r = stack_directory_open(dev, slink, &dirfd, &lockfd);
+ if (r < 0)
+ return r;
r = stack_directory_update(dev, dirfd, add);
if (r < 0)
- return log_device_debug_errno(dev, r, "Failed to update stack directory '%s': %m", dirname);
+ return log_device_debug_errno(dev, r, "Failed to update stack directory for '%s': %m", slink);
- r = stack_directory_find_prioritized_devnode(dev, dirname, add, &devnode);
+ r = stack_directory_find_prioritized_devnode(dev, dirfd, add, &devnode);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to determine device node with the highest priority for '%s': %m", slink);
if (r > 0)
} else if (streq(key, "SYMLINK")) {
if (attr)
return log_token_invalid_attr(rules, key);
- if (op == OP_REMOVE)
- return log_token_invalid_op(rules, key);
-
if (!is_match) {
check_value_format_and_warn(rules, key, value, false);
r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL);
if (truncated)
continue;
- r = device_add_devlink(dev, filename);
- if (r < 0)
- return log_rule_error_errno(dev, rules, r, "Failed to add devlink '%s': %m", filename);
+ if (token->op == OP_REMOVE) {
+ device_remove_devlink(dev, filename);
+ log_rule_debug(dev, rules, "Dropped SYMLINK '%s'", p);
+ } else {
+ r = device_add_devlink(dev, filename);
+ if (r < 0)
+ return log_rule_error_errno(dev, rules, r, "Failed to add devlink '%s': %m", filename);
+
+ log_rule_debug(dev, rules, "Added SYMLINK '%s'", p);
+ }
- log_rule_debug(dev, rules, "LINK '%s'", p);
p = next;
}
break;
#include "device-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "id128-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "set.h"
+#include "static-destruct.h"
#include "string-util.h"
#include "strv.h"
#include "udevadm.h"
static bool arg_dry_run = false;
static bool arg_quiet = false;
static bool arg_uuid = false;
+static bool arg_settle = false;
static int exec_list(
sd_device_enumerator *e,
sd_device_action_t action,
- Hashmap *settle_hashmap) {
+ Set **ret_settle_path_or_ids) {
- bool skip_uuid_logic = false;
+ _cleanup_set_free_ Set *settle_path_or_ids = NULL;
+ int uuid_supported = -1;
const char *action_str;
sd_device *d;
int r, ret = 0;
+ assert(e);
+
action_str = device_action_to_string(action);
FOREACH_DEVICE_AND_SUBSYSTEM(e, d) {
/* Use the UUID mode if the user explicitly asked for it, or if --settle has been specified,
* so that we can recognize our own uevent. */
- r = sd_device_trigger_with_uuid(d, action, (arg_uuid || settle_hashmap) && !skip_uuid_logic ? &id : NULL);
- if (r == -EINVAL && !arg_uuid && settle_hashmap && !skip_uuid_logic) {
+ r = sd_device_trigger_with_uuid(d, action, (arg_uuid || arg_settle) && uuid_supported != 0 ? &id : NULL);
+ if (r == -EINVAL && !arg_uuid && arg_settle && uuid_supported < 0) {
/* If we specified a UUID because of the settling logic, and we got EINVAL this might
* be caused by an old kernel which doesn't know the UUID logic (pre-4.13). Let's try
* if it works without the UUID logic then. */
r = sd_device_trigger(d, action);
if (r != -EINVAL)
- skip_uuid_logic = true; /* dropping the uuid stuff changed the return code,
+ uuid_supported = false; /* dropping the uuid stuff changed the return code,
* hence don't bother next time */
}
if (r < 0) {
continue;
}
+ if (uuid_supported < 0)
+ uuid_supported = true;
+
/* If the user asked for it, write event UUID to stdout */
if (arg_uuid)
printf(SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id));
- if (settle_hashmap) {
- _cleanup_free_ sd_id128_t *mid = NULL;
- _cleanup_free_ char *sp = NULL;
+ if (arg_settle) {
+ if (uuid_supported) {
+ sd_id128_t *dup;
- sp = strdup(syspath);
- if (!sp)
- return log_oom();
+ dup = newdup(sd_id128_t, &id, 1);
+ if (!dup)
+ return log_oom();
- mid = newdup(sd_id128_t, &id, 1);
- if (!d)
- return log_oom();
+ r = set_ensure_consume(&settle_path_or_ids, &id128_hash_ops_free, dup);
+ } else {
+ char *dup;
- r = hashmap_put(settle_hashmap, sp, mid);
+ dup = strdup(syspath);
+ if (!dup)
+ return log_oom();
+
+ r = set_ensure_consume(&settle_path_or_ids, &path_hash_ops_free, dup);
+ }
if (r < 0)
return log_oom();
-
- TAKE_PTR(sp);
- TAKE_PTR(mid);
}
}
+ if (ret_settle_path_or_ids)
+ *ret_settle_path_or_ids = TAKE_PTR(settle_path_or_ids);
+
return ret;
}
static int device_monitor_handler(sd_device_monitor *m, sd_device *dev, void *userdata) {
- Hashmap *settle_hashmap = ASSERT_PTR(userdata);
- sd_id128_t *settle_id;
+ Set *settle_path_or_ids = * (Set**) ASSERT_PTR(userdata);
const char *syspath;
- char *k;
+ sd_id128_t id;
int r;
assert(dev);
r = sd_device_get_syspath(dev, &syspath);
if (r < 0) {
- log_debug_errno(r, "Failed to get syspath of device event, ignoring: %m");
+ log_device_debug_errno(dev, r, "Failed to get syspath of device event, ignoring: %m");
return 0;
}
- settle_id = hashmap_get2(settle_hashmap, syspath, (void**) &k);
- if (!settle_id) {
- log_debug("Got uevent for unexpected device '%s', ignoring.", syspath);
- return 0;
- }
- if (!sd_id128_is_null(*settle_id)) { /* If this is SD_ID128_NULL then we are on pre-4.13 and have no UUID to check, hence don't */
- sd_id128_t event_id;
+ if (sd_device_get_trigger_uuid(dev, &id) >= 0) {
+ _cleanup_free_ sd_id128_t *saved = NULL;
- r = sd_device_get_trigger_uuid(dev, &event_id);
- if (r < 0) {
- log_debug_errno(r, "Got uevent without synthetic UUID for device '%s', ignoring: %m", syspath);
+ saved = set_remove(settle_path_or_ids, &id);
+ if (!saved) {
+ log_device_debug(dev, "Got uevent not matching expected UUID, ignoring.");
return 0;
}
-
- if (!sd_id128_equal(event_id, *settle_id)) {
- log_debug("Got uevent not matching expected UUID for device '%s', ignoring.", syspath);
+ } else {
+ _cleanup_free_ char *saved = NULL;
+
+ saved = set_remove(settle_path_or_ids, syspath);
+ if (!saved) {
+ const char *old_sysname;
+
+ /* When the device is renamed, the new name is broadcast, and the old name is saved
+ * in INTERFACE_OLD. */
+
+ if (sd_device_get_property_value(dev, "INTERFACE_OLD", &old_sysname) >= 0) {
+ _cleanup_free_ char *dir = NULL, *old_syspath = NULL;
+
+ r = path_extract_directory(syspath, &dir);
+ if (r < 0) {
+ log_device_debug_errno(dev, r,
+ "Failed to extract directory from '%s', ignoring: %m",
+ syspath);
+ return 0;
+ }
+
+ old_syspath = path_join(dir, old_sysname);
+ if (!old_syspath) {
+ log_oom_debug();
+ return 0;
+ }
+
+ saved = set_remove(settle_path_or_ids, old_syspath);
+ }
+ }
+ if (!saved) {
+ log_device_debug(dev, "Got uevent for unexpected device, ignoring.");
return 0;
}
}
printf("settle %s\n", syspath);
if (arg_uuid)
- printf("settle " SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(*settle_id));
+ printf("settle " SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id));
- free(hashmap_remove(settle_hashmap, syspath));
- free(k);
-
- if (hashmap_isempty(settle_hashmap))
+ if (set_isempty(settle_path_or_ids))
return sd_event_exit(sd_device_monitor_get_event(m), 0);
return 0;
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
_cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
- _cleanup_hashmap_free_ Hashmap *settle_hashmap = NULL;
+ _cleanup_set_free_ Set *settle_path_or_ids = NULL;
usec_t ping_timeout_usec = 5 * USEC_PER_SEC;
- bool settle = false, ping = false;
+ bool ping = false;
int c, r;
if (running_in_chroot() > 0) {
break;
}
case 'w':
- settle = true;
+ arg_settle = true;
break;
case ARG_NAME: {
return log_error_errno(r, "Failed to add parent match '%s': %m", argv[optind]);
}
- if (settle) {
- settle_hashmap = hashmap_new(&path_hash_ops_free_free);
- if (!settle_hashmap)
- return log_oom();
-
+ if (arg_settle) {
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to get default event: %m");
if (r < 0)
return log_error_errno(r, "Failed to attach event to device monitor: %m");
- r = sd_device_monitor_start(m, device_monitor_handler, settle_hashmap);
+ r = sd_device_monitor_start(m, device_monitor_handler, &settle_path_or_ids);
if (r < 0)
return log_error_errno(r, "Failed to start device monitor: %m");
}
assert_not_reached();
}
- r = exec_list(e, action, settle_hashmap);
+ r = exec_list(e, action, arg_settle ? &settle_path_or_ids : NULL);
if (r < 0)
return r;
- if (event && !hashmap_isempty(settle_hashmap)) {
+ if (!set_isempty(settle_path_or_ids)) {
r = sd_event_loop(event);
if (r < 0)
return log_error_errno(r, "Event loop failed: %m");
import typing
-__version__ = '{{GIT_VERSION}}'
+__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})'
EFI_ARCH_MAP = {
# host_arch glob : [efi_arch, 32_bit_efi_arch if mixed mode is supported]
return ' '.join(shlex.quote(str(x)) for x in cmd)
-def path_is_readable(s: str | None) -> pathlib.Path | None:
+def path_is_readable(s: typing.Optional[str]) -> typing.Optional[pathlib.Path]:
"""Convert a filename string to a Path and verify access."""
if s is None:
return None
class Section:
name: str
content: pathlib.Path
- tmpfile: typing.IO | None = None
+ tmpfile: typing.Optional[typing.IO] = None
flags: list[str] = dataclasses.field(default_factory=lambda: ['data', 'readonly'])
- offset: int | None = None
+ offset: typing.Optional[int] = None
measure: bool = False
@classmethod
def create(cls, name, contents, **kwargs):
- if isinstance(contents, str | bytes):
+ if isinstance(contents, (str, bytes)):
mode = 'wt' if isinstance(contents, str) else 'wb'
tmp = tempfile.NamedTemporaryFile(mode=mode, prefix=f'tmp{name}')
tmp.write(contents)
@dataclasses.dataclass
class UKI:
- executable: list[pathlib.Path|str]
+ executable: list[typing.Union[pathlib.Path, str]]
sections: list[Section] = dataclasses.field(default_factory=list, init=False)
- offset: int | None = dataclasses.field(default=None, init=False)
+ offset: typing.Optional[int] = dataclasses.field(default=None, init=False)
def __post_init__(self):
self.offset = round_up(pe_next_section_offset(self.executable))
def join_initrds(initrds):
- match initrds:
- case []:
- return None
- case [initrd]:
- return initrd
- case multiple:
- seq = []
- for file in multiple:
- initrd = file.read_bytes()
- padding = b'\0' * round_up(len(initrd), 4) # pad to 32 bit alignment
- seq += [initrd, padding]
-
- return b''.join(seq)
-
- assert False
+ if len(initrds) == 0:
+ return None
+ elif len(initrds) == 1:
+ return initrds[0]
+
+ seq = []
+ for file in initrds:
+ initrd = file.read_bytes()
+ padding = b'\0' * round_up(len(initrd), 4) # pad to 32 bit alignment
+ seq += [initrd, padding]
+
+ return b''.join(seq)
def pe_validate(filename):
In some situations one may want/need to run one of the tests run by Ubuntu CI
locally for debugging purposes. For this, you need a machine (or a VM) with
-the same Ubuntu release as is used by Ubuntu CI (Focal ATTOW).
+the same Ubuntu release as is used by Ubuntu CI (Jammy ATTOW).
First of all, clone the Debian systemd repository and sync it with the code of
the PR (set by the $UPSTREAM_PULL_REQUEST env variable) you'd like to debug:
## PPA with some newer Ubuntu packages required by upstream systemd
# add-apt-repository -y ppa:upstream-systemd-ci/systemd-ci
# apt build-dep -y systemd
-# apt install -y autopkgtest debhelper genisoimage git qemu-system-x86 \
- libzstd-dev libfdisk-dev libtss2-dev libfido2-dev libssl-dev \
- python3-jinja2 zstd
+# apt install -y autopkgtest debhelper genisoimage git qemu-system-x86
Build systemd deb packages with debug info:
-# DEB_BUILD_OPTIONS="nocheck nostrip" dpkg-buildpackage -us -uc
+# TEST_UPSTREAM=1 DEB_BUILD_OPTIONS="nocheck nostrip" dpkg-buildpackage -us -uc
# cd ..
Prepare a testbed image for autopkgtest (tweak the release as necessary):
-# autopkgtest-buildvm-ubuntu-cloud -v -a amd64 -r focal
+# autopkgtest-buildvm-ubuntu-cloud --ram-size 1024 -v -a amd64 -r jammy
And finally run the autopkgtest itself:
--timeout-factor=3 \
--test-name=boot-and-services \
--shell-fail \
- -- autopkgtest-virt-qemu autopkgtest-focal-amd64.img
+ -- autopkgtest-virt-qemu --ram-size 2048 autopkgtest-jammy-amd64.img
where --test-name= is the name of the test you want to run/debug. The
--shell-fail option will pause the execution in case the test fails and shows
install_dmevent
generate_module_dependencies
inst_binary tpm2_pcrextend
+ inst_binary tpm2_pcrread
inst_binary openssl
}
self.assertIn('nameserver 192.168.42.1\n', contents)
self.assertIn('nameserver 127.0.0.1\n', contents)
+ out = subprocess.check_output(['networkctl', 'status', 'dummy0'])
+ self.assertIn(b'test.network.d/dns.conf', out)
+
def test_dhcp_timezone(self):
'''networkd sets time zone from DHCP'''
</policy>
</busconfig>
EOF
+
+ # If we run without KVM, bump the service start timeout
+ if ! get_bool "$QEMU_KVM"; then
+ cat >"$initdir/etc/dbus-1/system.d/service.timeout.conf" <<EOF
+<?xml version="1.0"?>
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <limit name="service_start_timeout">60000</limit>
+</busconfig>
+EOF
+ fi
}
install_user_dbus() {
[Network]
DHCP=ipv4
IPv6AcceptRA=no
-Address=192.168.5.250
+Address=192.168.5.250/24
[DHCPv4]
UseDomains=yes
+++ /dev/null
-# SPDX-License-Identifier: LGPL-2.1-or-later
-[Match]
-Name=veth99
-
-[Network]
-DHCP=ipv4
-IPv6AcceptRA=no
-Address=192.168.5.250
self.assertRegex(output, r'Link File: /run/systemd/network/25-default.link')
self.assertRegex(output, r'Network File: /run/systemd/network/11-dummy.network')
+ # This test may be run on the system that has older udevd than 70f32a260b5ebb68c19ecadf5d69b3844896ba55 (v249).
+ # In that case, the udev DB for the loopback network interface may already have ID_NET_LINK_FILE property.
+ # Let's reprocess the interface and drop the property.
+ check_output(*udevadm_cmd, 'trigger', '--settle', '--action=add', '/sys/class/net/lo')
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'lo', env=env)
print(output)
self.assertRegex(output, r'Link File: n/a')
{
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
exp_links => ["boot_disk1", "boot_diskXY1"],
- not_exp_links => ["boot_diskXX1"],
+ not_exp_links => ["boot_diskXX1", "hoge"],
}],
rules => <<EOF
SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n"
-SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n", SYMLINK+="hoge"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK-="hoge"
EOF
},
{
udevadm control --log-priority=debug --reload --timeout=30
-# FIXME(?): the 'add' uevent is broadcast as for 'foobar', instead of 'hoge'. Hence, we cannot use --settle here.
-# See issue #25115.
-udevadm trigger --action=add /sys/devices/virtual/net/hoge
+udevadm trigger --action=add --settle /sys/devices/virtual/net/hoge
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/foobar
assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/foobar)"
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "active" ]]; do sleep .5; done'
-udevadm trigger --action=add /sys/devices/virtual/net/foobar
+udevadm trigger --action=add --settle /sys/devices/virtual/net/foobar
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
systemctl list-jobs --before
systemctl list-jobs --after --before
systemctl list-jobs "*"
+systemctl list-dependencies sysinit.target --type=socket,mount
+systemctl list-dependencies multi-user.target --state=active
+systemctl list-dependencies sysinit.target --state=mounted --all
# is-* verbs
# Should return 4 for a missing unit file
systemctl status "systemd-journald*.socket"
systemctl status "sys-devices-*-ttyS0.device"
systemctl status -- -.mount
+systemctl status 1
# --marked
systemctl restart "$UNIT_NAME"
# test for sysv-generator (issue #24990)
if [[ -x /usr/lib/systemd/system-generators/systemd-sysv-generator ]]; then
+ # This is configurable via -Dsysvinit-path=, but we can't get the value
+ # at runtime, so let's just support the two most common paths for now.
+ [[ -d /etc/rc.d/init.d ]] && SYSVINIT_PATH="/etc/rc.d/init.d" || SYSVINIT_PATH="/etc/init.d"
# invalid dependency
- cat >/etc/init.d/issue-24990 <<\EOF
+ cat >"${SYSVINIT_PATH:?}/issue-24990" <<\EOF
#!/bin/bash
### BEGIN INIT INFO
esac
EOF
- chmod +x /etc/init.d/issue-24990
+ chmod +x "$SYSVINIT_PATH/issue-24990"
systemctl daemon-reload
[[ -L /run/systemd/generator.late/test1.service ]]
[[ -L /run/systemd/generator.late/test2.service ]]
assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service"
assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service"
output=$(systemctl cat issue-24990)
- assert_in "SourcePath=/etc/init.d/issue-24990" "$output"
+ assert_in "SourcePath=$SYSVINIT_PATH/issue-24990" "$output"
assert_in "Description=LSB: Test" "$output"
assert_in "After=test1.service" "$output"
assert_in "After=remote-fs.target" "$output"
assert_in "After=network-online.target" "$output"
assert_in "Wants=network-online.target" "$output"
- assert_in "ExecStart=/etc/init.d/issue-24990 start" "$output"
- assert_in "ExecStop=/etc/init.d/issue-24990 stop" "$output"
+ assert_in "ExecStart=$SYSVINIT_PATH/issue-24990 start" "$output"
+ assert_in "ExecStop=$SYSVINIT_PATH/issue-24990 stop" "$output"
systemctl status issue-24990 || :
systemctl show issue-24990
assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)"
systemctl stop issue-24990
# valid dependency
- cat >/etc/init.d/issue-24990 <<\EOF
+ cat >"$SYSVINIT_PATH/issue-24990" <<\EOF
#!/bin/bash
### BEGIN INIT INFO
esac
EOF
- chmod +x /etc/init.d/issue-24990
+ chmod +x "$SYSVINIT_PATH/issue-24990"
systemctl daemon-reload
[[ -L /run/systemd/generator.late/test1.service ]]
[[ -L /run/systemd/generator.late/test2.service ]]
assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service"
assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service"
output=$(systemctl cat issue-24990)
- assert_in "SourcePath=/etc/init.d/issue-24990" "$output"
+ assert_in "SourcePath=$SYSVINIT_PATH/issue-24990" "$output"
assert_in "Description=LSB: Test" "$output"
assert_in "After=remote-fs.target" "$output"
- assert_in "ExecStart=/etc/init.d/issue-24990 start" "$output"
- assert_in "ExecStop=/etc/init.d/issue-24990 stop" "$output"
+ assert_in "ExecStart=$SYSVINIT_PATH/issue-24990 start" "$output"
+ assert_in "ExecStop=$SYSVINIT_PATH/issue-24990 stop" "$output"
systemctl status issue-24990 || :
systemctl show issue-24990
assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)"
systemd-analyze time || :
systemd-analyze blame || :
systemd-analyze critical-chain || :
+# plot
systemd-analyze plot >/dev/null || :
+systemd-analyze plot --json=pretty >/dev/null || :
+systemd-analyze plot --json=short >/dev/null || :
+systemd-analyze plot --json=off >/dev/null || :
+systemd-analyze plot --json=pretty --no-legend >/dev/null || :
+systemd-analyze plot --json=short --no-legend >/dev/null || :
+systemd-analyze plot --json=off --no-legend >/dev/null || :
+systemd-analyze plot --table >/dev/null || :
+systemd-analyze plot --table --no-legend >/dev/null || :
# legacy/deprecated options (moved to systemctl, but still usable from analyze)
systemd-analyze log-level
systemd-analyze log-level "$(systemctl log-level)"
echo "/usr/lib/systemd/systemd-measure or PCR sysfs files not found, skipping signed PCR policy test case"
fi
+if [ -e /usr/lib/systemd/systemd-pcrphase ] && \
+ [ -f /sys/class/tpm/tpm0/pcr-sha256/11 ]; then
+
+ # Let's measure the machine ID
+ tpm2_pcrread sha256:15 -Q -o /tmp/oldpcr15
+ mv /etc/machine-id /etc/machine-id.save
+ echo 994013bf23864ee7992eab39a96dd3bb >/etc/machine-id
+ SYSTEMD_FORCE_MEASURE=1 /usr/lib/systemd/systemd-pcrphase --machine-id
+ mv /etc/machine-id.save /etc/machine-id
+ tpm2_pcrread sha256:15 -Q -o /tmp/newpcr15
+
+ # And check it matches expectations
+ ( cat /tmp/oldpcr15 ;
+ echo -n "machine-id:994013bf23864ee7992eab39a96dd3bb" | openssl dgst -binary -sha256 ) | openssl dgst -binary -sha256 | cmp - /tmp/newpcr15
+
+ rm /tmp/oldpcr15 /tmp/newpcr15
+
+ # And similar for the boot phase measurement into PCR 11
+ tpm2_pcrread sha256:11 -Q -o /tmp/oldpcr11
+ SYSTEMD_FORCE_MEASURE=1 /usr/lib/systemd/systemd-pcrphase foobar
+ tpm2_pcrread sha256:11 -Q -o /tmp/newpcr11
+
+ ( cat /tmp/oldpcr11 ;
+ echo -n "foobar" | openssl dgst -binary -sha256 ) | openssl dgst -binary -sha256 | cmp - /tmp/newpcr11
+
+ rm /tmp/oldpcr11 /tmp/newpcr11
+else
+ echo "/usr/lib/systemd/systemd-pcrphase or PCR sysfs files not found, skipping PCR extension test case"
+fi
+
echo OK >/testok
exit 0
fi
}
+restore_sysfs_dmi() {
+ umount /sys/class/dmi/id
+ rm -rf /run/systemd/system/systemd-hostnamed.service.d
+ systemctl daemon-reload
+ systemctl stop systemd-hostnamed
+}
+
+test_firmware_date() {
+ # No DMI on s390x or ppc
+ if [[ ! -d /sys/class/dmi/id ]]; then
+ echo "/sys/class/dmi/id not found, skipping firmware date tests."
+ return 0
+ fi
+
+ trap restore_sysfs_dmi RETURN
+
+ # Ignore /sys being mounted as tmpfs
+ mkdir -p /run/systemd/system/systemd-hostnamed.service.d/
+ cat >/run/systemd/system/systemd-hostnamed.service.d/override.conf <<EOF
+[Service]
+Environment="SYSTEMD_DEVICE_VERIFY_SYSFS=0"
+EOF
+ systemctl daemon-reload
+
+ mount -t tmpfs none /sys/class/dmi/id
+ echo '1' > /sys/class/dmi/id/uevent
+
+ echo '01/01/2000' > /sys/class/dmi/id/bios_date
+ systemctl stop systemd-hostnamed
+ assert_in '2000-01-01' "$(hostnamectl)"
+
+ echo '2022' > /sys/class/dmi/id/bios_date
+ systemctl stop systemd-hostnamed
+ assert_not_in 'Firmware Date' "$(hostnamectl)"
+
+ echo 'garbage' > /sys/class/dmi/id/bios_date
+ systemctl stop systemd-hostnamed
+ assert_not_in 'Firmware Date' "$(hostnamectl)"
+}
+
: >/failed
test_hostname
test_chassis
+test_firmware_date
touch /testok
rm /failed
assert_rc 0 localectl set-locale "$i"
if [[ -f /etc/default/locale ]]; then
- # Debian/Ubuntu patch is buggy, and LC_CTYPE= still exists.
- assert_eq "$(cat /etc/default/locale)" "LANG=$i
-LC_CTYPE=$i"
+ assert_eq "$(cat /etc/default/locale)" "LANG=$i"
else
assert_eq "$(cat /etc/locale.conf)" "LANG=$i"
fi
+++ /dev/null
-# SPDX-License-Identifier: LGPL-2.1-or-later
-[Unit]
-Description=TEST-77-OPENFILE
-
-[Service]
-ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
-Type=simple
+++ /dev/null
-#!/usr/bin/env bash
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-echo "Socket" | nc -lkU /tmp/test.sock
--- /dev/null
+[Unit]
+Description=TEST-77-OPENFILE server socket
+
+[Socket]
+ListenStream=/tmp/test.sock
+Accept=yes
--- /dev/null
+[Unit]
+Description=TEST-77-OPENFILE server
+
+[Service]
+ExecStart=echo "Socket"
+StandardInput=socket
+StandardOutput=socket
+++ /dev/null
-# SPDX-License-Identifier: LGPL-2.1-or-later
-[Unit]
-Description=TEST-77-OPENFILE
-
-[Service]
-OpenFile=/tmp/test.sock:socket:read-only
-ExecStartPre=rm -f /failed /testok
-ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
-Type=oneshot
assert_eq "$text" "File"
# Test for socket
-systemctl start testsuite-77-netcat.service
-systemctl start testsuite-77-socket.service
+systemctl start testsuite-77-server.socket
+systemd-run -p OpenFile=/tmp/test.sock:socket:read-only \
+ --wait \
+ --pipe \
+ /usr/lib/systemd/tests/testdata/units/testsuite-77-client.sh
# Tests for D-Bus
diff <(systemctl show -p OpenFile testsuite-77) - <<EOF
'sysinit.target.wants/'],
['systemd-journal-gatewayd.socket', 'ENABLE_REMOTE HAVE_MICROHTTPD'],
['systemd-journal-remote.socket', 'ENABLE_REMOTE HAVE_MICROHTTPD'],
- ['systemd-journald-audit.socket', '',
- 'sockets.target.wants/'],
+ ['systemd-journald-audit.socket', ''],
['systemd-journald-dev-log.socket', '',
'sockets.target.wants/'],
['systemd-journald.socket', '',
'sysinit.target.wants/'],
['systemd-pcrphase.service', 'HAVE_GNU_EFI HAVE_OPENSSL HAVE_TPM2',
'sysinit.target.wants/'],
+ ['systemd-pcrmachine.service', 'HAVE_GNU_EFI HAVE_OPENSSL HAVE_TPM2',
+ 'sysinit.target.wants/'],
+ ['systemd-pcrfs-root.service', 'HAVE_GNU_EFI HAVE_OPENSSL HAVE_TPM2'],
+ ['systemd-pcrfs@.service', 'HAVE_GNU_EFI HAVE_OPENSSL HAVE_TPM2'],
+ ['systemd-growfs-root.service', ''],
+ ['systemd-growfs@.service', ''],
]
add_wants = []
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Grow Root File System
+Documentation=man:systemd-growfs-root.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-repart.service systemd-remount-fs.service
+Before=shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{ROOTLIBEXECDIR}}/systemd-growfs /
+TimeoutSec=0
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Grow File System on %f
+Documentation=man:systemd-growfs@.service(8)
+DefaultDependencies=no
+BindsTo=%i.mount
+Conflicts=shutdown.target
+After=systemd-repart.service %i.mount
+Before=shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{ROOTLIBEXECDIR}}/systemd-growfs %f
+TimeoutSec=0
ReceiveBuffer=128M
ListenNetlink=audit 1
PassCredentials=yes
+
+[Install]
+WantedBy=sockets.target
+WantedBy=systemd-journald.service
RestrictSUIDSGID=yes
RuntimeDirectory=systemd/journal
RuntimeDirectoryPreserve=yes
-Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
+# Audit socket is not listed here because this unit can be turned off. However
+# the link between the socket and the service units is still created thanks to
+# the 'Service=' setting specified in the socket unit.
+Sockets=systemd-journald.socket systemd-journald-dev-log.socket
StandardOutput=null
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=TPM2 PCR Root File System Measurement
+Documentation=man:systemd-pcrfs-root.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-pcrmachine.service
+Before=shutdown.target
+AssertPathExists=!/etc/initrd-release
+ConditionSecurity=tpm2
+ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --file-system=/
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=TPM2 PCR File System Measurement of %f
+Documentation=man:systemd-pcrfs@.service(8)
+DefaultDependencies=no
+BindsTo=%i.mount
+Conflicts=shutdown.target
+After=%i.mount systemd-pcrfs-root.service
+Before=shutdown.target
+AssertPathExists=!/etc/initrd-release
+ConditionSecurity=tpm2
+ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --file-system=%f
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=TPM2 PCR Machine ID Measurement
+Documentation=man:systemd-pcrmachine.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=sysinit.target shutdown.target
+AssertPathExists=!/etc/initrd-release
+ConditionSecurity=tpm2
+ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --machine-id