- name: kernel-install
keys: ['kernel-install']
- - name: logind
+ - name: login
keys: ['systemd-logind', 'loginctl', 'pam_systemd']
- - name: machined
+ - name: machine
keys: ['systemd-machined', 'machinectl']
- name: modules-load
- any-glob-to-any-file: 'src/journal-remote/*'
login:
- changed-files:
- - any-glob-to-any-file: '**/sd-login*/**'
-logind:
- - changed-files:
- - any-glob-to-any-file: 'src/login/*'
+ - any-glob-to-any-file: ['src/login/*', '**/sd-login*/**']
meson:
- changed-files:
- any-glob-to-any-file: ['meson_options.txt', '**/meson.build']
steps:
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633
- - uses: systemd/mkosi@a845d4108ac87ca443bd1ad78ab53520bffd2eda
+ - uses: systemd/mkosi@6ab7d9f09f8f2633f4b7c777a04e62e109486e2f
# Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space
# immediately, we remove the files in the background. However, we first move them to a different location
or /efi/ hierarchies in /etc/fstab. This is to prevent the generator
from interfering with systems where the ESP is explicitly configured
to be mounted at some path, for example /boot/efi/ (this type of
- setup is obsolete but still commonly found).
+ setup is obsolete, but still commonly found).
* The behavior of systemd-sleep and systemd-homed has been updated to
freeze user sessions when entering the various sleep modes or when
embedded in the file name. The files are ordered by version and
the newest one is selected.
- systemd-nspawn --image=/--directory=, systemd-dissect, and the
- RootDirectory=, RootImage=, ExtensionImages=, and
+ systemd-nspawn --image=/--directory=, systemd-dissect, systemd-portabled,
+ and the RootDirectory=, RootImage=, ExtensionImages=, and
ExtensionDirectories= settings for units now support the vpick
protocol and allow the latest version to be selected automatically if
a "*.v/" directory is specified as the source.
- * Encrypted service credentials may now be made accessible to
+ * Encrypted service credentials can now be made accessible to
unprivileged users. systemd-creds gained new options --user/--uid=
for encrypting/decrypting a credential for a specific user.
* New command-line tool 'importctl' to download, import, and export
disk images via systemd-importd is added with the following verbs:
pull-tar, pull-raw, import-tar, import-raw, import-fs, export-tar,
- export-raw, list-transfers, cancel-transfer. This functionality was
- previously available in "machinectl", where it was exclusively for
- machine image. The new "importctl" generalizes this for sysext,
- confext, portable service images, too.
+ export-raw, list-transfers, and cancel-transfer. This functionality
+ was previously available in "machinectl", where it was used
+ exclusively for machine images. The new "importctl" generalizes this
+ for sysext, confext, and portable service images.
Service Management:
enabled by default in the initrd.
* New unit setting WantsMountsFor= has been added. It is analogous to
- RequiresMountsFor=, but with a Wants= dependency instead of
- Requires=. This new logic is used in various places where mounts were
- added as dependencies for other settings (WorkingDirectory=-…,
+ RequiresMountsFor=, but creates a Wants= dependency instead of
+ Requires=. This new logic is now used in various places where mounts
+ were added as dependencies for other settings (WorkingDirectory=-…,
PrivateTmp=yes, cryptsetup lines with 'nofail').
* New unit setting MemoryZSwapWriteback= can be used to control the new
* The manager gained a org.freedesktop.systemd1.StartAuxiliaryScope()
D-Bus method to devolve some processes from a service into a new
- scope. This new scope will remain even if the original service unit
- is restarted. Control group properties of the new scope are copied
- from the originating unit, so various limits are retained.
+ scope. This new scope will remain running, even when the original
+ service unit is restarted or stopped. This allows a service unit to
+ split out some worker processes which need to continue running.
+ Control group properties of the new scope are copied from the
+ originating unit, so various limits are retained.
* Units now expose properties EffectiveMemoryMax=,
EffectiveMemoryHigh=, and EffectiveTasksMax=, which report the
system credential.
* The systemd binary will no longer chainload sysvinit's "telinit"
- binary when called under the init/telinit name on a system that
- isn't booted with systemd. This previously has been supported to make
- sure a distribution that has both init systems installed can be
- reasonably switched from one to the other via a simple
- reboot. Distributions apparently have lost interest in this, and the
- functionality has not been supported on the primary distribution this
- was still intended for for a longer time, and hence has been removed
- now.
-
- * A new concept called "capsules" has been introduced. "Capsules"
- encapsulate additional per-user service managers, whose users are
- transient and are only defined as long as the service manager
- is running (implemented via DynamicUser=1). These service managers run
- off home directories defined in /var/lib/capsules/<name>, where
- <name> is a the capsule's name. These home directories can contain
- regular per-user services and other units. A capsule is started via a
- simple "systemctl start capsule@<name>.service". See the
- capsule@.service(5) man page for further details. Various systemd
- tools (including, and most importantly, systemctl and systemd-run)
- have been updated to interact with capsules via the new
+ binary when called under the init/telinit name on a system that isn't
+ booted with systemd. This previously has been supported to make sure
+ a distribution that has both init systems installed can reasonably
+ switch from one to the other via a simple reboot. Distributions
+ apparently have lost interest in this, and the functionality has not
+ been supported on the primary distribution this was still intended
+ for for a long time, and hence has been removed now.
+
+ * A new concept called "capsules" has been introduced. "Capsules" wrap
+ additional per-user service managers, whose users are transient and
+ are only defined as long as the service manager is running. (This is
+ implemented via DynamicUser=1), allowing a user manager to be used to
+ manager a group of processes without needing to create an actual user
+ account. These service managers run with home directories of
+ /var/lib/capsules/<capsule-name> and can contain regular services and
+ other units. A capsule is started via a simple "systemctl start
+ capsule@<name>.service". See the capsule@.service(5) man page for
+ further details.
+
+ Various systemd tools (including, and most importantly, systemctl and
+ systemd-run) have been updated to interact with capsules via the new
"--capsule="/"-C" switch.
* .socket units gained a new setting PassFileDescriptorsToExec=, taking
encapsulates are passed to the ExecStartPost=, ExecStopPre=,
ExecStopPost= using the usual $LISTEN_FDS interface. This may be used
for doing additional initializations on the sockets once they are
- allocated (for example, install an additional eBPF program on them).
+ allocated. (For example, to install an additional eBPF program on
+ them).
* The .socket setting MaxConnectionsPerSource= (which so far put a
limit on concurrent connections per IP in Accept=yes socket units),
services in a simple Accept=yes mode.
* The service manager will now maintain a counter of soft reboot cycles
- the system went through so far. It may be queried via the D-Bus APIs.
+ the system went through. It may be queried via the D-Bus APIs.
* systemd's execution logic now supports the new pidfd_spawn() API
introduced by glibc 2.39, which allows us to invoke a subprocess in a
target cgroup and get a pidfd back in a single operation.
- * systemd/PID 1 will now send an additional sd_notify() message to its
+ * systemd/PID 1 will now send an additional sd_notify() message to its
supervising VMM or container manager reporting the selected hostname
("X_SYSTEMD_HOSTNAME=") and machine ID ("X_SYSTEMD_MACHINE_ID=") at
boot. Moreover, the service manager will send additional sd_notify()
reports "ssh-access.target" being reached a VMM/container manager
knows it can now connect to the system via SSH. Finally, a new
sd_notify() message ("X_SYSTEMD_SIGNALS_LEVEL=2") is sent the moment
- PID 1 successfully completed installation of its various UNIX process
- signal handlers (i.e. the moment where SIGRTMIN+4 sent to PID 1 will
- start to have the effect of shutting down the system cleanly).
+ PID 1 has successfully completed installation of its various UNIX
+ process signal handlers (i.e. the moment where SIGRTMIN+4 sent to
+ PID 1 will start to have the effect of shutting down the system
+ cleanly).
Journal:
* confexts are loaded by systemd-stub from the ESP as well.
- * The pcrlock policy is saved in an unencrypted credential file
- "pcrlock.<entry-token>.cred" under XBOOTLDR/ESP in the
- /loader/credentials/ directory. It will be picked up at boot by
- systemd-stub and passed to the initrd, where it can be used to unlock
- the root file system.
-
* kernel-install gained support for --root= for the 'list' verb.
- * systemd-pcrlock gained an --entry-token= option to configure the
- entry-token.
-
- * systemd-pcrlock now provides a basic Varlink interface and can be run
- as a daemon via a template unit.
-
* bootctl now provides a basic Varlink interface and can be run as a
daemon via a template unit.
for enrolling "dbx" too (Previously, only db/KEK/PK enrollment was
supported). It also now supports UEFI "Custom" mode.
+ * The pcrlock policy is saved in an unencrypted credential file
+ "pcrlock.<entry-token>.cred" under XBOOTLDR/ESP in the
+ /loader/credentials/ directory. It will be picked up at boot by
+ systemd-stub and passed to the initrd, where it can be used to unlock
+ the root file system.
+
+ * systemd-pcrlock gained an --entry-token= option to configure the
+ entry-token.
+
+ * systemd-pcrlock now provides a basic Varlink interface and can be run
+ as a daemon via a template unit.
+
+ * systemd-pcrlock's TPM nvindex access policy has been modified, this
+ means that previous pcrlock policies stored in nvindexes are
+ invalidated. They must be removed (systemd-pcrlock remove-policy) and
+ recreated (systemd-pcrlock make-policy). For the time being
+ systemd-pcrlock remains an experimental feature, but it is expected
+ to become stable in the next release, i.e. v257.
+
+ * systemd-pcrlock's --recovery-pin= switch now takes three values:
+ "hide", "show", "query". If "show" is selected the automatically
+ generated recovery PIN is shown to the user. If "query" is selected
+ then the PIN is queried from the user.
+
systemd-run/run0:
* systemd-run is now a multi-call binary. When invoked as 'run0', it
--ssh-key-type= to optionally set up transient SSH keys to pass to the
invoked VMs in order to be able to SSH into them once booted.
+ * systemd-vmspawn will now enable various "HyperV enlightenments" and
+ the "VM Generation ID" on the VMs.
+
+ * A new environment variable $SYSTEMD_VMSPAWN_QEMU_EXTRA may carry
+ additional qemu command line options to pass to qemu.
+
systemd-repart:
* systemd-repart gained new options --generate-fstab= and
sd_journal_stream_fd() but creates a log stream targeted at a
specific log namespace.
+ * The sd-id128 API gained a new API call
+ sd_id128_get_invocation_app_specific() for acquiring an app-specific
+ ID that is derived from the service invocation ID.
+
systemd-cryptsetup/systemd-cryptenroll:
* systemd-cryptenroll can now enroll directly with a PKCS11 public key
Features:
+* systemd-repart should probably enable btrfs' "temp_fsid" feature for all file
+ systems it creates, as we have no interest in RAID for repart, and it should
+ make sure that we can mount them trivially everywhere.
+
* systemd-nspawn should get the same SSH key support that vmspawn now has.
* insert the new pidfs inode number as a third field into PidRef, so that
PCRs.
* vmspawn:
- - enable hyperv extension by default (https://www.qemu.org/docs/master/system/i386/hyperv.html)
- - register with machined
- run in scope unit when invoked from command line, and machined registration is off
- - support --directory= via virtiofs
- sd_notify support
- --ephemeral support
- --read-only support
/* Mark a couple of iterator explicitly as iterators, otherwise Coccinelle gets a bit confused. Coccinelle
* can usually infer this information automagically, but in these specific cases it needs a bit of help. */
#define FOREACH_ARRAY(i, array, num) YACFE_ITERATOR
+#define FOREACH_ELEMENT(i, array) YACFE_ITERATOR
#define FOREACH_DIRENT_ALL(de, d, on_error) YACFE_ITERATOR
#define FOREACH_STRING(x, y, ...) YACFE_ITERATOR
#define HASHMAP_FOREACH(e, h) YACFE_ITERATOR
expected format is six groups of two hexadecimal digits separated by colons,
e.g. `SYSTEMD_VMSPAWN_NETWORK_MAC=12:34:56:78:90:AB`
+* `$SYSTEMD_VMSPAWN_QEMU_EXTRA=…` – may contain additional command line
+ arguments to append the qemu command line.
+
`systemd-logind`:
* `$SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK=1` — if set, report that
<!ENTITY DEFAULT_USER_TIMEOUT "{{DEFAULT_USER_TIMEOUT_SEC}} s">
<!ENTITY DEFAULT_KEYMAP "{{SYSTEMD_DEFAULT_KEYMAP}}">
<!ENTITY fedora_latest_version "40">
+<!ENTITY fedora_cloud_release "1.10">
<xi:include href="version-info.xml" xpointer="v254"/>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>UseDomains=</varname></term>
+ <listitem>
+ <para>Specifies the network- and protocol-independent default value for the same settings in
+ [IPv6AcceptRA], [DHCPv4], and [DHCPv6] sections below. Takes a boolean, or the special value
+ <option>route</option>. See the same setting in
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ Defaults to <literal>no</literal>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>[IPv6AcceptRA] Section Options</title>
+
+ <para>This section configures the default setting of the Neighbor Discovery. The following options are
+ available in the [IPv6AcceptRA] section:</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>UseDomains=</varname></term>
+ <listitem>
+ <para>Specifies the network-independent default value for the same setting in the [IPv6AcceptRA]
+ section in
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ Takes a boolean, or the special value <option>route</option>. When unspecified, the value specified
+ in the [Network] section in
+ <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ which defaults to <literal>no</literal>, will be used.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
<varlistentry>
<term><varname>UseDomains=</varname></term>
- <listitem><para>Specifies the default value for per-network <varname>UseDomains=</varname>.
- Takes a boolean. See for details in
- <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
- Defaults to <literal>no</literal>.</para>
-
+ <listitem>
+ <para>Same as the one in the [IPv6AcceptRA] section, but applied for DHCPv4 protocol.</para>
+
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<variablelist class='network-directives'>
<varlistentry>
- <term><varname>PersistLeases=</varname></term>
+ <term><varname>UseDomains=</varname></term>
<listitem>
- <para>Specifies the default value for per-network <varname>PersistLeases=</varname>.
- Takes a boolean. See for details in
- <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
- Defaults to <literal>yes</literal>.</para>
+ <para>Same as the one in the [IPv6AcceptRA] section, but applied for DHCPv4 protocol.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
};
</programlisting>
- <!--method GetHardwareSerial is not documented!-->
-
- <!--property OperatingSystemSupportEnd is not documented!-->
-
- <!--property HardwareVendor is not documented!-->
-
- <!--property HardwareModel is not documented!-->
-
- <!--property FirmwareVersion is not documented!-->
-
- <!--property FirmwareVendor is not documented!-->
-
- <!--property FirmwareDate is not documented!-->
-
<!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.hostname1"/>
<constant>UINT32_MAX</constant> otherwise. See <citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details.</para>
+ <para><varname>OperatingSystemSupportEnd</varname> exposes when the OS' vendor support ends, if this
+ information is known. It's an unsigned 64bit value, in µs since the UNIX epoch, UTC. If this information
+ is not known carries the value 2^64-1, i.e. <constant>UINT64_MAX</constant>.</para>
+
+ <para><varname>HardwareVendor</varname> and <varname>HardwareModel</varname> expose information about the
+ vendor of the hardware of the system. If no such information can be determined these properties are set
+ to empty strings.</para>
+
+ <para><varname>FirmwareVersion</varname> and <varname>FirmwareVendor</varname> expose information about
+ the system's firmware, i.e. a version string and a vendor name. If no such information can be determined
+ these properties are set to empty strings.</para>
+
+ <para><varname>FirmwareDate</varname> exposes the firmware build date, if that information is known. It's
+ an unsigned 64bit value, in µs since the UNIX epoch, UTC. If not known
+ <constant>UNIT64_MAX</constant>.</para>
+
<refsect2>
<title>Methods</title>
requires root privileges, and this method allows access to unprivileged clients through the polkit
framework.</para>
+ <para><function>GetHardwareSerial()</function> returns the "hardware serial" as exposed by the kernel
+ based on DMI information. Reading the file directly requires root privileges, and this method allows
+ access to unprivileged clients through the polkit framework.</para>
+
<para><function>Describe()</function> returns a JSON representation of all properties in one.</para>
</refsect2>
on the system. Note that this method returns only after all the listed operations are completed,
and due to the I/O involved it might take some time.</para>
+ <xi:include href="vpick.xml" xpointer="image"/>
+ <xi:include href="vpick.xml" xpointer="directory"/>
+
<para><function>AttachImageWithExtensions()</function> attaches a portable image to the system.
This method is a superset of <function>AttachImage()</function> with the addition of
a list of extensions as input parameter, which will be overlaid on top of the main
<!--method EnqueueUnitJob is not documented!-->
- <!--method CleanUnit is not documented!-->
-
<!--method FreezeUnit is not documented!-->
<!--method ThawUnit is not documented!-->
shouldn't be bound to a lifecycle of the service, e.g. they should continue running after the restart
of the service. Note that the main PID of the service can not be migrated to an auxiliary scope.
Also, <varname>flags</varname> argument must be 0 and is reserved for future extensions.</para>
+
+ <para><function>CleanUnit()</function> deletes the configuration, state, logs, cache and runtime data
+ directories and clear out the file descriptors store for the unit, as specified in the mask
+ parameters. The possible values are <literal>configuration</literal>, <literal>state</literal>,
+ <literal>logs</literal>, <literal>cache</literal>, <literal>runtime</literal>,
+ <literal>fdstore</literal>, and <literal>all</literal>.</para>
</refsect2>
<refsect2>
<term><varname>keyname=</varname></term>
<listitem><para>Takes a string argument which sets the keyname to read.
- The default is <literal>cryptsetup</literal>, which is used by
+ The default is <literal>cryptsetup</literal>.
+ During boot,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- to store LUKS passphrase during boot.</para>
+ stores a passphrase or PIN in the keyring.
+ The LUKS2 volume key can also be used, via the <option>link-volume-key</option> option in
+ <citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+ <table>
+ <title>
+ Possible values for <varname>keyname</varname>.
+ </title>
+
+ <tgroup cols='2'>
+ <colspec colname='value' />
+ <colspec colname='description' />
+ <thead>
+ <row>
+ <entry>Value</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>cryptsetup</entry>
+ <entry>Passphrase or recovery key</entry>
+ </row>
+ <row>
+ <entry>fido2-pin</entry>
+ <entry>Security token PIN</entry>
+ </row>
+ <row>
+ <entry>luks2-pin</entry>
+ <entry>LUKS2 token PIN</entry>
+ </row>
+ <row>
+ <entry>tpm2-pin</entry>
+ <entry>TPM2 PIN</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<programlisting>
-auth optional pam_systemd_loadkey.so
+-auth optional pam_gnome_keyring.so
-session optional pam_gnome_keyring.so auto_start
-session optional pam_kwallet5.so auto_start
</programlisting>
immediately started (blocking operation unless <option>--no-block</option> is passed) and/or enabled after
attaching the image.</para>
+ <xi:include href="vpick.xml" xpointer="image"/>
+ <xi:include href="vpick.xml" xpointer="directory"/>
<xi:include href="version-info.xml" xpointer="v239"/>
</listitem>
</varlistentry>
<xi:include href="version-info.xml" xpointer="v245"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--clean</option></term>
+
+ <listitem><para>When detaching ensure the configuration, state, logs, cache, and runtime data
+ directories of the portable service are removed from the host system.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--extension=<replaceable>PATH</replaceable></option></term>
<para>Note that the same extensions have to be specified, in the same order, when attaching
and detaching.</para>
+ <xi:include href="vpick.xml" xpointer="image"/>
+ <xi:include href="vpick.xml" xpointer="directory"/>
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
<refname>sd_event_add_inotify</refname>
<refname>sd_event_add_inotify_fd</refname>
<refname>sd_event_source_get_inotify_mask</refname>
+ <refname>sd_event_source_get_inotify_path</refname>
<refname>sd_event_inotify_handler_t</refname>
<refpurpose>Add an "inotify" file system inode event source to an event loop</refpurpose>
<funcprototype>
<funcdef>int <function>sd_event_source_get_inotify_mask</function></funcdef>
<paramdef>sd_event_source *<parameter>source</parameter></paramdef>
- <paramdef>uint32_t *<parameter>mask</parameter></paramdef>
+ <paramdef>uint32_t *<parameter>ret</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_event_source_get_inotify_path</function></funcdef>
+ <paramdef>sd_event_source *<parameter>source</parameter></paramdef>
+ <paramdef>const char **<parameter>ret</parameter></paramdef>
</funcprototype>
</funcsynopsis>
event source created previously with <function>sd_event_add_inotify()</function>. It takes the event source object
as the <parameter>source</parameter> parameter and a pointer to a <type>uint32_t</type> variable to return the mask
in.</para>
+
+ <para><function>sd_event_source_get_inotify_path()</function> retrieves the target path of the configured
+ inotify watch of an event source created previously with <function>sd_event_add_inotify()</function>. It
+ takes the event source object as the <parameter>source</parameter> parameter and a pointer to a
+ <type>const char **</type> variable to return the path in. The caller must not free the returned path.
+ </para>
</refsect1>
<refsect1>
<varlistentry>
<term><constant>-ESTALE</constant></term>
- <listitem><para>The event loop is already terminated.</para></listitem>
+ <listitem>
+ <para>Returned by <function>sd_event_source_add_inotify()</function> or
+ <function>sd_event_source_add_inotify_fd()</function> when the event loop is already terminated.
+ Returned by <function>sd_event_source_get_inotify_path()</function> when no active inode data is
+ assigned to the event source, e.g. when the event source is disabled.</para>
+ </listitem>
</varlistentry>
<varlistentry>
<term><constant>-EBADF</constant></term>
- <listitem><para>The passed file descriptor is not valid.</para>
+ <listitem><para>The passed file descriptor is not valid.</para></listitem>
- <xi:include href="version-info.xml" xpointer="v250"/></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ENOSYS</constant></term>
- <listitem><para><function>sd_event_add_inotify_fd()</function> was called without
- <filename>/proc/</filename> mounted.</para>
+ <listitem>
+ <para><function>sd_event_add_inotify_fd()</function> or
+ <function>sd_event_source_get_inotify_path()</function> was called without
+ <filename>/proc/</filename> mounted.</para>
+ </listitem>
- <xi:include href="version-info.xml" xpointer="v250"/></listitem>
</varlistentry>
</variablelist>
<function>sd_event_add_inotify()</function>, and
<function>sd_event_source_get_inotify_mask()</function> were added in version 239.</para>
<para><function>sd_event_add_inotify_fd()</function> was added in version 250.</para>
+ <para><function>sd_event_source_get_inotify_path()</function> was added in version 256.</para>
</refsect1>
<refsect1>
source object and returns the non-negative file descriptor
or a negative error number on error (see below).</para>
- <para><function>sd_event_source_set_io_fd()</function>
- changes the UNIX file descriptor of an I/O event source created
- previously with <function>sd_event_add_io()</function>. It takes
- the event source object and the new file descriptor.</para>
-
- <para><function>sd_event_source_set_io_fd_own()</function> controls whether the file descriptor of the event source
- shall be closed automatically when the event source is freed, i.e. whether it shall be considered 'owned' by the
- event source object. By default it is not closed automatically, and the application has to do this on its own. The
- <parameter>b</parameter> parameter is a boolean parameter: if zero, the file descriptor is not closed automatically
- when the event source is freed, otherwise it is closed.</para>
+ <para><function>sd_event_source_set_io_fd()</function> changes the UNIX file descriptor of an I/O event
+ source created previously with <function>sd_event_add_io()</function>. It takes the event source object
+ and the new file descriptor. If the event source takes the ownership of the previous file descriptor,
+ that is, <function>sd_event_source_set_io_fd_own()</function> was called for the event source with a
+ non-zero value, then the previous file descriptor will be closed and the event source will also take the
+ ownership of the new file descriptor on success.</para>
+
+ <para><function>sd_event_source_set_io_fd_own()</function> controls whether the file descriptor of the
+ event source shall be closed automatically when the event source is freed (or when the file descriptor
+ assigned to the event source is replaced by <function>sd_event_source_set_io_fd()</function>), i.e.
+ whether it shall be considered 'owned' by the event source object. By default it is not closed
+ automatically, and the application has to do this on its own. The <parameter>b</parameter> parameter is a
+ boolean parameter: if zero, the file descriptor is not closed automatically when the event source is
+ freed, otherwise it is closed.</para>
<para><function>sd_event_source_get_io_fd_own()</function> may be used to query the current setting of the file
descriptor ownership boolean flag as set with <function>sd_event_source_set_io_fd_own()</function>. It returns
<paramdef>sd_id128_t *<parameter>ret</parameter></paramdef>
</funcprototype>
+ <funcprototype>
+ <funcdef>int <function>sd_id128_get_invocation_app_specific</function></funcdef>
+ <paramdef>sd_id128_t <parameter>app_id</parameter></paramdef>
+ <paramdef>sd_id128_t *<parameter>ret</parameter></paramdef>
+ </funcprototype>
+
</funcsynopsis>
</refsynopsisdiv>
for details. The ID is cached internally. In future a different mechanism to determine the invocation ID
may be added.</para>
+ <para><function>sd_id128_get_invocation_app_specific()</function> derives an application-specific ID from
+ the invocation ID.</para>
+
<para>Note that <function>sd_id128_get_machine_app_specific()</function>,
- <function>sd_id128_get_boot()</function>, <function>sd_id128_get_boot_app_specific()</function>, and
- <function>sd_id128_get_invocation()</function> always return UUID Variant 1 Version 4 compatible IDs.
- <function>sd_id128_get_machine()</function> will also return a UUID Variant 1 Version 4 compatible ID on
- new installations but might not on older. It is possible to convert the machine ID non-reversibly into a
- UUID Variant 1 Version 4 compatible one. For more information, see
+ <function>sd_id128_get_boot()</function>, <function>sd_id128_get_boot_app_specific()</function>,
+ <function>sd_id128_get_invocation()</function> and
+ <function>sd_id128_get_invocation_app_specific</function> always return UUID Variant 1 Version 4
+ compatible IDs. <function>sd_id128_get_machine()</function> will also return a UUID Variant 1 Version 4
+ compatible ID on new installations but might not on older. It is possible to convert the machine ID
+ non-reversibly into a UUID Variant 1 Version 4 compatible one. For more information, see
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>. It is
hence guaranteed that these functions will never return the ID consisting of all zero or all one bits
(<constant>SD_ID128_NULL</constant>, <constant>SD_ID128_ALLF</constant>) — with the possible exception of
<para><function>sd_id128_get_machine_app_specific()</function> was added in version 233.</para>
<para><function>sd_id128_get_boot_app_specific()</function> was added in version 240.</para>
<para><function>sd_id128_get_app_specific()</function> was added in version 255.</para>
+ <para><function>sd_id128_get_invocation_app_specific()</function> was added in version 256.</para>
</refsect1>
<refsect1>
these three types of resources are generally redundant and reproducible on the next invocation of
the unit). Note that the specified units must be stopped to invoke this operation.</para>
+ <table>
+ <title>
+ Possible values for <option>--what=</option>
+ </title>
+
+ <tgroup cols='2'>
+ <thead>
+ <row>
+ <entry>Value</entry>
+ <entry>Unit Setting</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>runtime</literal></entry>
+ <entry><varname>RuntimeDirectory=</varname></entry>
+ </row>
+ <row>
+ <entry><literal>state</literal></entry>
+ <entry><varname>StateDirectory=</varname></entry>
+ </row>
+ <row>
+ <entry><literal>cache</literal></entry>
+ <entry><varname>CacheDirectory=</varname></entry>
+ </row>
+ <row>
+ <entry><literal>logs</literal></entry>
+ <entry><varname>LogsDirectory=</varname></entry>
+ </row>
+ <row>
+ <entry><literal>configuration</literal></entry>
+ <entry><varname>ConfigurationDirectory=</varname></entry>
+ </row>
+ <row>
+ <entry><literal>fdstore</literal></entry>
+ <entry><varname>FileDescriptorStorePreserve=</varname></entry>
+ </row>
+ <row>
+ <entry><literal>all</literal></entry>
+ <entry>All of the above</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
<xi:include href="version-info.xml" xpointer="v243"/>
</listitem>
</varlistentry>
<table>
<title>Partition Type GUIDs</title>
- <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <tgroup cols='5' align='left' colsep='1' rowsep='1'>
+ <colspec colname="type" />
<colspec colname="guid" />
<colspec colname="name" />
<colspec colname="where" />
<colspec colname="explanation" />
<thead>
<row>
- <entry>Partition Type GUID</entry>
+ <entry>Partition Type</entry>
+ <entry>GUID</entry>
<entry>Name</entry>
<entry>Mount Point</entry>
<entry>Explanation</entry>
</thead>
<tbody>
<row>
- <entry><constant>SD_GPT_ROOT_X86_64</constant> <constant>4f68bce3-e8cd-4db1-96e7-fbcaf984b709</constant></entry>
+ <entry><constant>SD_GPT_ROOT_X86_64</constant></entry>
+ <entry><constant>4f68bce3-e8cd-4db1-96e7-fbcaf984b709</constant></entry>
<entry><filename>Root Partition (x86-64)</filename></entry>
<entry><filename>/</filename></entry>
- <entry>The first partition with this type UUID, located on the same disk as the ESP, is used as the root file system <filename>/</filename> on AMD64 / 64-bit x86 systems.</entry>
+ <entry>The first partition with this type UUID, located on the same disk as the ESP used for booting, is used as the root file system <filename>/</filename> on AMD64 / 64-bit x86 systems.</entry>
</row>
<row>
- <entry><constant>SD_GPT_ROOT_ARM64</constant> <constant>b921b045-1df0-41c3-af44-4c6f280d3fae</constant></entry>
+ <entry><constant>SD_GPT_ROOT_ARM64</constant></entry>
+ <entry><constant>b921b045-1df0-41c3-af44-4c6f280d3fae</constant></entry>
<entry><filename>Root Partition (64-bit ARM)</filename></entry>
<entry><filename>/</filename></entry>
- <entry>The first partition with this type UUID, located on the same disk as the ESP, is used as the root file system <filename>/</filename> on AArch64 / 64-bit ARM systems.</entry>
+ <entry>The first partition with this type UUID, located on the same disk as the ESP used for booting, is used as the root file system <filename>/</filename> on AArch64 / 64-bit ARM systems.</entry>
</row>
<row>
- <entry>
- <constant>SD_GPT_ROOT_ALPHA</constant> <constant>SD_GPT_ROOT_ARC</constant> <constant>SD_GPT_ROOT_ARM</constant> <constant>SD_GPT_ROOT_ARM64</constant> <constant>SD_GPT_ROOT_IA64</constant> <constant>SD_GPT_ROOT_LOONGARCH64</constant> <constant>SD_GPT_ROOT_MIPS</constant> <constant>SD_GPT_ROOT_MIPS64</constant> <constant>SD_GPT_ROOT_MIPS_LE</constant> <constant>SD_GPT_ROOT_MIPS64_LE</constant> <constant>SD_GPT_ROOT_PARISC</constant> <constant>SD_GPT_ROOT_PPC</constant> <constant>SD_GPT_ROOT_PPC64</constant> <constant>SD_GPT_ROOT_PPC64_LE</constant> <constant>SD_GPT_ROOT_RISCV32</constant> <constant>SD_GPT_ROOT_RISCV64</constant> <constant>SD_GPT_ROOT_S390</constant> <constant>SD_GPT_ROOT_S390X</constant> <constant>SD_GPT_ROOT_TILEGX</constant> <constant>SD_GPT_ROOT_X86</constant> <constant>SD_GPT_ROOT_X86_64</constant> <constant>SD_GPT_USR_ALPHA</constant> <constant>SD_GPT_USR_ARC</constant> <constant>SD_GPT_USR_ARM</constant> <constant>SD_GPT_USR_IA64</constant> <constant>SD_GPT_USR_LOONGARCH64</constant> <constant>SD_GPT_USR_MIPS_LE</constant> <constant>SD_GPT_USR_MIPS64_LE</constant> <constant>SD_GPT_USR_PARISC</constant> <constant>SD_GPT_USR_PPC</constant> <constant>SD_GPT_USR_PPC64</constant> <constant>SD_GPT_USR_PPC64_LE</constant> <constant>SD_GPT_USR_RISCV32</constant> <constant>SD_GPT_USR_RISCV64</constant> <constant>SD_GPT_USR_S390</constant> <constant>SD_GPT_USR_S390X</constant> <constant>SD_GPT_USR_TILEGX</constant> <constant>SD_GPT_USR_X86</constant>
- </entry>
- <entry>root partitions for other architectures</entry>
+ <entry><constant>SD_GPT_ROOT_ALPHA</constant> <constant>SD_GPT_ROOT_ARC</constant> <constant>SD_GPT_ROOT_ARM</constant> <constant>SD_GPT_ROOT_ARM64</constant> <constant>SD_GPT_ROOT_IA64</constant> <constant>SD_GPT_ROOT_LOONGARCH64</constant> <constant>SD_GPT_ROOT_MIPS</constant> <constant>SD_GPT_ROOT_MIPS64</constant> <constant>SD_GPT_ROOT_MIPS_LE</constant> <constant>SD_GPT_ROOT_MIPS64_LE</constant> <constant>SD_GPT_ROOT_PARISC</constant> <constant>SD_GPT_ROOT_PPC</constant> <constant>SD_GPT_ROOT_PPC64</constant> <constant>SD_GPT_ROOT_PPC64_LE</constant> <constant>SD_GPT_ROOT_RISCV32</constant> <constant>SD_GPT_ROOT_RISCV64</constant> <constant>SD_GPT_ROOT_S390</constant> <constant>SD_GPT_ROOT_S390X</constant> <constant>SD_GPT_ROOT_TILEGX</constant> <constant>SD_GPT_ROOT_X86</constant> <constant>SD_GPT_ROOT_X86_64</constant> <constant>SD_GPT_USR_ALPHA</constant> <constant>SD_GPT_USR_ARC</constant> <constant>SD_GPT_USR_ARM</constant> <constant>SD_GPT_USR_IA64</constant> <constant>SD_GPT_USR_LOONGARCH64</constant> <constant>SD_GPT_USR_MIPS_LE</constant> <constant>SD_GPT_USR_MIPS64_LE</constant> <constant>SD_GPT_USR_PARISC</constant> <constant>SD_GPT_USR_PPC</constant> <constant>SD_GPT_USR_PPC64</constant> <constant>SD_GPT_USR_PPC64_LE</constant> <constant>SD_GPT_USR_RISCV32</constant> <constant>SD_GPT_USR_RISCV64</constant> <constant>SD_GPT_USR_S390</constant> <constant>SD_GPT_USR_S390X</constant> <constant>SD_GPT_USR_TILEGX</constant> <constant>SD_GPT_USR_X86</constant></entry>
+ <entry>…</entry>
+ <entry>Root partitions for other architectures</entry>
<entry><filename>/</filename></entry>
- <entry>The first partition with the type UUID matching the architecture, located on the same disk as the ESP, is used as the root file system <filename>/</filename>. For the full list and constant values, see <ulink url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable Partitions Specification</ulink>.</entry>
+ <entry>The first partition with the type UUID matching the architecture, located on the same disk as the ESP used for booting, is used as the root file system <filename>/</filename>. For the full list and constant values, see <ulink url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable Partitions Specification</ulink>.</entry>
</row>
<row>
- <entry><constant>SD_GPT_HOME</constant> <constant>933ac7e1-2eb4-4f13-b844-0e14e2aef915</constant></entry>
+ <entry><constant>SD_GPT_HOME</constant></entry>
+ <entry><constant>933ac7e1-2eb4-4f13-b844-0e14e2aef915</constant></entry>
<entry>Home Partition</entry>
<entry><filename>/home/</filename></entry>
- <entry>The first partition with this type UUID on the same disk as the ESP is mounted to <filename>/home/</filename>.</entry>
+ <entry>The first partition with this type UUID on the same disk as the root partition is mounted to <filename>/home/</filename>.</entry>
</row>
<row>
- <entry><constant>SD_GPT_SRV</constant> <constant>3b8f8425-20e0-4f3b-907f-1a25a76f98e8</constant></entry>
+ <entry><constant>SD_GPT_SRV</constant></entry>
+ <entry><constant>3b8f8425-20e0-4f3b-907f-1a25a76f98e8</constant></entry>
<entry>Server Data Partition</entry>
<entry><filename>/srv/</filename></entry>
- <entry>The first partition with this type UUID on the same disk as the ESP is mounted to <filename>/srv/</filename>.</entry>
+ <entry>The first partition with this type UUID on the same disk as the root partition is mounted to <filename>/srv/</filename>.</entry>
</row>
<row>
- <entry><constant>SD_GPT_VAR</constant> <constant>4d21b016-b534-45c2-a9fb-5c16e091fd2d</constant></entry>
+ <entry><constant>SD_GPT_VAR</constant></entry>
+ <entry><constant>4d21b016-b534-45c2-a9fb-5c16e091fd2d</constant></entry>
<entry>Variable Data Partition</entry>
<entry><filename>/var/</filename></entry>
- <entry>The first partition with this type UUID on the same disk as the ESP is mounted to <filename>/var/</filename> — under the condition its partition UUID matches the first 128 bit of the HMAC-SHA256 of the GPT type uuid of this partition keyed by the machine ID of the installation stored in <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</entry>
+ <entry>The first partition with this type UUID on the same disk as the root partition is mounted to <filename>/var/</filename> — under the condition its partition UUID matches the first 128 bit of the HMAC-SHA256 of the GPT type uuid of this partition keyed by the machine ID of the installation stored in <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</entry>
</row>
<row>
- <entry><constant>SD_GPT_TMP</constant> <constant>7ec6f557-3bc5-4aca-b293-16ef5df639d1</constant></entry>
+ <entry><constant>SD_GPT_TMP</constant></entry>
+ <entry><constant>7ec6f557-3bc5-4aca-b293-16ef5df639d1</constant></entry>
<entry>Temporary Data Partition</entry>
<entry><filename>/var/tmp/</filename></entry>
- <entry>The first partition with this type UUID on the same disk as the ESP is mounted to <filename>/var/tmp/</filename>.</entry>
+ <entry>The first partition with this type UUID on the same disk as the root partition is mounted to <filename>/var/tmp/</filename>.</entry>
</row>
<row>
- <entry><constant>SD_GPT_SWAP</constant> <constant>0657fd6d-a4ab-43c4-84e5-0933c84b4f4f</constant></entry>
+ <entry><constant>SD_GPT_SWAP</constant></entry>
+ <entry><constant>0657fd6d-a4ab-43c4-84e5-0933c84b4f4f</constant></entry>
<entry>Swap</entry>
<entry>n/a</entry>
- <entry>All partitions with this type UUID on the same disk as the ESP are used as swap.</entry>
+ <entry>All partitions with this type UUID on the same disk as the root partition are used as swap.</entry>
</row>
<row>
- <entry><constant>SD_GPT_ESP</constant> <constant>c12a7328-f81f-11d2-ba4b-00a0c93ec93b</constant></entry>
+ <entry><constant>SD_GPT_ESP</constant></entry>
+ <entry><constant>c12a7328-f81f-11d2-ba4b-00a0c93ec93b</constant></entry>
<entry>EFI System Partition (ESP)</entry>
<entry><filename>/efi/</filename> or <filename>/boot/</filename></entry>
<entry>The first partition with this type UUID located on the same disk as the root partition is mounted to <filename>/boot/</filename> or <filename>/efi/</filename>, see below.</entry>
</row>
<row>
- <entry><constant>SD_GPT_XBOOTLDR</constant> <constant>bc13c2ff-59e6-4262-a352-b275fd6f7172</constant></entry>
+ <entry><constant>SD_GPT_XBOOTLDR</constant></entry>
+ <entry><constant>bc13c2ff-59e6-4262-a352-b275fd6f7172</constant></entry>
<entry>Extended Boot Loader Partition</entry>
<entry><filename>/boot/</filename></entry>
<entry>The first partition with this type UUID located on the same disk as the root partition is mounted to <filename>/boot/</filename>, see below.</entry>
<table>
<title>Partition Attribute Flags</title>
- <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <tgroup cols='4' align='left' colsep='1' rowsep='1'>
<colspec colname="flag" />
+ <colspec colname="value" />
<colspec colname="where" />
<colspec colname="explanation" />
<thead>
<row>
<entry>Flag</entry>
+ <entry>Value</entry>
<entry>Applicable to</entry>
<entry>Explanation</entry>
</row>
</thead>
<tbody>
<row>
- <entry><constant>SD_GPT_FLAG_READ_ONLY</constant> <constant>0x1000000000000000</constant></entry>
+ <entry><constant>SD_GPT_FLAG_READ_ONLY</constant></entry>
+ <entry><constant>0x1000000000000000</constant></entry>
<entry><filename>/</filename>, <filename>/home/</filename>, <filename>/srv/</filename>, <filename>/var/</filename>, <filename>/var/tmp/</filename>, Extended Boot Loader Partition</entry>
<entry>Partition is mounted read-only</entry>
</row>
<row>
- <entry><constant>SD_GPT_FLAG_NO_AUTO</constant> <constant>0x8000000000000000</constant></entry>
+ <entry><constant>SD_GPT_FLAG_NO_AUTO</constant></entry>
+ <entry><constant>0x8000000000000000</constant></entry>
<entry><filename>/</filename>, <filename>/home/</filename>, <filename>/srv/</filename>, <filename>/var/</filename>, <filename>/var/tmp/</filename>, Extended Boot Loader Partition</entry>
<entry>Partition is not mounted automatically</entry>
</row>
<row>
- <entry><constant>SD_GPT_FLAG_NO_BLOCK_IO_PROTOCOL</constant> <constant>0x0000000000000002</constant></entry>
+ <entry><constant>SD_GPT_FLAG_NO_BLOCK_IO_PROTOCOL</constant></entry>
+ <entry><constant>0x0000000000000002</constant></entry>
<entry>EFI System Partition (ESP)</entry>
<entry>Partition is not mounted automatically</entry>
</row>
<listitem><para>Pre-calculate the expected values seen in PCR register 11 after boot-up of a unified
kernel image consisting of the components specified with <option>--linux=</option>,
<option>--osrel=</option>, <option>--cmdline=</option>, <option>--initrd=</option>,
- <option>--splash=</option>, <option>--dtb=</option>, <option>--uname=</option>,
- <option>--sbat=</option>, <option>--pcrpkey=</option> see below. Only <option>--linux=</option> is
- mandatory. (Alternatively, specify <option>--current</option> to use the current values of PCR
+ <option>--ucode=</option>, <option>--splash=</option>, <option>--dtb=</option>,
+ <option>--uname=</option>, <option>--sbat=</option>, <option>--pcrpkey=</option> see below.
+ Only <option>--linux=</option> is mandatory. (Alternatively, specify <option>--current</option> to use the current values of PCR
register 11 instead.)</para>
<xi:include href="version-info.xml" xpointer="v252"/>
<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>--ucode=<replaceable>PATH</replaceable></option></term>
<term><option>--splash=<replaceable>PATH</replaceable></option></term>
<term><option>--dtb=<replaceable>PATH</replaceable></option></term>
<term><option>--uname=<replaceable>PATH</replaceable></option></term>
<varlistentry>
<term><option>--recovery-pin=</option></term>
- <listitem><para>Takes a boolean. Defaults to false. Honoured by <command>make-policy</command>. If
- true, will query the user for a PIN to unlock the TPM2 NV index with. If no policy was created before
- this PIN is used to protect the newly allocated NV index. If a policy has been created before the PIN
- is used to unlock write access to the NV index. If this option is not used a PIN is automatically
- generated. Regardless if user supplied or automatically generated, it is stored in encrypted form in
- the policy metadata file. The recovery PIN may be used to regain write access to an NV index in case
- the access policy became out of date.</para>
+ <listitem><para>Takes one of <literal>hide</literal>, <literal>show</literal> or
+ <literal>query</literal>. Defaults to <literal>hide</literal>. Honoured by
+ <command>make-policy</command>. If <literal>query</literal>, will query the user for a PIN to unlock
+ the TPM2 NV index with. If no policy was created before, this PIN is used to protect the newly
+ allocated NV index. If a policy has been created before, the PIN is used to unlock write access to
+ the NV index. If either <literal>hide</literal> or <literal>show</literal> is used, a PIN is
+ automatically generated, and — only in case of <literal>show</literal> — displayed on
+ screen. Regardless if user supplied or automatically generated, it is stored in encrypted form in the
+ policy metadata file. The recovery PIN may be used to regain write access to an NV index in case the
+ access policy became out of date.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<listitem><para>An <literal>.initrd</literal> section with the initrd.</para></listitem>
+ <listitem><para>A <literal>.ucode</literal> section with an initrd containing microcode, to be handed
+ to the kernel before any other initrd. This initrd must not be compressed.</para></listitem>
+
<listitem><para>A <literal>.splash</literal> section with an image (in the Windows
<filename>.BMP</filename> format) to show on screen before invoking the kernel.</para></listitem>
configuration.</para>
<para>In case Secure Boot is enabled, these files will be validated using keys in UEFI DB, Shim's DB or
- Shim's MOK, and will be rejected otherwise. Additionally, if the both the addon and the UKI contain a a
+ Shim's MOK, and will be rejected otherwise. Additionally, if both the addon and the UKI contain a
<literal>.uname</literal> section, the addon will be rejected if they do not match exactly. It is
recommended to always add a <literal>.sbat</literal> section to all signed addons, so that they may be
revoked with a SBAT policy update, without requiring blocklisting via DBX/MOKX. The
core kernel, the embedded initrd and kernel command line (see above for a full list).</para>
<para>Also note that the Linux kernel will measure all initrds it receives into TPM PCR 9. This means
- every type of initrd will be measured two or three times: the initrd embedded in the kernel image will be
+ every type of initrd will be measured two or three times: the initrds embedded in the kernel image will be
measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized from credentials (and the one synthesized
from configuration extensions) will be measured to both PCR 9 and PCR 12; the initrd synthesized from
system extensions will be measured to both PCR 4 and PCR 9. Let's summarize the OS resources and the PCRs
<entry>4 + 9 + 11</entry>
</row>
+ <row>
+ <entry>Microcode initrd (embedded in unified PE binary)</entry>
+ <entry>4 + 9 + 11</entry>
+ </row>
+
<row>
<entry>Default kernel command line (embedded in unified PE binary)</entry>
<entry>4 + 11</entry>
<?xml version='1.0'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
- "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+<!ENTITY % entities SYSTEM "custom-entities.ent" >
+%entities;
+]>
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-vmspawn" conditional="ENABLE_VMSPAWN"
<listitem><para>Directory to use as file system root for the virtual machine.</para>
- <para>One of either <option>--directory=</option> or <option>--image=</option> must be specified.</para>
+ <para>One of either <option>--directory=</option> or <option>--image=</option> must be specified.
+ If neither are specified <option>--directory=.</option> is assumed.</para>
<para>Note: If mounting a non-root owned directory you may require <option>--private-users=</option>
- to map into the user's subuid namespace.</para>
+ to map into the user's subuid namespace. An example of how to use <constant>/etc/subuid</constant>
+ for this is given later.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
<varlistentry>
<term><option>--cpus=<replaceable>CPUS</replaceable></option></term>
- <listitem><para>Configures the number of CPUs to start the virtual machine with.
+ <listitem><para>The number of CPUs to start the virtual machine with.
Defaults to 1.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
<varlistentry>
<term><option>--ram=<replaceable>BYTES</replaceable></option></term>
- <listitem><para>Configures the amount of memory to start the virtual machine with.
+ <listitem><para>The amount of memory to start the virtual machine with.
Defaults to 2G.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
<varlistentry>
<term><option>--kvm=<replaceable>BOOL</replaceable></option></term>
- <listitem><para>Configures whether to use KVM. If the option is not specified KVM support will be
+ <listitem><para>If <option>--kvm=</option> is not specified KVM support will be
detected automatically. If true, KVM is always used, and if false, KVM is never used.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
<varlistentry>
<term><option>--vsock=<replaceable>BOOL</replaceable></option></term>
- <listitem>
- <para>Configure whether to use VSOCK networking.</para>
+ <listitem><para>If <option>--vsock=</option> is not specified VSOCK networking support will be
+ detected automatically. If true, VSOCK networking is always used, and if false, VSOCK networking is never used.</para>
- <para>If the option is not specified VSOCK support will be detected automatically. If yes is
- specified VSOCK is always used, and vice versa if no is set VSOCK are never used.</para>
- <xi:include href="version-info.xml" xpointer="v255"/>
- </listitem>
+ <xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--vsock-cid=<replaceable>CID</replaceable></option></term>
<listitem>
- <para>Configure vmspawn to use a specific CID for the guest.</para>
-
- <para>If the option is not specified or an empty argument is supplied the guest will be assigned a random CID.</para>
-
- <para>Valid CIDs are in the range <constant>3</constant> to <constant>4294967294</constant> (<constant>0xFFFF_FFFE</constant>).
- CIDs outside of this range are reserved.</para>
+ <para>Sets the specific CID to use for the guest.
+ Valid CIDs are in the range <constant>3</constant> to <constant>4294967294</constant> (<constant>0xFFFF_FFFE</constant>).
+ CIDs outside of this range are reserved. By default vmspawn will attempt to derive a CID for the guest derived from the machine name,
+ falling back to a random CID if this CID is taken.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
<term><option>--tpm=<replaceable>BOOL</replaceable></option></term>
<listitem>
- <para>Configure whether to use VM with a virtual TPM or not.</para>
-
- <para>If the option is not specified vmspawn will detect the presence of <citerefentry project='debian'>
+ <para>If <option>--tpm=</option> is not specified vmspawn will detect the presence of <citerefentry project='debian'>
<refentrytitle>swtpm</refentrytitle><manvolnum>8</manvolnum></citerefentry> and use it if available.
If yes is specified <citerefentry project='debian'><refentrytitle>swtpm</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- is always used, and vice versa if no is set <citerefentry project='debian'><refentrytitle>swtpm</refentrytitle>
+ is always used, and if no is set <citerefentry project='debian'><refentrytitle>swtpm</refentrytitle>
<manvolnum>8</manvolnum></citerefentry> is never used.</para>
<para>Note: the virtual TPM used may change in future.</para>
<term><option>--linux=<replaceable>PATH</replaceable></option></term>
<listitem>
- <para>Set the linux kernel image to use for direct kernel boot.</para>
-
- <para>If no kernel was installed into the image then the image will fail to boot.</para>
+ <para>Set the linux kernel image to use for direct kernel boot.
+ If a directory type image is used and <option>--linux=</option> was omitted, vmspawn will search for boot loader entries
+ according to the
+ <ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader Specification</ulink> assuming
+ XBOOTLDR to be located at /boot and ESP to be /efi respectively.
+ If no kernel was installed into the image then the image will fail to boot.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
<term><option>--initrd=<replaceable>PATH</replaceable></option></term>
<listitem>
- <para>Set the initrd to use for direct kernel boot.</para>
+ <para>Set the initrd to use for direct kernel boot.
+ If the <option>--linux=</option> supplied is a
+ <ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader Specification</ulink>
+ Type #2 entry, then this argument is not required.
+ If no initrd was installed into the image then the image will fail to boot.</para>
- <para>If the linux kernel supplied is a UKI then this argument is not required.</para>
-
- <para>If the option is specified multiple times vmspawn will merge the initrds together.</para>
-
- <para>If no initrd was installed into the image then the image will fail to boot.</para>
+ <para><option>--initrd=</option> can be specified multiple times and vmspawn will merge them together.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
<term><option>--private-users=<replaceable>UID_SHIFT[:UID_RANGE]</replaceable></option></term>
<listitem><para>Controls user namespacing under <option>--directory=</option>.
- If enabled, <command>virtiofsd</command> is instructed to map user and group ids (UIDs and GIDs).
+ If enabled,
+ <citerefentry><refentrytitle>virtiofsd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ is instructed to map user and group ids (UIDs and GIDs).
This involves mapping the private UIDs/GIDs used in the virtual machine (starting with the virtual machine's
root user 0 and up) to a range of UIDs/GIDs on the host that are not used for other purposes (usually in the
range beyond the host's UID/GID 65536).</para>
for more information.</para>
<para>By default <literal>ed25519</literal> keys are generated, however <literal>rsa</literal> keys
- may also be useful if the VM has a particularly old version of <command>sshd</command></para>.
+ may also be useful if the VM has a particularly old version of <command>sshd</command>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
$ systemd-vmspawn --image=image.raw
</programlisting>
</example>
+
+ <example>
+ <title>Import and run a Fedora 39 Cloud image using machinectl</title>
+
+ <programlisting>
+$ curl -L \
+ -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-Base-&fedora_latest_version;-&fedora_cloud_release;.x86_64.raw.xz \
+ -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM \
+ -O https://fedoraproject.org/fedora.gpg
+$ gpgv --keyring ./fedora.gpg Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM
+$ sha256sum -c Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM
+# machinectl import-raw Fedora-Cloud-Base-&fedora_latest_version;-&fedora_cloud_release;.x86_64.raw.xz fedora-&fedora_latest_version;-cloud
+# systemd-vmspawn -M fedora-&fedora_latest_version;-cloud
+ </programlisting>
+ </example>
+
+ <example>
+ <title>Build and run systemd's system image and forward the VM's journal to a local file</title>
+
+ <programlisting>
+$ mkosi build
+$ systemd-vmspawn \
+ -D mkosi.output/system \
+ --private-users $(grep $(whoami) /etc/subuid | cut -d: -f2) \
+ --linux mkosi.output/system.efi \
+ --forward-journal=vm.journal \
+ enforcing=0
+ </programlisting>
+
+ <para>Note: this example also uses a kernel command line argument to ensure SELinux isn't started in enforcing mode.</para>
+ </example>
+
+ <example>
+ <title>SSH into a running VM using <command>systemd-ssh-proxy</command></title>
+
+ <programlisting>
+$ mkosi build
+$ my_vsock_cid=3735928559
+$ systemd-vmspawn \
+ -D mkosi.output/system \
+ --private-users $(grep $(whoami) /etc/subuid | cut -d: -f2) \
+ --linux mkosi.output/system.efi \
+ --vsock-cid $my_vsock_cid \
+ enforcing=0
+$ ssh root@vsock/$my_vsock_cid -i /run/user/$UID/systemd/vmspawn/machine-*-system-ed25519
+ </programlisting>
+ </example>
</refsect1>
<refsect1>
<member><citerefentry project='debian'><refentrytitle>mkosi</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>importctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader Specification</ulink></member>
</simplelist></para>
</refsect1>
</refentry>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>UseDomains=</varname></term>
+ <listitem>
+ <para>Specifies the protocol-independent default value for the same settings in
+ [IPv6AcceptRA], [DHCPv4], and [DHCPv6] sections below. Takes a boolean, or the special value
+ <option>route</option>. See also the same setting in [DHCPv4] below. Defaults to unset.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>Domains=</varname></term>
<listitem>
<varlistentry>
<term><varname>IPv6AcceptRA=</varname></term>
<listitem>
- <para>Takes a boolean. Controls IPv6 Router Advertisement (RA) reception support for the
- interface. If true, RAs are accepted; if false, RAs are ignored. When RAs are accepted, they
- 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
- devices aggregated in a bond device or when link-local addressing is disabled.</para>
+ <para>Takes a boolean. Controls IPv6 Router Advertisement (RA) reception support for the interface.
+ If true, RAs are accepted; if false, RAs are ignored. When RAs are accepted, they 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. Defaults to false for bridge devices, when IP forwarding is enabled,
+ <varname>IPv6SendRA=</varname> or <varname>KeepMaster=</varname> is enabled. Otherwise, enabled by
+ default. Cannot be enabled on 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>
effect of the <option>Domains=</option> setting. If set to <option>route</option>, the domain name
received from the DHCP server will be used for routing DNS queries only, but not for searching,
similarly to the effect of the <option>Domains=</option> setting when the argument is prefixed with
- <literal>~</literal>. When unspecified, the value specified in the same setting in
- <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
- which defaults to <literal>no</literal>, will be used.</para>
+ <literal>~</literal>.</para>
+
+ <para>When unspecified, the value specified in the same setting in the [Network] section will be
+ used. When it is unspecified, the value specified in the same setting in the [DHCPv4] section in
+ <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ will be used. When it is unspecified, the value specified in the same setting in the [Network]
+ section in
+ <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ will be used. When none of them are specified, defaults to <literal>no</literal>.</para>
<para>It is recommended to enable this option only on trusted networks, as setting this
affects resolution of all hostnames, in particular of single-label names. It is generally
<para>Additional sections will be inserted into the UKI, either automatically or only if a specific
option is provided. See the discussions of
+ <varname>Microcode=</varname>/<option>--microcode=</option>,
<varname>Cmdline=</varname>/<option>--cmdline=</option>,
<varname>OSRelease=</varname>/<option>--os-release=</option>,
<varname>DeviceTree=</varname>/<option>--devicetree=</option>,
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>Microcode=<replaceable>UCODE</replaceable></varname></term>
+ <term><option>--microcode=<replaceable>UCODE</replaceable></option></term>
+
+ <listitem><para>Path to initrd containing microcode updates. If not specified, the section
+ will not be present.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>Cmdline=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></varname></term>
<term><option>--cmdline=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
#####################################################################
+if get_option('integration-tests') != false
+ system_mkosi = custom_target('system_mkosi',
+ build_always_stale : true,
+ output : 'system',
+ console : true,
+ command : ['mkosi', '-C', meson.project_source_root(), '--image=system', '--format=disk', '--output-dir', meson.project_build_root() / '@OUTPUT@', '--without-tests', '-fi', 'build'],
+ depends : [executables_by_name['bootctl'], executables_by_name['systemd-measure'], executables_by_name['systemd-repart'], ukify],
+ )
+endif
+
+############################################################
+
subdir('rules.d')
subdir('test')
description : 'install test executables')
option('log-message-verification', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
description : 'do fake printf() calls to verify format strings')
+option('integration-tests', type : 'boolean', value : false,
+ description : 'run the integration tests')
option('ok-color', type : 'combo',
choices : ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
# SPDX-License-Identifier: LGPL-2.1-or-later
[Config]
-Images=system
+@Images=system
MinimumVersion=23~devel
[Output]
@SELinuxRelabel=no
BuildSourcesEphemeral=yes
+KernelCommandLine=systemd.crash_shell
+ systemd.log_level=debug,console:info
+ systemd.log_ratelimit_kmsg=0
+ systemd.journald.forward_to_console
+ systemd.journald.max_level_console=warning
+ # Disable the kernel's ratelimiting on userspace logging to kmsg.
+ printk.devkmsg=on
+ # Make sure /sysroot is mounted rw in the initrd.
+ rw
+ # Lower the default device timeout so we get a shell earlier if the root device does
+ # not appear for some reason.
+ systemd.default_device_timeout_sec=20
+ # Make sure no LSMs are enabled by default.
+ apparmor=0
+ selinux=0
+ enforcing=0
+ systemd.early_core_pattern=/core
+ systemd.firstboot=no
+ systemd.setenv=SYSTEMD_ENABLE_LOG_CONTEXT=yes
+ SYSTEMD_ENABLE_LOG_CONTEXT=yes
+
[Host]
@Incremental=yes
@RuntimeSize=8G
@RuntimeBuildSources=yes
+@QemuSmp=2
ToolsTreePackages=virtiofsd
-KernelCommandLineExtra=systemd.crash_shell
- systemd.log_level=debug,console:info
- systemd.log_ratelimit_kmsg=0
- systemd.journald.forward_to_console
- systemd.journald.max_level_console=warning
- # Disable the kernel's ratelimiting on userspace logging to kmsg.
- printk.devkmsg=on
- # Make sure /sysroot is mounted rw in the initrd.
- rw
- # Lower the default device timeout so we get a shell earlier if the root device does
- # not appear for some reason.
- systemd.default_device_timeout_sec=10
- # Make sure no LSMs are enabled by default.
- apparmor=0
- selinux=0
- enforcing=0
- systemd.early_core_pattern=/core
- systemd.firstboot=no
- systemd.setenv=SYSTEMD_ENABLE_LOG_CONTEXT=yes
- SYSTEMD_ENABLE_LOG_CONTEXT=yes
[Content]
Autologin=yes
+ExtraTrees=
+ %D/mkosi.crt:/usr/lib/verity.d/mkosi.crt # sysext verification key
+
Packages=
acl
bash-completion
#!/bin/bash
# SPDX-License-Identifier: LGPL-2.1-or-later
-set -ex
+set -e
# shellcheck source=/dev/null
. /usr/lib/os-release
# on the makepkg command line so we append to /etc/makepkg.conf instead. The rootfs is overlaid with a
# writable tmpfs during the build script so these changes don't end up in the image itself.
tee --append /etc/makepkg.conf >/dev/null <<EOF
-CFLAGS="$CFLAGS -O0 -Wp,-D_FORTIFY_SOURCE=0"
+CFLAGS="$CFLAGS -O${OPTIMIZATION:-0} -Wp,-U_FORTIFY_SOURCE"
OPTIONS=(
docs
!libtool
[Content]
VolatilePackages=
systemd
- systemd-ukify
- systemd-sysvcompat
+ systemd-libs
systemd-resolvconf
+ systemd-sysvcompat
systemd-tests
+ systemd-ukify
Packages=
bpf
git
gnutls
iproute
+ iputils
linux
man-db
openbsd-netcat
pacman
pkgconf
polkit
+ procps-ng
quota-tools
sbsigntools
shadow
InitrdVolatilePackages=
systemd
+ systemd-libs
systemd-sysvcompat
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Environment=WITH_DEBUG=1
+
+[Content]
+VolatilePackages=systemd-debug
ARCH="$(rpm --eval %_arch)"
SRCDEST="/usr/src/debug/systemd-$VERSION-${RELEASE}${DIST}.$ARCH"
-# TODO: Drop -D_FORTIFY_SOURCE when we switch to CentOS Stream 10.
-EXTRA_CFLAGS="-O0 -Wp,-D_FORTIFY_SOURCE=0"
+# TODO: Drop -U_FORTIFY_SOURCE when we switch to CentOS Stream 10.
+CFLAGS="$(rpm --define "_fortify_level 0" --undefine _lto_cflags --eval %build_cflags) -O${OPTIMIZATION:-0} -Wp,-U_FORTIFY_SOURCE"
if ((WITH_DEBUG)); then
- EXTRA_CFLAGS="$EXTRA_CFLAGS -ffile-prefix-map=../src=$SRCDEST"
+ CFLAGS="$CFLAGS -ffile-prefix-map=../src=$SRCDEST"
fi
IFS=
# TODO: Replace meson_build and meson_install overrides with "--undefine __meson_verbose" once
# https://github.com/mesonbuild/meson/pull/12835 is available.
# shellcheck disable=SC2046
-rpmbuild \
+ANNOBIN="no-active-checks" rpmbuild \
-bb \
--build-in-place \
--with upstream \
$( ((WITH_DEBUG)) || echo "debug_package %{nil}") \
--define "version_override $VERSION" \
--define "release_override $RELEASE" \
- --define "build_cflags $(rpm --eval %build_cflags) $EXTRA_CFLAGS" \
+ --define "build_cflags $CFLAGS" \
--define "meson_build %{shrink:%{__meson} compile -C %{_vpath_builddir} -j %{_smp_build_ncpus} %{nil}}" \
--define "meson_install %{shrink:DESTDIR=%{buildroot} %{__meson} install -C %{_vpath_builddir} --no-rebuild --quiet %{nil}}" \
--define "meson_extra_configure_options -D mode=developer -D b_sanitize=${SANITIZERS:-none}" \
--define "_find_debuginfo_dwz_opts %{nil}" \
--define "_fortify_level 0" \
--undefine _lto_cflags \
+ --undefine _package_note_flags \
--noclean \
"pkg/$ID/systemd.spec"
[Content]
VolatilePackages=
systemd
- systemd-udev
+ systemd-boot
systemd-container
- systemd-repart
- systemd-resolved
+ systemd-devel
+ systemd-journal-remote
systemd-networkd
- systemd-boot
+ systemd-networkd-defaults
+ systemd-oomd-defaults
+ systemd-pam
+ systemd-resolved
systemd-tests
+ systemd-udev
systemd-ukify
- systemd-pam
- systemd-oomd-defaults
- systemd-journal-remote
- systemd-networkd-defaults
Packages=
bpftool
integritysetup
iproute
iproute-tc
+ iputils
kernel-core
libasan
libcap-ng-utils
p11-kit
pam
passwd
+ policycoreutils
polkit
procps-ng
quota
selinux-policy
selinux-policy-targeted
setools-console
- policycoreutils
util-linux
vim-common
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Environment=WITH_DEBUG=1
+
+[Content]
+VolatilePackages=
+ systemd-container-debuginfo
+ systemd-debuginfo
+ systemd-debugsource
+ systemd-journal-remote-debuginfo
+ systemd-libs-debuginfo
+ systemd-networkd-debuginfo
+ systemd-pam-debuginfo
+ systemd-resolved-debuginfo
+ systemd-tests-debuginfo
+ systemd-udev-debuginfo
--- /dev/null
+g root 0
+g bin 1
+g daemon 2
+g sys 3
+g adm 4
+g tty 5
+g disk 6
+g lp 7
+g mem 8
+g kmem 9
+g wheel 10
+g cdrom 11
+g mail 12
+g man 15
+g dialout 18
+g floppy 19
+g games 20
+g tape 33
+g video 39
+g ftp 50
+g lock 54
+g audio 63
+g users 100
+g nobody 65534
--- /dev/null
+u root 0:0 "Super User" /root /bin/bash
+u bin 1:1 "bin" /bin -
+u daemon 2:2 "daemon" /sbin -
+u adm 3:4 "adm" /var/adm -
+u lp 4:7 "lp" /var/spool/lpd -
+u sync 5:0 "sync" /sbin /bin/sync
+u shutdown 6:0 "shutdown" /sbin /sbin/shutdown
+u halt 7:0 "halt" /sbin /sbin/halt
+u mail 8:12 "mail" /var/spool/mail -
+u operator 11:0 "operator" /root -
+u games 12:100 "games" /usr/games -
+u ftp 14:50 "FTP User" /var/ftp -
+u nobody 65534:65534 "Kernel Overflow User" - -
mv debian/changelog.new debian/changelog
build() {
- DEB_BUILD_OPTIONS="\
+ DEB_BUILD_OPTIONS=$(awk '$1=$1' <<<"\
$( ((WITH_TESTS)) || echo nocheck) \
$( ((WITH_DOCS)) || echo nodoc) \
$( ((WITH_DEBUG)) || echo nostrip) \
terse \
optimize=-lto \
- noopt \
- " \
- DEB_BUILD_PROFILES="\
+ hardening=-fortify \
+ ") \
+ DEB_BUILD_PROFILES=$(awk '$1=$1' <<<"\
$( ((WITH_TESTS)) || echo nocheck) \
$( ((WITH_DOCS)) || echo nodoc) \
pkg.systemd.upstream \
- " \
+ ") \
+ DEB_CFLAGS_APPEND="-O${OPTIMIZATION:-0}" \
DPKG_FORCE="unsafe-io" \
DPKG_DEB_COMPRESSOR_TYPE="none" \
DH_MISSING="--fail-missing" \
[Content]
VolatilePackages=
+ libnss-myhostname
+ libnss-mymachines
+ libnss-resolve
+ libnss-systemd
+ libpam-systemd
+ libsystemd-dev
+ libudev-dev
systemd
- systemd-userdbd
+ systemd-boot
+ systemd-boot-efi
+ systemd-container
+ systemd-coredump
+ systemd-dev
+ systemd-homed
+ systemd-journal-remote
systemd-oomd
+ systemd-resolved
systemd-sysv
systemd-tests
systemd-timesyncd
- systemd-resolved
- systemd-homed
- systemd-coredump
- systemd-journal-remote
- systemd-container
- systemd-boot
systemd-ukify
+ systemd-userdbd
udev
Packages=
fdisk
git-core
iproute2
+ iputils-ping
isc-dhcp-server
libcap-ng-utils
libtss2-rc0
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Environment=WITH_DEBUG=1
+
+[Content]
+VolatilePackages=
+ libnss-myhostname-dbgsym
+ libnss-mymachines-dbgsym
+ libnss-resolve-dbgsym
+ libnss-systemd-dbgsym
+ libpam-systemd-dbgsym
+ libsystemd-shared-dbgsym
+ libsystemd0-dbgsym
+ libudev1-dbgsym
+ systemd-boot-dbgsym
+ systemd-container-dbgsym
+ systemd-coredump-dbgsym
+ systemd-dbgsym
+ systemd-homed-dbgsym
+ systemd-journal-remote-dbgsym
+ systemd-oomd-dbgsym
+ systemd-resolved-dbgsym
+ systemd-tests-dbgsym
+ systemd-timesyncd-dbgsym
+ systemd-userdbd-dbgsym
+ udev-dbgsym
%{nil}
EOF
+VERSION="$(cat meson.version)"
+RELEASE="$(date "+%Y%m%d%H%M%S" --date "@$TS")"
+
+DIST="$(rpm --eval %dist)"
+ARCH="$(rpm --eval %_arch)"
+SRCDEST="/usr/src/debug/systemd-$VERSION-${RELEASE}${DIST}.$ARCH"
+
+# TODO: Enable this when the opensuse spec stops unconditionally setting FORTIFY_SOURCE=2.
+# EXTRA_CFLAGS="-O${OPTIMIZATION:-0} -Wp,-U_FORTIFY_SOURCE"
+EXTRA_CFLAGS=""
+if ((WITH_DEBUG)); then
+ EXTRA_CFLAGS="$EXTRA_CFLAGS -ffile-prefix-map=../src=$SRCDEST"
+fi
+
build() {
IFS=
# TODO: Replace meson_build and meson_install overrides with "--undefine __meson_verbose" once
$( ((WITH_DEBUG)) || echo --define) \
$( ((WITH_DEBUG)) || echo "debug_package %{nil}") \
--define "vendor openSUSE" \
- --define "version_override $(cat meson.version)" \
- --define "release_override $(date "+%Y%m%d%H%M%S" --date "@$TS")" \
+ --define "version_override $VERSION" \
+ --define "release_override $RELEASE" \
--define "__check_files sh -c '$(rpm --eval %__check_files) | tee /tmp/unpackaged-files'" \
+ --define "build_cflags $(rpm --eval %build_cflags) $EXTRA_CFLAGS" \
--define "meson_build %{shrink:%{__meson} compile -C %{_vpath_builddir} -j %{_smp_build_ncpus} %{nil}}" \
--define "meson_install %{shrink:DESTDIR=%{buildroot} %{__meson} install -C %{_vpath_builddir} --no-rebuild --quiet %{nil}}" \
--define "meson_extra_configure_options -D mode=developer -D b_sanitize=${SANITIZERS:-none}" \
--define "__os_install_post /usr/lib/rpm/brp-suse %{nil}" \
--define "__elf_exclude_path ^/usr/lib/systemd/tests/unit-tests/.*$" \
--define "__script_requires %{nil}" \
+ --define "_find_debuginfo_dwz_opts %{nil}" \
--noclean \
"$@" \
"pkg/$ID/systemd.spec"
[Content]
VolatilePackages=
systemd
- udev
- systemd-experimental
systemd-boot
systemd-container
+ systemd-devel
+ systemd-doc
+ systemd-experimental
systemd-homed
+ systemd-lang
systemd-network
systemd-portable
systemd-sysvcompat
systemd-testsuite
+ udev
# We install gawk, gzip, grep, xz, sed, rsync and docbook-xsl-stylesheets here explicitly so that the busybox
# versions don't get installed instead.
git-core
glibc-locale-base
grep
+ group(bin)
+ group(daemon)
+ group(games)
+ group(nobody)
+ group(root)
gzip
+ iputils
kernel-kvmsmall
kmod
libasan8
openssh-server
pam
patterns-base-minimal_base
+ procps4
python3-pefile
quota
rpm-build
sed
shadow
timezone
+ user(bin)
+ user(daemon)
+ user(games)
+ user(nobody)
+ user(root)
vim
xz
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Environment=WITH_DEBUG=1
+
+[Content]
+VolatilePackages=
+ libsystemd0-debuginfo
+ libudev1-debuginfo
+ systemd-boot-debuginfo
+ systemd-container-debuginfo
+ systemd-coredump-debuginfo
+ systemd-debuginfo
+ systemd-debugsource
+ systemd-experimental-debuginfo
+ systemd-homed-debuginfo
+ systemd-journal-remote-debuginfo
+ systemd-network-debuginfo
+ systemd-portable-debuginfo
+ systemd-sysvcompat-debuginfo
+ systemd-testsuite-debuginfo
+ udev-debuginfo
--- /dev/null
+[Service]
+PassEnvironment=SYSTEMD_UNIT_PATH
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
+# Make sure serial console line wrapping is re-enabled as qemu's seabios firmware disables serial console
+# line-wrapping on boot.
+echo "tput smam || :" >>/etc/profile
+
if [ -n "$SANITIZERS" ]; then
LD_PRELOAD=$(ldd /usr/lib/systemd/systemd | grep libasan.so | awk '{print $3}')
-Subproject commit ccc32ea10164a9b6ca3098765e63f653cddc6817
+Subproject commit 124b1da79088d0f0ab33dc2d840d4733074dbf64
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-04 10:09+0100\n"
-"PO-Revision-Date: 2024-01-21 10:36+0000\n"
+"PO-Revision-Date: 2024-04-20 07:36+0000\n"
"Last-Translator: Andika Triwidada <andika@gmail.com>\n"
"Language-Team: Indonesian <https://translate.fedoraproject.org/projects/"
-"systemd/master/id/>\n"
+"systemd/main/id/>\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Weblate 5.3.1\n"
+"X-Generator: Weblate 5.4\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
#: src/home/org.freedesktop.home1.policy:73
msgid "Inhibit automatic lock of a home area"
-msgstr ""
+msgstr "Cegah penguncian otomatis dari area home"
#: src/home/org.freedesktop.home1.policy:74
-#, fuzzy
msgid ""
"Authentication is required to inhibit automatic lock of a user's home area."
-msgstr "Otentikasi diperlukan untuk memperbarui suatu area rumah pengguna."
+msgstr ""
+"Otentikasi diperlukan untuk mencegah penguncian otomatis dari area home "
+"pengguna."
#: src/home/org.freedesktop.home1.policy:83
-#, fuzzy
msgid "Activate a home area"
-msgstr "Menciptakan suatu area rumah"
+msgstr "Aktifkan suatu area home"
#: src/home/org.freedesktop.home1.policy:84
-#, fuzzy
msgid "Authentication is required to activate a user's home area."
-msgstr "Otentikasi diperlukan untuk mencipta suatu area rumah pengguna."
+msgstr "Otentikasi diperlukan untuk mengaktifkan suatu area home pengguna."
#: src/home/pam_systemd_home.c:293
#, c-format
msgstr "Otentikasi diperlukan untuk mendapatkan deskripsi sistem."
#: src/import/org.freedesktop.import1.policy:22
-#, fuzzy
msgid "Import a disk image"
-msgstr "Impor sebuah image kontainer atau VM"
+msgstr "Impor sebuah image disk"
#: src/import/org.freedesktop.import1.policy:23
-#, fuzzy
msgid "Authentication is required to import an image"
-msgstr "Otentikasi diperlukan untuk mengimpor suatu image kontainer atau VM"
+msgstr "Otentikasi diperlukan untuk mengimpor suatu image"
#: src/import/org.freedesktop.import1.policy:32
-#, fuzzy
msgid "Export a disk image"
-msgstr "Ekspor sebuah image kontainer atau VM"
+msgstr "Ekspor sebuah image disk"
#: src/import/org.freedesktop.import1.policy:33
-#, fuzzy
msgid "Authentication is required to export disk image"
-msgstr "Otentikasi diperlukan untuk mengekspor suatu image kontainer atau VM"
+msgstr "Otentikasi diperlukan untuk mengekspor suatu image disk"
#: src/import/org.freedesktop.import1.policy:42
-#, fuzzy
msgid "Download a disk image"
-msgstr "Unduh sebuah image kontainer atau VM"
+msgstr "Unduh sebuah image disk"
#: src/import/org.freedesktop.import1.policy:43
-#, fuzzy
msgid "Authentication is required to download a disk image"
-msgstr "Otentikasi diperlukan untuk mengunduh suatu image kontainer atau VM"
+msgstr "Otentikasi diperlukan untuk mengunduh suatu image disk"
#: src/import/org.freedesktop.import1.policy:52
msgid "Cancel transfer of a disk image"
-msgstr ""
+msgstr "Batalkan transfer suatu image disk"
#: src/import/org.freedesktop.import1.policy:53
-#, fuzzy
msgid ""
"Authentication is required to cancel the ongoing transfer of a disk image"
msgstr ""
-"Otentikasi diperlukan untuk mengubah kata sandi dari suatu area rumah "
-"pengguna."
+"Otentikasi diperlukan untuk membatalkan transfer suatu image disk yang "
+"sedang berjalan"
#: src/locale/org.freedesktop.locale1.policy:22
msgid "Set system locale"
ENV{MAJOR}=="", GOTO="persistent_media_ctl_end"
IMPORT{builtin}="path_id"
-ENV{ID_PATH}=="?*", KERNEL=="media*", SYMLINK+="media/by-path/$env{ID_PATH}-media-controller"
+KERNEL=="media*", ENV{ID_PATH_WITH_USB_REVISION}=="?*", SYMLINK+="media/by-path/$env{ID_PATH_WITH_USB_REVISION}-media-controller"
+KERNEL=="media*", ENV{ID_PATH_WITH_USB_REVISION}=="", ENV{ID_PATH}=="?*", SYMLINK+="media/by-path/$env{ID_PATH}-media-controller"
LABEL="persistent_media_ctl_end"
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
local -A OPTS=(
[STANDALONE]='-q --quiet --runtime --no-reload --cat --no-pager --no-legend
- --no-ask-password --enable --now -h --help --version'
+ --no-ask-password --enable --now -h --help --version
+ --clean --no-block --force'
[ARG]='-p --profile --copy -H --host -M --machine --extension'
)
comps='auto yes no'
;;
--what)
- comps='configuration state cache logs runtime all'
+ comps='configuration state cache logs runtime fdstore all'
;;
--image)
comps=$(compgen -A file -- "$cur")
(void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7);
}
- FOREACH_ARRAY(a, security_assessor_table, ELEMENTSOF(security_assessor_table)) {
+ FOREACH_ELEMENT(a, security_assessor_table) {
_cleanup_free_ char *d = NULL;
uint64_t badness;
void *data;
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
int r;
- r = tpm2_context_new(/* device= */ NULL, &c);
+ r = tpm2_context_new_or_warn(/* device= */ NULL, &c);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
r = tpm2_get_srk(
c,
if (!t)
return;
- FOREACH_ARRAY(d, t->deps, ELEMENTSOF(t->deps))
+ FOREACH_ELEMENT(d, t->deps)
*d = strv_free(*d);
t->name = mfree(t->name);
assert(u);
if (u->type == UNIT_SERVICE)
- FOREACH_ARRAY(i, SERVICE(u)->exec_command, ELEMENTSOF(SERVICE(u)->exec_command))
+ FOREACH_ELEMENT(i, SERVICE(u)->exec_command)
LIST_FOREACH(command, j, *i)
RET_GATHER(r, verify_executable(u, j, root));
if (u->type == UNIT_SOCKET)
- FOREACH_ARRAY(i, SOCKET(u)->exec_command, ELEMENTSOF(SOCKET(u)->exec_command))
+ FOREACH_ELEMENT(i, SOCKET(u)->exec_command)
LIST_FOREACH(command, j, *i)
RET_GATHER(r, verify_executable(u, j, root));
return r;
}
-static bool stderr_is_journal(void) {
+bool stderr_is_journal(void) {
_cleanup_free_ char *w = NULL;
const char *e;
uint64_t dev, ino;
assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1);
#define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1])
+bool stderr_is_journal(void);
int log_open(void);
void log_close(void);
void log_forget_fds(void);
#define FOREACH_ARRAY(i, array, num) \
_FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ))
+#define FOREACH_ELEMENT(i, array) \
+ FOREACH_ARRAY(i, array, ELEMENTSOF(array))
+
#define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \
scope type *name##_ref(type *p) { \
if (!p) \
return strcmp(fa, fb);
}
+int path_equal_or_inode_same_full(const char *a, const char *b, int flags) {
+ /* Returns true if paths are of the same entry, false if not, <0 on error. */
+
+ if (path_equal(a, b))
+ return 1;
+
+ if (!a || !b)
+ return 0;
+
+ return inode_same(a, b, flags);
+}
+
char* path_extend_internal(char **x, ...) {
size_t sz, old_sz;
char *q, *nx;
return path_compare_filename(a, b) == 0;
}
+int path_equal_or_inode_same_full(const char *a, const char *b, int flags);
static inline bool path_equal_or_inode_same(const char *a, const char *b, int flags) {
- return path_equal(a, b) || inode_same(a, b, flags) > 0;
+ return path_equal_or_inode_same_full(a, b, flags) > 0;
}
char* path_extend_internal(char **x, ...);
/* Returns a short identifier for the various VM implementations */
Virtualization detect_vm(void) {
static thread_local Virtualization cached_found = _VIRTUALIZATION_INVALID;
- bool other = false;
+ bool other = false, hyperv = false;
int xen_dom0 = 0;
Virtualization v, dmi;
v = detect_vm_cpuid();
if (v < 0)
return v;
- if (v == VIRTUALIZATION_VM_OTHER)
+ if (v == VIRTUALIZATION_MICROSOFT)
+ /* QEMU sets the CPUID string to hyperv's, in case it provides hyperv enlightenments. Let's
+ * hence not return Microsoft here but just use the other mechanisms first to make a better
+ * decision. */
+ hyperv = true;
+ else if (v == VIRTUALIZATION_VM_OTHER)
other = true;
else if (v != VIRTUALIZATION_NONE)
goto finish;
return v;
finish:
- if (v == VIRTUALIZATION_NONE && other)
- v = VIRTUALIZATION_VM_OTHER;
+ /* None of the checks above gave us a clear answer, hence let's now use fallback logic: if hyperv
+ * enlightenments are available but the VMM wasn't recognized as anything yet, it's probably
+ * Microsoft. */
+ if (v == VIRTUALIZATION_NONE) {
+ if (hyperv)
+ v = VIRTUALIZATION_MICROSOFT;
+ else if (other)
+ v = VIRTUALIZATION_VM_OTHER;
+ }
cached_found = v;
log_debug("Found VM virtualization %s", virtualization_to_string(v));
assert(ret_initrd_size);
if (entry->type != LOADER_LINUX || !entry->initrd) {
- ret_options = NULL;
- ret_initrd = NULL;
- ret_initrd_size = 0;
+ *ret_options = NULL;
+ *ret_initrd = NULL;
+ *ret_initrd_size = 0;
return EFI_SUCCESS;
}
char *a;
assert(fname);
- assert(contents_size || contents_size == 0);
+ assert(contents || contents_size == 0);
assert(target_dir_prefix);
assert(inode_counter);
assert(cpio_buffer);
DECLARE_SBAT(SBAT_STUB_SECTION_TEXT);
-static EFI_STATUS combine_initrd(
- EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size,
- const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds,
+/* Combine initrds by concatenation in memory */
+static EFI_STATUS combine_initrds(
+ const void * const initrds[], const size_t initrd_sizes[], size_t n_initrds,
Pages *ret_initr_pages, size_t *ret_initrd_size) {
- size_t n;
+ size_t n = 0;
assert(ret_initr_pages);
assert(ret_initrd_size);
- /* Combines four initrds into one, by simple concatenation in memory */
-
- n = ALIGN4(initrd_size); /* main initrd might not be padded yet */
-
- for (size_t i = 0; i < n_extra_initrds; i++) {
- if (!extra_initrds[i])
+ for (size_t i = 0; i < n_initrds; i++) {
+ if (!initrds[i])
continue;
- if (n > SIZE_MAX - extra_initrd_sizes[i])
+ /* some initrds (the ones from UKI sections) need padding,
+ * pad all to be safe */
+ size_t initrd_size = ALIGN4(initrd_sizes[i]);
+ if (n > SIZE_MAX - initrd_size)
return EFI_OUT_OF_RESOURCES;
- n += extra_initrd_sizes[i];
+ n += initrd_size;
}
_cleanup_pages_ Pages pages = xmalloc_pages(
EFI_SIZE_TO_PAGES(n),
UINT32_MAX /* Below 4G boundary. */);
uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr);
- if (initrd_base != 0) {
+ for (size_t i = 0; i < n_initrds; i++) {
+ if (!initrds[i])
+ continue;
+
size_t pad;
- /* Order matters, the real initrd must come first, since it might include microcode updates
- * which the kernel only looks for in the first cpio archive */
- p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
+ p = mempcpy(p, initrds[i], initrd_sizes[i]);
- pad = ALIGN4(initrd_size) - initrd_size;
+ pad = ALIGN4(initrd_sizes[i]) - initrd_sizes[i];
if (pad > 0) {
memzero(p, pad);
p += pad;
}
}
- for (size_t i = 0; i < n_extra_initrds; i++) {
- if (!extra_initrds[i])
- continue;
-
- p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]);
- }
-
assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p);
*ret_initr_pages = pages;
void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL;
char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL;
_cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL;
- size_t linux_size, initrd_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0;
- EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
+ size_t linux_size, initrd_size, ucode_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0;
+ EFI_PHYSICAL_ADDRESS linux_base, initrd_base, ucode_base, dt_base;
_cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
CLEANUP_ARRAY(dt_filenames_addons_global, n_dts_addons_global, dt_filenames_free);
CLEANUP_ARRAY(dt_filenames_addons_uki, n_dts_addons_uki, dt_filenames_free);
+ if (szs[UNIFIED_SECTION_UNAME] > 0)
+ uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME],
+ szs[UNIFIED_SECTION_UNAME]);
+
/* Now that we have the UKI sections loaded, also load global first and then local (per-UKI)
* addons. The data is loaded at once, and then used later. */
err = load_addons(
/* Show splash screen as early as possible */
graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]);
- if (szs[UNIFIED_SECTION_UNAME] > 0)
- uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME],
- szs[UNIFIED_SECTION_UNAME]);
-
if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) {
/* Let's measure the passed kernel command line into the TPM. Note that this possibly
* duplicates what we already did in the boot menu, if that was already used. However, since
initrd_size = szs[UNIFIED_SECTION_INITRD];
initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0;
+ ucode_size = szs[UNIFIED_SECTION_UCODE];
+ ucode_base = ucode_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_UCODE] : 0;
+
_cleanup_pages_ Pages initrd_pages = {};
- if (credential_initrd || global_credential_initrd || sysext_initrd || confext_initrd || pcrsig_initrd || pcrpkey_initrd) {
- /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */
- err = combine_initrd(
- initrd_base, initrd_size,
+ if (ucode_base || credential_initrd || global_credential_initrd || sysext_initrd || confext_initrd || pcrsig_initrd || pcrpkey_initrd) {
+ /* If we have generated initrds dynamically or there is a microcode initrd, combine them with the built-in initrd. */
+ err = combine_initrds(
(const void*const[]) {
+ /* Microcode must always be first as kernel only scans uncompressed cpios
+ * and later initrds might be compressed. */
+ PHYSICAL_ADDRESS_TO_POINTER(ucode_base),
+ PHYSICAL_ADDRESS_TO_POINTER(initrd_base),
credential_initrd,
global_credential_initrd,
sysext_initrd,
pcrpkey_initrd,
},
(const size_t[]) {
+ ucode_size,
+ initrd_size,
credential_initrd_size,
global_credential_initrd_size,
sysext_initrd_size,
pcrsig_initrd_size,
pcrpkey_initrd_size,
},
- 6,
+ 8,
&initrd_pages, &initrd_size);
if (err != EFI_SUCCESS)
return err;
" --osrel=PATH Path to os-release file %7$s .osrel\n"
" --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n"
" --initrd=PATH Path to initrd image file %7$s .initrd\n"
+ " --ucode=PATH Path to microcode image file %7$s .ucode\n"
" --splash=PATH Path to splash bitmap file %7$s .splash\n"
" --dtb=PATH Path to Devicetree file %7$s .dtb\n"
" --uname=PATH Path to 'uname -r' file %7$s .uname\n"
ARG_OSREL,
ARG_CMDLINE,
ARG_INITRD,
+ ARG_UCODE,
ARG_SPLASH,
ARG_DTB,
ARG_UNAME,
{ "osrel", required_argument, NULL, ARG_OSREL },
{ "cmdline", required_argument, NULL, ARG_CMDLINE },
{ "initrd", required_argument, NULL, ARG_INITRD },
+ { "ucode", required_argument, NULL, ARG_UCODE },
{ "splash", required_argument, NULL, ARG_SPLASH },
{ "dtb", required_argument, NULL, ARG_DTB },
{ "uname", required_argument, NULL, ARG_UNAME },
if (!crt || !crt->cgroup_path)
return;
- FOREACH_ARRAY(i, crt->memory_accounting_last, ELEMENTSOF(crt->memory_accounting_last))
+ FOREACH_ELEMENT(i, crt->memory_accounting_last)
*i = UINT64_MAX;
}
.cgroup_invalidated_mask = _CGROUP_MASK_ALL,
};
- FOREACH_ARRAY(i, crt->memory_accounting_last, ELEMENTSOF(crt->memory_accounting_last))
+ FOREACH_ELEMENT(i, crt->memory_accounting_last)
*i = UINT64_MAX;
- FOREACH_ARRAY(i, crt->io_accounting_base, ELEMENTSOF(crt->io_accounting_base))
+ FOREACH_ELEMENT(i, crt->io_accounting_base)
*i = UINT64_MAX;
- FOREACH_ARRAY(i, crt->io_accounting_last, ELEMENTSOF(crt->io_accounting_last))
+ FOREACH_ELEMENT(i, crt->io_accounting_last)
*i = UINT64_MAX;
- FOREACH_ARRAY(i, crt->ip_accounting_extra, ELEMENTSOF(crt->ip_accounting_extra))
+ FOREACH_ELEMENT(i, crt->ip_accounting_extra)
*i = UINT64_MAX;
return TAKE_PTR(crt);
if (!sd_bus_track_contains(j->bus_track, sd_bus_message_get_sender(message))) {
/* And for everybody else consult polkit */
- r = bus_verify_manage_units_async(j->unit->manager, message, error);
+ r = bus_verify_manage_units_async(j->manager, message, error);
if (r < 0)
return r;
if (r == 0)
if (!j->sent_dbus_new_signal && !including_new)
return;
- if (MANAGER_IS_RELOADING(j->unit->manager))
+ if (MANAGER_IS_RELOADING(j->manager))
return;
bus_job_send_change_signal(j);
if (j->bus_track)
return 0;
- return sd_bus_track_new(j->unit->manager->api_bus, &j->bus_track, bus_job_track_handler, j);
+ return sd_bus_track_new(j->manager->api_bus, &j->bus_track, bus_job_track_handler, j);
}
int bus_job_coldplug_bus_track(Job *j) {
- int r;
_cleanup_strv_free_ char **deserialized_clients = NULL;
+ int r;
assert(j);
assert(j);
assert(m);
- if (sd_bus_message_get_bus(m) != j->unit->manager->api_bus) {
+ if (sd_bus_message_get_bus(m) != j->manager->api_bus) {
j->ref_by_private_bus = true;
return 0;
}
int (*send_message)(sd_bus *bus, void *userdata),
void *userdata) {
- sd_bus *b;
- int r, ret = 0;
+ int r = 0;
+
+ assert(m);
+ assert(send_message);
/* Send to all direct buses, unconditionally */
+ sd_bus *b;
SET_FOREACH(b, m->private_buses) {
/* Don't bother with enqueuing these messages to clients that haven't started yet */
if (sd_bus_is_ready(b) <= 0)
continue;
- r = send_message(b, userdata);
- if (r < 0)
- ret = r;
+ RET_GATHER(r, send_message(b, userdata));
}
/* Send to API bus, but only if somebody is subscribed */
if (m->api_bus &&
(sd_bus_track_count(m->subscribed) > 0 ||
- sd_bus_track_count(subscribed2) > 0)) {
- r = send_message(m->api_bus, userdata);
- if (r < 0)
- ret = r;
- }
+ sd_bus_track_count(subscribed2) > 0))
+ RET_GATHER(r, send_message(m->api_bus, userdata));
- return ret;
+ return r;
}
void bus_track_serialize(sd_bus_track *t, FILE *f, const char *prefix) {
return log_exec_error_errno(c, p, r, "Failed to set sockaddr for '%s': %m", of->path);
sa_len = r;
- FOREACH_ARRAY(i, socket_types, ELEMENTSOF(socket_types)) {
+ FOREACH_ELEMENT(i, socket_types) {
_cleanup_close_ int fd = -EBADF;
fd = socket(AF_UNIX, *i|SOCK_CLOEXEC, 0);
static void job_set_state(Job *j, JobState state) {
assert(j);
+ assert(j->manager);
assert(state >= 0);
assert(state < _JOB_STATE_MAX);
return;
if (j->state == JOB_RUNNING)
- j->unit->manager->n_running_jobs++;
+ j->manager->n_running_jobs++;
else {
assert(j->state == JOB_WAITING);
- assert(j->unit->manager->n_running_jobs > 0);
+ assert(j->manager->n_running_jobs > 0);
- j->unit->manager->n_running_jobs--;
+ j->manager->n_running_jobs--;
- if (j->unit->manager->n_running_jobs <= 0)
- j->unit->manager->jobs_in_progress_event_source = sd_event_source_disable_unref(j->unit->manager->jobs_in_progress_event_source);
+ if (j->manager->n_running_jobs <= 0)
+ j->manager->jobs_in_progress_event_source = sd_event_source_disable_unref(j->manager->jobs_in_progress_event_source);
}
}
Job **pj;
int r;
+ assert(j);
+ assert(j->manager);
assert(!j->installed);
if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION)
j->installed = true;
if (j->state == JOB_RUNNING)
- j->unit->manager->n_running_jobs++;
+ j->manager->n_running_jobs++;
log_unit_debug(j->unit,
"Reinstalled deserialized job %s/%s as %u",
void job_shutdown_magic(Job *j) {
assert(j);
+ assert(j->manager);
/* The shutdown target gets some special treatment here: we
* tell the kernel to begin with flushing its disk caches, to
return;
/* This is the very beginning of the shutdown phase, so take the timestamp here */
- dual_timestamp_now(ASSERT_PTR(j->manager)->timestamps + MANAGER_TIMESTAMP_SHUTDOWN_START);
+ dual_timestamp_now(j->manager->timestamps + MANAGER_TIMESTAMP_SHUTDOWN_START);
if (!MANAGER_IS_SYSTEM(j->manager))
return;
/* In case messages on console has been disabled on boot */
- j->unit->manager->no_console_output = false;
+ j->manager->no_console_output = false;
- manager_invalidate_startup_units(j->unit->manager);
+ manager_invalidate_startup_units(j->manager);
if (detect_container() > 0)
return;
Unit *other;
assert(j);
+ assert(j->manager);
/* Checks whether this job should be GC'ed away. We only do this for jobs of units that have no effect on their
* own and just track external state. For now the only unit type that qualifies for this are .device units.
* referenced by one, and reset this whenever we notice that no private bus connections are around. This means
* the GC is a bit too conservative when it comes to jobs created by private bus connections. */
if (j->ref_by_private_bus) {
- if (set_isempty(j->unit->manager->private_buses))
+ if (set_isempty(j->manager->private_buses))
j->ref_by_private_bus = false;
else
return false;
void job_add_to_gc_queue(Job *j) {
assert(j);
+ assert(j->manager);
if (j->in_gc_queue)
return;
if (!job_may_gc(j))
return;
- LIST_PREPEND(gc_queue, j->unit->manager->gc_job_queue, j);
+ LIST_PREPEND(gc_queue, j->manager->gc_job_queue, j);
j->in_gc_queue = true;
}
return 0;
_cleanup_(sym_kmod_unrefp) struct kmod_ctx *ctx = NULL;
- FOREACH_ARRAY(kmod, kmod_table, ELEMENTSOF(kmod_table)) {
+ FOREACH_ELEMENT(kmod, kmod_table) {
if (kmod->path && access(kmod->path, F_OK) >= 0)
continue;
(void) serialize_usec(f, "pretimeout-watchdog-overridden", m->watchdog_overridden[WATCHDOG_PRETIMEOUT]);
(void) serialize_item(f, "pretimeout-watchdog-governor-overridden", m->watchdog_pretimeout_governor_overridden);
+ (void) serialize_item(f, "previous-objective", manager_objective_to_string(m->objective));
(void) serialize_item_format(f, "soft-reboots-count", "%u", m->soft_reboots_count);
for (ManagerTimestamp q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) {
(void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit);
(void) serialize_ratelimit(f, "reload-reexec-ratelimit", &m->reload_reexec_ratelimit);
- if (m->objective >= 0 && m->objective < _MANAGER_OBJECTIVE_MAX)
- (void) serialize_item_format(f, "previous-objective", "%u", (unsigned) m->objective);
-
bus_track_serialize(m->subscribed, f, "subscribed");
r = dynamic_user_serialize(m, f, fds);
else
m->soft_reboots_count = n;
} else if ((val = startswith(l, "previous-objective="))) {
- unsigned n;
+ ManagerObjective objective;
- if (safe_atou(val, &n) < 0 || n >= _MANAGER_OBJECTIVE_MAX)
- log_notice("Failed to parse objective '%s', ignoring.", val);
+ objective = manager_objective_from_string(val);
+ if (objective < 0)
+ log_notice("Failed to parse previous objective '%s', ignoring.", val);
else
- m->previous_objective = n;
+ m->previous_objective = objective;
} else {
ManagerTimestamp q;
return log_error_errno(r, "Deserialization failed: %m");
}
+ if (m->previous_objective >= 0) {
+ if (IN_SET(m->previous_objective, MANAGER_REEXECUTE, MANAGER_SOFT_REBOOT, MANAGER_SWITCH_ROOT))
+ log_debug("Launching as effect of a '%s' operation.",
+ manager_objective_to_string(m->previous_objective));
+ else
+ log_warning("Got unexpected previous objective '%s', ignoring.",
+ manager_objective_to_string(m->previous_objective));
+ }
+
/* If we are in a new soft-reboot iteration bump the counter now before starting units, so
* that they can reliably read it. We get the previous objective from serialized state. */
if (m->previous_objective == MANAGER_SOFT_REBOOT)
if (MANAGER_IS_SYSTEM(m) && m->soft_reboots_count > 0) {
/* The soft-reboot case, where we only report data for the last reboot */
firmware_usec = loader_usec = initrd_usec = kernel_usec = 0;
- total_usec = userspace_usec = usec_sub_unsigned(m->timestamps[MANAGER_TIMESTAMP_FINISH].monotonic, m->timestamps[MANAGER_TIMESTAMP_SHUTDOWN_START].monotonic);
+ total_usec = userspace_usec = usec_sub_unsigned(m->timestamps[MANAGER_TIMESTAMP_FINISH].monotonic,
+ m->timestamps[MANAGER_TIMESTAMP_SHUTDOWN_START].monotonic);
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_STARTUP_FINISHED_STR,
"USERSPACE_USEC="USEC_FMT, userspace_usec,
- LOG_MESSAGE("Soft-reboot finished in %s.",
- FORMAT_TIMESPAN(total_usec, USEC_PER_MSEC)));
+ LOG_MESSAGE("Soft-reboot finished in %s, counter is now at %u.",
+ FORMAT_TIMESPAN(total_usec, USEC_PER_MSEC),
+ m->soft_reboots_count));
} else if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) {
char buf[FORMAT_TIMESPAN_MAX + STRLEN(" (firmware) + ") + FORMAT_TIMESPAN_MAX + STRLEN(" (loader) + ")]
= {};
set_show_status_marker(show_status_on(mode));
}
-const char *manager_get_confirm_spawn(Manager *m) {
+const char* manager_get_confirm_spawn(Manager *m) {
static int last_errno = 0;
struct stat st;
int r;
return LOG_TARGET_KMSG;
}
-static const char *const manager_state_table[_MANAGER_STATE_MAX] = {
+static const char* const manager_state_table[_MANAGER_STATE_MAX] = {
[MANAGER_INITIALIZING] = "initializing",
[MANAGER_STARTING] = "starting",
[MANAGER_RUNNING] = "running",
DEFINE_STRING_TABLE_LOOKUP(manager_state, ManagerState);
-static const char *const manager_timestamp_table[_MANAGER_TIMESTAMP_MAX] = {
+static const char* const manager_objective_table[_MANAGER_OBJECTIVE_MAX] = {
+ [MANAGER_OK] = "ok",
+ [MANAGER_EXIT] = "exit",
+ [MANAGER_RELOAD] = "reload",
+ [MANAGER_REEXECUTE] = "reexecute",
+ [MANAGER_REBOOT] = "reboot",
+ [MANAGER_SOFT_REBOOT] = "soft-reboot",
+ [MANAGER_POWEROFF] = "poweroff",
+ [MANAGER_HALT] = "halt",
+ [MANAGER_KEXEC] = "kexec",
+ [MANAGER_SWITCH_ROOT] = "switch-root",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(manager_objective, ManagerObjective);
+
+static const char* const manager_timestamp_table[_MANAGER_TIMESTAMP_MAX] = {
[MANAGER_TIMESTAMP_FIRMWARE] = "firmware",
[MANAGER_TIMESTAMP_LOADER] = "loader",
[MANAGER_TIMESTAMP_KERNEL] = "kernel",
void manager_override_log_target(Manager *m, LogTarget target);
void manager_restore_original_log_target(Manager *m);
-const char *manager_state_to_string(ManagerState m) _const_;
+const char* manager_get_confirm_spawn(Manager *m);
+void manager_disable_confirm_spawn(void);
+
+const char* manager_state_to_string(ManagerState m) _const_;
ManagerState manager_state_from_string(const char *s) _pure_;
-const char *manager_get_confirm_spawn(Manager *m);
-void manager_disable_confirm_spawn(void);
+const char* manager_objective_to_string(ManagerObjective m) _const_;
+ManagerObjective manager_objective_from_string(const char *s) _pure_;
-const char *manager_timestamp_to_string(ManagerTimestamp m) _const_;
+const char* manager_timestamp_to_string(ManagerTimestamp m) _const_;
ManagerTimestamp manager_timestamp_from_string(const char *s) _pure_;
ManagerTimestamp manager_timestamp_initrd_mangle(ManagerTimestamp s);
* the graph over 'before' edges in the actual job execution order. We traverse over both unit
* ordering dependencies and we test with job_compare() whether it is the 'before' edge in the job
* execution ordering. */
- FOREACH_ARRAY(d, directions, ELEMENTSOF(directions)) {
+ FOREACH_ELEMENT(d, directions) {
Unit *u;
UNIT_FOREACH_DEPENDENCY(u, j->unit, *d) {
break;
case COLLECT_INACTIVE_OR_FAILED:
- if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED))
+ if (!UNIT_IS_INACTIVE_OR_FAILED(state))
return false;
break;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Must provide all PCR values when using TPM2 device key.");
} else {
- r = tpm2_context_new(device, &tpm2_context);
+ r = tpm2_context_new_or_warn(device, &tpm2_context);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) {
r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values);
}
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
- r = tpm2_context_new(device, &tpm2_context);
+ r = tpm2_context_new_or_warn(device, &tpm2_context);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
#if HAVE_TPM2
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
- r = tpm2_context_new(arg_tpm2_device, &c);
+ r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
_cleanup_strv_free_ char **l = NULL;
if (strv_isempty(arg_tpm2_measure_banks)) {
[UNIFIED_SECTION_OSREL] = ".osrel",
[UNIFIED_SECTION_CMDLINE] = ".cmdline",
[UNIFIED_SECTION_INITRD] = ".initrd",
+ [UNIFIED_SECTION_UCODE] = ".ucode",
[UNIFIED_SECTION_SPLASH] = ".splash",
[UNIFIED_SECTION_DTB] = ".dtb",
[UNIFIED_SECTION_UNAME] = ".uname",
UNIFIED_SECTION_OSREL,
UNIFIED_SECTION_CMDLINE,
UNIFIED_SECTION_INITRD,
+ UNIFIED_SECTION_UCODE,
UNIFIED_SECTION_SPLASH,
UNIFIED_SECTION_DTB,
UNIFIED_SECTION_UNAME,
};
int r;
- FOREACH_ARRAY(t, table, ELEMENTSOF(table)) {
+ FOREACH_ELEMENT(t, table) {
_cleanup_free_ char *b = NULL;
size_t sz = 0;
/* Pref64 option type (RFC8781, section 4) */
#define RADV_OPT_PREF64 38
-enum RAdvState {
+typedef enum RAdvState {
RADV_STATE_IDLE = 0,
RADV_STATE_ADVERTISING = 1,
-};
-typedef enum RAdvState RAdvState;
+} RAdvState;
struct sd_radv_opt_dns {
uint8_t type;
int ifindex;
char *ifname;
+ struct in6_addr ipv6ll;
sd_event *event;
int event_priority;
struct ether_addr mac_addr;
uint8_t hop_limit;
uint8_t flags;
+ uint8_t preference;
uint32_t mtu;
usec_t retransmit_usec;
usec_t lifetime_usec; /* timespan */
lifetime_usec <= RADV_MAX_ROUTER_LIFETIME_USEC);
}
-static int radv_send_router(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_usec) {
+static int radv_send_router_on_stop(sd_radv *ra) {
+ static const struct nd_router_advert adv = {
+ .nd_ra_type = ND_ROUTER_ADVERT,
+ };
+
+ _cleanup_set_free_ Set *options = NULL;
+ usec_t time_now;
+ int r;
+
+ assert(ra);
+
+ r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ if (!ether_addr_is_null(&ra->mac_addr)) {
+ r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &ra->mac_addr);
+ if (r < 0)
+ return r;
+ }
+
+ return ndisc_send(ra->fd, &IN6_ADDR_ALL_NODES_MULTICAST, &adv.nd_ra_hdr, options, time_now);
+}
+
+static int radv_send_router(sd_radv *ra, const struct in6_addr *dst) {
assert(ra);
- assert(router_lifetime_is_valid(lifetime_usec));
struct sockaddr_in6 dst_addr = {
.sin6_family = AF_INET6,
};
struct nd_router_advert adv = {
.nd_ra_type = ND_ROUTER_ADVERT,
- .nd_ra_router_lifetime = usec_to_be16_sec(lifetime_usec),
+ .nd_ra_router_lifetime = usec_to_be16_sec(ra->lifetime_usec),
.nd_ra_retransmit = usec_to_be32_msec(ra->retransmit_usec),
};
struct {
/* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime
* simultaneously in the structured initializer in the above. */
adv.nd_ra_curhoplimit = ra->hop_limit;
- adv.nd_ra_flags_reserved = ra->flags;
+ /* RFC 4191, Section 2.2,
+ * "...If the Router Lifetime is zero, the preference value MUST be set to (00) by the sender..." */
+ adv.nd_ra_flags_reserved = ra->flags | (ra->lifetime_usec > 0 ? (ra->preference << 3) : 0);
iov[msg.msg_iovlen++] = IOVEC_MAKE(&adv, sizeof(adv));
/* MAC address is optional, either because the link does not use L2 addresses or load sharing is
if (r < 0)
return r;
- struct in6_addr src = {};
+ struct in6_addr src;
r = sd_ndisc_router_solicit_get_sender_address(rs, &src);
- if (r < 0 && r != -ENODATA) /* null address is allowed */
+ if (r == -ENODATA) /* null address is allowed */
+ return sd_radv_send(ra); /* When an unsolicited RA, we need to also update timer. */
+ if (r < 0)
return log_radv_errno(ra, r, "Failed to get sender address of RS, ignoring: %m");
+ if (in6_addr_equal(&src, &ra->ipv6ll))
+ /* This should be definitely caused by a misconfiguration. If we send RA to ourself, the
+ * kernel complains about that. Let's ignore the packet. */
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EADDRINUSE), "Received RS from the same interface, ignoring.");
- r = radv_send_router(ra, &src, ra->lifetime_usec);
+ r = radv_send_router(ra, &src);
if (r < 0)
return log_radv_errno(ra, r, "Unable to send solicited Router Advertisement to %s, ignoring: %m", IN6_ADDR_TO_STRING(&src));
}
static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
- usec_t min_timeout, max_timeout, time_now, timeout;
sd_radv *ra = ASSERT_PTR(userdata);
+
+ if (sd_radv_send(ra) < 0)
+ (void) sd_radv_stop(ra);
+
+ return 0;
+}
+
+int sd_radv_send(sd_radv *ra) {
+ usec_t min_timeout, max_timeout, time_now, timeout;
int r;
- assert(s);
- assert(ra->event);
+ assert_return(ra, -EINVAL);
+ assert_return(ra->event, -EINVAL);
+ assert_return(sd_radv_is_running(ra), -EINVAL);
assert(router_lifetime_is_valid(ra->lifetime_usec));
r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now);
if (r < 0)
- goto fail;
+ return r;
- r = radv_send_router(ra, NULL, ra->lifetime_usec);
+ r = radv_send_router(ra, NULL);
if (r < 0)
- log_radv_errno(ra, r, "Unable to send Router Advertisement, ignoring: %m");
+ return log_radv_errno(ra, r, "Unable to send Router Advertisement: %m");
+
+ ra->ra_sent++;
/* RFC 4861, Section 6.2.4, sending initial Router Advertisements */
- if (ra->ra_sent < RADV_MAX_INITIAL_RTR_ADVERTISEMENTS)
+ if (ra->ra_sent <= RADV_MAX_INITIAL_RTR_ADVERTISEMENTS)
max_timeout = RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC;
else
max_timeout = RADV_DEFAULT_MAX_TIMEOUT_USEC;
assert(min_timeout <= max_timeout * 3 / 4);
timeout = min_timeout + random_u64_range(max_timeout - min_timeout);
- log_radv(ra, "Next Router Advertisement in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
-
- r = event_reset_time(ra->event, &ra->timeout_event_source,
- CLOCK_BOOTTIME,
- usec_add(time_now, timeout), MSEC_PER_SEC,
- radv_timeout, ra,
- ra->event_priority, "radv-timeout", true);
- if (r < 0)
- goto fail;
-
- ra->ra_sent++;
-
- return 0;
-
-fail:
- sd_radv_stop(ra);
-
- return 0;
+ log_radv(ra, "Sent unsolicited Router Advertisement. Next advertisement will be in %s.",
+ FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
+
+ return event_reset_time(
+ ra->event, &ra->timeout_event_source,
+ CLOCK_BOOTTIME,
+ usec_add(time_now, timeout), MSEC_PER_SEC,
+ radv_timeout, ra,
+ ra->event_priority, "radv-timeout", true);
}
int sd_radv_stop(sd_radv *ra) {
int r;
- if (!ra)
- return 0;
-
- if (ra->state == RADV_STATE_IDLE)
- return 0;
+ if (!sd_radv_is_running(ra))
+ return 0; /* Already stopped. */
log_radv(ra, "Stopping IPv6 Router Advertisement daemon");
/* RFC 4861, Section 6.2.5:
* the router SHOULD transmit one or more (but not more than MAX_FINAL_RTR_ADVERTISEMENTS) final
* multicast Router Advertisements on the interface with a Router Lifetime field of zero. */
- r = radv_send_router(ra, NULL, 0);
+ r = radv_send_router_on_stop(ra);
if (r < 0)
log_radv_errno(ra, r, "Unable to send last Router Advertisement with router lifetime set to zero, ignoring: %m");
assert_return(ra->event, -EINVAL);
assert_return(ra->ifindex > 0, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return 0;
+ if (sd_radv_is_running(ra))
+ return 0; /* Already started. */
r = radv_setup_recv_event(ra);
if (r < 0)
int sd_radv_set_ifindex(sd_radv *ra, int ifindex) {
assert_return(ra, -EINVAL);
+ assert_return(!sd_radv_is_running(ra), -EBUSY);
assert_return(ifindex > 0, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
ra->ifindex = ifindex;
-
return 0;
}
return 0;
}
-int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) {
+int sd_radv_set_link_local_address(sd_radv *ra, const struct in6_addr *addr) {
assert_return(ra, -EINVAL);
+ assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL);
+
+ if (addr)
+ ra->ipv6ll = *addr;
+ else
+ zero(ra->ipv6ll);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
+ return 0;
+}
+
+int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) {
+ assert_return(ra, -EINVAL);
if (mac_addr)
ra->mac_addr = *mac_addr;
int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
ra->hop_limit = hop_limit;
-
return 0;
}
int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
- if (usec > RADV_MAX_RETRANSMIT_USEC)
- return -EINVAL;
-
ra->retransmit_usec = usec;
return 0;
}
int sd_radv_set_router_lifetime(sd_radv *ra, uint64_t usec) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
if (!router_lifetime_is_valid(usec))
return -EINVAL;
- /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set
- * to (00) by the sender..." */
- if (usec == 0 &&
- (ra->flags & (0x3 << 3)) != (SD_NDISC_PREFERENCE_MEDIUM << 3))
- return -EINVAL;
-
ra->lifetime_usec = usec;
return 0;
}
-int sd_radv_set_managed_information(sd_radv *ra, int managed) {
+int sd_radv_set_managed_information(sd_radv *ra, int b) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
- SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, managed);
-
+ SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, b);
return 0;
}
-int sd_radv_set_other_information(sd_radv *ra, int other) {
+int sd_radv_set_other_information(sd_radv *ra, int b) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
- SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, other);
-
+ SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, b);
return 0;
}
-int sd_radv_set_preference(sd_radv *ra, unsigned preference) {
+int sd_radv_set_preference(sd_radv *ra, uint8_t preference) {
assert_return(ra, -EINVAL);
assert_return(IN_SET(preference,
SD_NDISC_PREFERENCE_LOW,
SD_NDISC_PREFERENCE_MEDIUM,
SD_NDISC_PREFERENCE_HIGH), -EINVAL);
- /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set
- * to (00) by the sender..." */
- if (ra->lifetime_usec == 0 && preference != SD_NDISC_PREFERENCE_MEDIUM)
- return -EINVAL;
-
- ra->flags = (ra->flags & ~(0x3 << 3)) | (preference << 3);
-
+ ra->preference = preference;
return 0;
}
int sd_radv_set_home_agent_information(sd_radv *ra, int home_agent) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
SET_FLAG(ra->flags, ND_RA_FLAG_HOME_AGENT, home_agent);
-
return 0;
}
int sd_radv_set_home_agent_preference(sd_radv *ra, uint16_t preference) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
ra->home_agent.nd_opt_home_agent_info_preference = htobe16(preference);
-
return 0;
}
int sd_radv_set_home_agent_lifetime(sd_radv *ra, uint64_t lifetime_usec) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
if (lifetime_usec > RADV_HOME_AGENT_MAX_LIFETIME_USEC)
return -EINVAL;
log_radv(ra, "Added prefix %s", addr_p);
}
- if (ra->state == RADV_STATE_IDLE)
+ if (!sd_radv_is_running(ra))
return 0;
if (ra->ra_sent == 0)
return 0;
/* If RAs have already been sent, send an RA immediately to announce the newly-added prefix */
- r = radv_send_router(ra, NULL, ra->lifetime_usec);
+ r = radv_send_router(ra, NULL);
if (r < 0)
log_radv_errno(ra, r, "Unable to send Router Advertisement for added prefix %s, ignoring: %m", addr_p);
else
log_radv(ra, "Added route prefix %s", strna(addr_p));
}
- if (ra->state == RADV_STATE_IDLE)
+ if (!sd_radv_is_running(ra))
return 0;
if (ra->ra_sent == 0)
return 0;
/* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
- r = radv_send_router(ra, NULL, ra->lifetime_usec);
+ r = radv_send_router(ra, NULL);
if (r < 0)
log_radv_errno(ra, r, "Unable to send Router Advertisement for added route prefix %s, ignoring: %m",
strna(addr_p));
log_radv(ra, "Added PREF64 prefix %s", strna(addr_p));
}
- if (ra->state == RADV_STATE_IDLE)
+ if (!sd_radv_is_running(ra))
return 0;
if (ra->ra_sent == 0)
return 0;
/* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
- r = radv_send_router(ra, NULL, ra->lifetime_usec);
+ r = radv_send_router(ra, NULL);
if (r < 0)
log_radv_errno(ra, r, "Unable to send Router Advertisement for added PREF64 prefix %s, ignoring: %m",
strna(addr_p));
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
ASSERT_RETURN_EXPECTED_SE(sd_radv_set_preference(ra, ~0) < 0);
- assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
- assert_se(sd_radv_set_router_lifetime(ra, 300 * USEC_PER_SEC) >= 0);
- assert_se(sd_radv_set_router_lifetime(ra, 0) < 0);
- assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0);
- assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0);
-
ASSERT_RETURN_EXPECTED_SE(sd_radv_set_managed_information(NULL, true) < 0);
assert_se(sd_radv_set_managed_information(ra, true) >= 0);
assert_se(sd_radv_set_managed_information(ra, false) >= 0);
ASSERT_RETURN_EXPECTED_SE(sd_radv_set_retransmit(NULL, 10 * USEC_PER_MSEC) < 0);
assert_se(sd_radv_set_retransmit(ra, 10 * USEC_PER_MSEC) >= 0);
assert_se(sd_radv_set_retransmit(ra, 0) >= 0);
- assert_se(sd_radv_set_retransmit(ra, usec_add(UINT32_MAX * USEC_PER_MSEC, USEC_PER_MSEC)) < 0);
+ assert_se(sd_radv_set_retransmit(ra, USEC_INFINITY) >= 0);
ASSERT_RETURN_EXPECTED_SE(sd_radv_set_rdnss(NULL, 0, NULL, 0) < 0);
assert_se(sd_radv_set_rdnss(ra, 0, NULL, 0) >= 0);
/* verify only up to known options, rest is not yet implemented */
for (size_t i = 0, m = MIN(len, sizeof(advertisement)); i < m; i++) {
if (test_stopped)
+ /* on stop, many header fields are zero */
switch (i) {
- case 6 ... 7: /* router lifetime must be zero on stop. */
+ case 4: /* hop limit */
+ case 5: /* flags */
+ case 6 ... 7: /* router lifetime */
+ case 8 ... 11: /* reachable time */
+ case 12 ... 15: /* retrans timer */
assert_se(buf[i] == 0);
continue;
}
global:
sd_bus_creds_get_pidfd_dup;
sd_bus_creds_new_from_pidfd;
+ sd_id128_get_invocation_app_specific;
sd_journal_stream_fd_with_namespace;
+ sd_event_source_get_inotify_path;
} LIBSYSTEMD_255;
* iteration. */
int fd;
+ /* The path that the fd points to. The field is optional. */
+ char *path;
+
/* The inotify "watch descriptor" */
int wd;
assert_se(hashmap_remove(d->inotify_data->inodes, d) == d);
}
+ free(d->path);
free(d);
}
wd = inotify_add_watch_fd(d->inotify_data->fd, d->fd, combined_mask);
if (wd < 0)
- return -errno;
+ return wd;
if (d->wd < 0) {
r = hashmap_put(d->inotify_data->wd, INT_TO_PTR(wd), d);
}
LIST_PREPEND(to_close, e->inode_data_to_close_list, inode_data);
+
+ _cleanup_free_ char *path = NULL;
+ r = fd_get_path(inode_data->fd, &path);
+ if (r < 0 && r != -ENOSYS) { /* The path is optional, hence ignore -ENOSYS. */
+ event_gc_inode_data(e, inode_data);
+ return r;
+ }
+
+ free_and_replace(inode_data->path, path);
}
/* Link our event source to the inode data object */
}
_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
- int r;
+ int saved_fd, r;
assert_return(s, -EINVAL);
assert_return(fd >= 0, -EBADF);
if (s->io.fd == fd)
return 0;
- if (event_source_is_offline(s)) {
- s->io.fd = fd;
- s->io.registered = false;
- } else {
- int saved_fd;
+ saved_fd = s->io.fd;
+ s->io.fd = fd;
- saved_fd = s->io.fd;
- assert(s->io.registered);
+ assert(event_source_is_offline(s) == !s->io.registered);
- s->io.fd = fd;
+ if (s->io.registered) {
s->io.registered = false;
r = source_io_register(s, s->enabled, s->io.events);
(void) epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL);
}
+ if (s->io.owned)
+ safe_close(saved_fd);
+
return 0;
}
}
LIST_PREPEND(to_close, s->event->inode_data_to_close_list, new_inode_data);
+
+ _cleanup_free_ char *path = NULL;
+ r = fd_get_path(new_inode_data->fd, &path);
+ if (r < 0 && r != -ENOSYS)
+ goto fail;
+
+ free_and_replace(new_inode_data->path, path);
}
/* Move the event source to the new inode data structure */
return 0;
}
-_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) {
+_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret) {
+ assert_return(s, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(s->type == SOURCE_INOTIFY, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
+
+ *ret = s->inotify.mask;
+ return 0;
+}
+
+_public_ int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret) {
assert_return(s, -EINVAL);
- assert_return(mask, -EINVAL);
+ assert_return(ret, -EINVAL);
assert_return(s->type == SOURCE_INOTIFY, -EDOM);
assert_return(!event_origin_changed(s->event), -ECHILD);
- *mask = s->inotify.mask;
+ if (!s->inotify.inode_data)
+ return -ESTALE; /* already disconnected. */
+
+ if (!s->inotify.inode_data->path)
+ return -ENOSYS; /* /proc was not mounted? */
+
+ *ret = s->inotify.inode_data->path;
return 0;
}
unsigned create_called[CREATE_EVENTS_MAX];
unsigned create_overflow;
unsigned n_create_events;
+ const char *path;
};
static void maybe_exit(sd_event_source *s, struct inotify_context *c) {
}
static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) {
- struct inotify_context *c = userdata;
- const char *description;
+ struct inotify_context *c = ASSERT_PTR(userdata);
+ const char *path, *description;
unsigned bit, n;
+ assert_se(sd_event_source_get_inotify_path(s, &path) >= 0);
+
assert_se(sd_event_source_get_description(s, &description) >= 0);
assert_se(safe_atou(description, &n) >= 0);
bit = 1U << n;
if (ev->mask & IN_Q_OVERFLOW) {
- log_info("inotify-handler <%s>: overflow", description);
+ log_info("inotify-handler for %s <%s>: overflow", path, description);
c->create_overflow |= bit;
} else if (ev->mask & IN_CREATE) {
+ assert_se(path_equal_or_inode_same(path, c->path, 0));
if (streq(ev->name, "sub"))
- log_debug("inotify-handler <%s>: create on %s", description, ev->name);
+ log_debug("inotify-handler for %s <%s>: create on %s", path, description, ev->name);
else {
unsigned i;
c->create_called[i] |= bit;
}
} else if (ev->mask & IN_DELETE) {
- log_info("inotify-handler <%s>: delete of %s", description, ev->name);
+ log_info("inotify-handler for %s <%s>: delete of %s", path, description, ev->name);
assert_se(streq(ev->name, "sub"));
} else
assert_not_reached();
}
static int delete_self_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) {
- struct inotify_context *c = userdata;
+ struct inotify_context *c = ASSERT_PTR(userdata);
+ const char *path;
+
+ assert_se(sd_event_source_get_inotify_path(s, &path) >= 0);
if (ev->mask & IN_Q_OVERFLOW) {
- log_info("delete-self-handler: overflow");
+ log_info("delete-self-handler for %s: overflow", path);
c->delete_self_handler_called = true;
} else if (ev->mask & IN_DELETE_SELF) {
- log_info("delete-self-handler: delete-self");
+ log_info("delete-self-handler for %s: delete-self", path);
c->delete_self_handler_called = true;
} else if (ev->mask & IN_IGNORED) {
- log_info("delete-self-handler: ignore");
+ log_info("delete-self-handler for %s: ignore", path);
} else
assert_not_reached();
.n_create_events = n_create_events,
};
sd_event *e = NULL;
- const char *q;
+ const char *q, *pp;
unsigned i;
log_info("/* %s(%u) */", __func__, n_create_events);
assert_se(sd_event_default(&e) >= 0);
assert_se(mkdtemp_malloc("/tmp/test-inotify-XXXXXX", &p) >= 0);
+ context.path = p;
assert_se(sd_event_add_inotify(e, &a, p, IN_CREATE|IN_ONLYDIR, inotify_handler, &context) >= 0);
assert_se(sd_event_add_inotify(e, &b, p, IN_CREATE|IN_DELETE|IN_DONT_FOLLOW, inotify_handler, &context) >= 0);
assert_se(sd_event_source_set_description(b, "1") >= 0);
assert_se(sd_event_source_set_description(c, "2") >= 0);
+ assert_se(sd_event_source_get_inotify_path(a, &pp) >= 0);
+ assert_se(path_equal_or_inode_same(pp, p, 0));
+ assert_se(sd_event_source_get_inotify_path(b, &pp) >= 0);
+ assert_se(path_equal_or_inode_same(pp, p, 0));
+ assert_se(sd_event_source_get_inotify_path(b, &pp) >= 0);
+ assert_se(path_equal_or_inode_same(pp, p, 0));
+
q = strjoina(p, "/sub");
assert_se(touch(q) >= 0);
assert_se(sd_event_add_inotify(e, &d, q, IN_DELETE_SELF, delete_self_handler, &context) >= 0);
assert_se(r >= 0);
}
+TEST(sd_event_source_set_io_fd) {
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_close_pair_ int pfd_a[2] = EBADF_PAIR, pfd_b[2] = EBADF_PAIR;
+
+ assert_se(sd_event_default(&e) >= 0);
+
+ assert_se(pipe2(pfd_a, O_CLOEXEC) >= 0);
+ assert_se(pipe2(pfd_b, O_CLOEXEC) >= 0);
+
+ assert_se(sd_event_add_io(e, &s, pfd_a[0], EPOLLIN, NULL, INT_TO_PTR(-ENOANO)) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+ TAKE_FD(pfd_a[0]);
+
+ assert_se(sd_event_source_set_io_fd(s, pfd_b[0]) >= 0);
+ TAKE_FD(pfd_b[0]);
+}
+
static int hup_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
unsigned *c = userdata;
return sd_id128_get_app_specific(id, app_id, ret);
}
+
+_public_ int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+ sd_id128_t id;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ r = sd_id128_get_invocation(&id);
+ if (r < 0)
+ return r;
+
+ return sd_id128_get_app_specific(id, app_id, ret);
+}
int r;
if (le64toh(o->data.entry_offset) == 0)
- warning(offset, "Unused data (entry_offset==0)");
+ debug(offset, "Unused data (entry_offset==0)");
if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) {
error(offset, "Bad n_entries: %"PRIu64, le64toh(o->data.n_entries));
m->wd = inotify_add_watch_fd(j->inotify_fd, fd, mask);
if (m->wd < 0) {
- log_debug_errno(errno, "Failed to watch journal directory '%s', ignoring: %m", m->path);
+ log_debug_errno(m->wd, "Failed to watch journal directory '%s', ignoring: %m", m->path);
return;
}
}
static void append_number(JournalFile *f, int n, const sd_id128_t *boot_id, uint64_t *seqnum, uint64_t *ret_offset) {
- _cleanup_free_ char *p = NULL, *q = NULL;
+ _cleanup_free_ char *p = NULL, *q = NULL, *s = NULL;
dual_timestamp ts;
- struct iovec iovec[2];
+ struct iovec iovec[3];
size_t n_iov = 0;
dual_timestamp_now(&ts);
assert_se(asprintf(&p, "NUMBER=%d", n) >= 0);
iovec[n_iov++] = IOVEC_MAKE_STRING(p);
+ assert_se(s = strjoin("LESS_THAN_FIVE=%d", yes_no(n < 5)));
+ iovec[n_iov++] = IOVEC_MAKE_STRING(s);
+
if (boot_id) {
assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id)));
iovec[n_iov++] = IOVEC_MAKE_STRING(q);
(void) chattr_path(path, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
}
+static void test_cursor(sd_journal *j) {
+ _cleanup_strv_free_ char **cursors = NULL;
+ int r;
+
+ assert_se(sd_journal_seek_head(j) >= 0);
+
+ for (;;) {
+ r = sd_journal_next(j);
+ assert_se(r >= 0);
+ if (r == 0)
+ break;
+
+ _cleanup_free_ char *cursor = NULL;
+ assert_se(sd_journal_get_cursor(j, &cursor) >= 0);
+ assert_se(sd_journal_test_cursor(j, cursor) > 0);
+ assert_se(strv_consume(&cursors, TAKE_PTR(cursor)) >= 0);
+ }
+
+ STRV_FOREACH(c, cursors) {
+ assert_se(sd_journal_seek_cursor(j, *c) >= 0);
+ assert_se(sd_journal_next(j) >= 0);
+ assert_se(sd_journal_test_cursor(j, *c) > 0);
+ }
+
+ assert_se(sd_journal_seek_head(j) >= 0);
+ STRV_FOREACH(c, cursors) {
+ assert_se(sd_journal_next(j) >= 0);
+ assert_se(sd_journal_test_cursor(j, *c) > 0);
+ }
+}
+
static void test_skip_one(void (*setup)(void)) {
char t[] = "/var/tmp/journal-skip-XXXXXX";
sd_journal *j;
test_check_numbers_up(j, 9);
sd_journal_close(j);
+ /* For issue #31516. */
+ assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
+ test_cursor(j);
+ sd_journal_flush_matches(j);
+ assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0);
+ test_cursor(j);
+ sd_journal_flush_matches(j);
+ assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=no", SIZE_MAX) >= 0);
+ test_cursor(j);
+ sd_journal_flush_matches(j);
+ assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=hoge", SIZE_MAX) >= 0);
+ test_cursor(j);
+ sd_journal_flush_matches(j);
+ assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=3", SIZE_MAX) >= 0);
+ test_cursor(j);
+ sd_journal_flush_matches(j);
+ assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=3", SIZE_MAX) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=4", SIZE_MAX) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=5", SIZE_MAX) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=6", SIZE_MAX) >= 0);
+ test_cursor(j);
+
test_done(t);
}
assert(ret);
- FOREACH_ARRAY(i, sleep_actions, ELEMENTSOF(sleep_actions))
+ FOREACH_ELEMENT(i, sleep_actions)
if (FLAGS_SET(mask, 1U << *i)) {
r = strv_extend(&actions, handle_action_to_string(*i));
if (r < 0)
HandleAction handle_action_sleep_select(Manager *m) {
assert(m);
- FOREACH_ARRAY(i, sleep_actions, ELEMENTSOF(sleep_actions)) {
+ FOREACH_ELEMENT(i, sleep_actions) {
HandleActionSleepMask action_mask = 1U << *i;
const HandleActionData *a;
_cleanup_free_ char *load_state = NULL;
{ "IOWeight", u->user_record->io_weight },
};
- FOREACH_ARRAY(st, settings, ELEMENTSOF(settings)) {
+ FOREACH_ELEMENT(st, settings) {
if (st->value == UINT64_MAX)
continue;
'networkd-dhcp4.c',
'networkd-dhcp6-bus.c',
'networkd-dhcp6.c',
+ 'networkd-dns.c',
'networkd-ipv4acd.c',
'networkd-ipv4ll.c',
'networkd-ipv6-proxy-ndp.c',
'networkd-network-bus.c',
'networkd-network.c',
'networkd-nexthop.c',
+ 'networkd-ntp.c',
'networkd-queue.c',
'networkd-radv.c',
'networkd-route.c',
return 0;
}
-int config_parse_dhcp_use_dns(
- 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 = userdata;
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
- assert(rvalue);
- assert(data);
-
- r = parse_boolean(rvalue);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to parse UseDNS=%s, ignoring assignment: %m", rvalue);
- return 0;
- }
-
- switch (ltype) {
- case AF_INET:
- network->dhcp_use_dns = r;
- network->dhcp_use_dns_set = true;
- break;
- case AF_INET6:
- network->dhcp6_use_dns = r;
- network->dhcp6_use_dns_set = true;
- break;
- case AF_UNSPEC:
- /* For backward compatibility. */
- if (!network->dhcp_use_dns_set)
- network->dhcp_use_dns = r;
- if (!network->dhcp6_use_dns_set)
- network->dhcp6_use_dns = r;
- break;
- default:
- assert_not_reached();
- }
-
- return 0;
-}
-
-int config_parse_dhcp_use_domains(
- 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 = userdata;
- DHCPUseDomains d;
-
- assert(filename);
- assert(lvalue);
- assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
- assert(rvalue);
- assert(data);
-
- d = dhcp_use_domains_from_string(rvalue);
- if (d < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, d,
- "Failed to parse %s=%s, ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
-
- switch (ltype) {
- case AF_INET:
- network->dhcp_use_domains = d;
- network->dhcp_use_domains_set = true;
- break;
- case AF_INET6:
- network->dhcp6_use_domains = d;
- network->dhcp6_use_domains_set = true;
- break;
- case AF_UNSPEC:
- /* For backward compatibility. */
- if (!network->dhcp_use_domains_set)
- network->dhcp_use_domains = d;
- if (!network->dhcp6_use_domains_set)
- network->dhcp6_use_domains = d;
- break;
- default:
- assert_not_reached();
- }
-
- return 0;
-}
-
-DEFINE_CONFIG_PARSE_ENUM(config_parse_default_dhcp_use_domains, dhcp_use_domains, DHCPUseDomains, "Failed to parse UseDomains=")
-
-int config_parse_dhcp_use_ntp(
- 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 = userdata;
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
- assert(rvalue);
- assert(data);
-
- r = parse_boolean(rvalue);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to parse UseNTP=%s, ignoring assignment: %m", rvalue);
- return 0;
- }
-
- switch (ltype) {
- case AF_INET:
- network->dhcp_use_ntp = r;
- network->dhcp_use_ntp_set = true;
- break;
- case AF_INET6:
- network->dhcp6_use_ntp = r;
- network->dhcp6_use_ntp_set = true;
- break;
- case AF_UNSPEC:
- /* For backward compatibility. */
- if (!network->dhcp_use_ntp_set)
- network->dhcp_use_ntp = r;
- if (!network->dhcp6_use_ntp_set)
- network->dhcp6_use_ntp = r;
- break;
- default:
- assert_not_reached();
- }
-
- return 0;
-}
int config_parse_dhcp_or_ra_route_table(
const char *unit,
}
}
-static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = {
- [DHCP_USE_DOMAINS_NO] = "no",
- [DHCP_USE_DOMAINS_ROUTE] = "route",
- [DHCP_USE_DOMAINS_YES] = "yes",
-};
-
-DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES);
-
static const char * const dhcp_option_data_type_table[_DHCP_OPTION_DATA_MAX] = {
[DHCP_OPTION_DATA_UINT8] = "uint8",
[DHCP_OPTION_DATA_UINT16] = "uint16",
typedef struct Manager Manager;
typedef struct Network Network;
-typedef enum DHCPUseDomains {
- DHCP_USE_DOMAINS_NO,
- DHCP_USE_DOMAINS_YES,
- DHCP_USE_DOMAINS_ROUTE,
- _DHCP_USE_DOMAINS_MAX,
- _DHCP_USE_DOMAINS_INVALID = -EINVAL,
-} DHCPUseDomains;
-
typedef enum DHCPOptionDataType {
DHCP_OPTION_DATA_UINT8,
DHCP_OPTION_DATA_UINT16,
int link_get_captive_portal(Link *link, const char **ret);
-const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_;
-DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_;
-
const char *dhcp_option_data_type_to_string(DHCPOptionDataType d) _const_;
DHCPOptionDataType dhcp_option_data_type_from_string(const char *d) _pure_;
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_route_metric);
CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_route_metric);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname);
-CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns);
-CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_domains);
-CONFIG_PARSER_PROTOTYPE(config_parse_default_dhcp_use_domains);
-CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_ntp);
CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class);
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-network.h"
+#include "networkd-ntp.h"
#include "networkd-queue.h"
#include "networkd-route-util.h"
#include "parse-util.h"
addresses[n_addresses++] = ia;
}
- use_dhcp_lease_data = link->network->dhcp_use_dns;
+ use_dhcp_lease_data = link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4);
break;
case SD_DHCP_LEASE_NTP: {
addresses[n_addresses++] = ia.in;
}
- use_dhcp_lease_data = link->network->dhcp_use_ntp;
+ use_dhcp_lease_data = link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4);
break;
}
#include "networkd-manager.h"
#include "networkd-network.h"
#include "networkd-nexthop.h"
+#include "networkd-ntp.h"
#include "networkd-queue.h"
#include "networkd-route.h"
#include "networkd-setlink.h"
assert(link->dhcp_lease);
assert(link->network);
- if (!link->network->dhcp_use_dns ||
+ if (!link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4) ||
!link->network->dhcp_routes_to_dns)
return 0;
assert(link->dhcp_lease);
assert(link->network);
- if (!link->network->dhcp_use_ntp ||
+ if (!link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4) ||
!link->network->dhcp_routes_to_ntp)
return 0;
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for classless static route: %m");
}
- if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
+ if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) > 0) {
r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_DOMAIN_SEARCH);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for domain search list: %m");
}
- if (link->network->dhcp_use_ntp) {
+ if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NTP_SERVER);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for NTP server: %m");
#include "networkd-dhcp6.h"
#include "networkd-link.h"
#include "networkd-manager.h"
+#include "networkd-ntp.h"
#include "networkd-queue.h"
#include "networkd-route.h"
#include "networkd-state-file.h"
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set MUD URL: %m");
}
- if (link->network->dhcp6_use_dns) {
+ if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request DNS servers: %m");
}
- if (link->network->dhcp6_use_domains > 0) {
+ if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) > 0) {
r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request domains: %m");
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request captive portal: %m");
}
- if (link->network->dhcp6_use_ntp) {
+ if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request NTP servers: %m");
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "dns-domain.h"
+#include "hostname-util.h"
+#include "networkd-dns.h"
+#include "networkd-manager.h"
+#include "networkd-network.h"
+#include "parse-util.h"
+#include "string-table.h"
+
+UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto) {
+ UseDomains n, c, m;
+
+ assert(link);
+ assert(link->manager);
+
+ if (!link->network)
+ return USE_DOMAINS_NO;
+
+ switch (proto) {
+ case NETWORK_CONFIG_SOURCE_DHCP4:
+ n = link->network->dhcp_use_domains;
+ c = link->network->compat_dhcp_use_domains;
+ m = link->manager->dhcp_use_domains;
+ break;
+ case NETWORK_CONFIG_SOURCE_DHCP6:
+ n = link->network->dhcp6_use_domains;
+ c = link->network->compat_dhcp_use_domains;
+ m = link->manager->dhcp6_use_domains;
+ break;
+ case NETWORK_CONFIG_SOURCE_NDISC:
+ n = link->network->ndisc_use_domains;
+ c = _USE_DOMAINS_INVALID;
+ m = link->manager->ndisc_use_domains;
+ break;
+ default:
+ assert_not_reached();
+ }
+
+ /* If per-network and per-protocol setting is specified, use it. */
+ if (n >= 0)
+ return n;
+
+ /* If compat setting is specified, use it. */
+ if (c >= 0)
+ return c;
+
+ /* If per-network but protocol-independent setting is specified, use it. */
+ if (link->network->use_domains >= 0)
+ return link->network->use_domains;
+
+ /* If global per-protocol setting is specified, use it. */
+ if (m >= 0)
+ return m;
+
+ /* If none of them are specified, use the global protocol-independent value. */
+ return link->manager->use_domains;
+}
+
+bool link_get_use_dns(Link *link, NetworkConfigSource proto) {
+ int n, c;
+
+ assert(link);
+
+ if (!link->network)
+ return false;
+
+ switch (proto) {
+ case NETWORK_CONFIG_SOURCE_DHCP4:
+ n = link->network->dhcp_use_dns;
+ c = link->network->compat_dhcp_use_dns;
+ break;
+ case NETWORK_CONFIG_SOURCE_DHCP6:
+ n = link->network->dhcp6_use_dns;
+ c = link->network->compat_dhcp_use_dns;
+ break;
+ case NETWORK_CONFIG_SOURCE_NDISC:
+ n = link->network->ndisc_use_dns;
+ c = -1;
+ break;
+ default:
+ assert_not_reached();
+ }
+
+ /* If per-network and per-protocol setting is specified, use it. */
+ if (n >= 0)
+ return n;
+
+ /* If compat setting is specified, use it. */
+ if (c >= 0)
+ return c;
+
+ /* Otherwise, defaults to yes. */
+ return true;
+}
+
+int config_parse_domains(
+ 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 *n = ASSERT_PTR(userdata);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ n->search_domains = ordered_set_free(n->search_domains);
+ n->route_domains = ordered_set_free(n->route_domains);
+ return 0;
+ }
+
+ for (const char *p = rvalue;;) {
+ _cleanup_free_ char *w = NULL, *normalized = NULL;
+ const char *domain;
+ bool is_route;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to extract search or route domain, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ is_route = w[0] == '~';
+ domain = is_route ? w + 1 : w;
+
+ if (dns_name_is_root(domain) || streq(domain, "*")) {
+ /* If the root domain appears as is, or the special token "*" is found, we'll
+ * consider this as routing domain, unconditionally. */
+ is_route = true;
+ domain = "."; /* make sure we don't allow empty strings, thus write the root
+ * domain as "." */
+ } else {
+ r = dns_name_normalize(domain, 0, &normalized);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "'%s' is not a valid domain name, ignoring.", domain);
+ continue;
+ }
+
+ domain = normalized;
+
+ if (is_localhost(domain)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "'localhost' domain may not be configured as search or route domain, ignoring assignment: %s",
+ domain);
+ continue;
+ }
+ }
+
+ OrderedSet **set = is_route ? &n->route_domains : &n->search_domains;
+ r = ordered_set_put_strdup(set, domain);
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return log_oom();
+ }
+}
+
+int config_parse_dns(
+ 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 *n = ASSERT_PTR(userdata);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ for (unsigned i = 0; i < n->n_dns; i++)
+ in_addr_full_free(n->dns[i]);
+ n->dns = mfree(n->dns);
+ n->n_dns = 0;
+ return 0;
+ }
+
+ for (const char *p = rvalue;;) {
+ _cleanup_(in_addr_full_freep) struct in_addr_full *dns = NULL;
+ _cleanup_free_ char *w = NULL;
+ struct in_addr_full **m;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = in_addr_full_new_from_string(w, &dns);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse dns server address, ignoring: %s", w);
+ continue;
+ }
+
+ if (IN_SET(dns->port, 53, 853))
+ dns->port = 0;
+
+ m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_full*));
+ if (!m)
+ return log_oom();
+
+ m[n->n_dns++] = TAKE_PTR(dns);
+ n->dns = m;
+ }
+}
+
+int config_parse_dnssec_negative_trust_anchors(
+ 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) {
+
+ Set **nta = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *nta = set_free_free(*nta);
+ return 0;
+ }
+
+ for (const char *p = rvalue;;) {
+ _cleanup_free_ char *w = NULL;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to extract negative trust anchor domain, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = dns_name_is_valid(w);
+ if (r <= 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "%s is not a valid domain name, ignoring.", w);
+ continue;
+ }
+
+ r = set_ensure_consume(nta, &dns_name_hash_ops, TAKE_PTR(w));
+ if (r < 0)
+ return log_oom();
+ }
+}
+
+static const char* const use_domains_table[_USE_DOMAINS_MAX] = {
+ [USE_DOMAINS_NO] = "no",
+ [USE_DOMAINS_ROUTE] = "route",
+ [USE_DOMAINS_YES] = "yes",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(use_domains, UseDomains, USE_DOMAINS_YES);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_use_domains, use_domains, UseDomains, "Failed to parse UseDomains=")
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "conf-parser.h"
+#include "macro.h"
+#include "networkd-util.h"
+
+typedef struct Link Link;
+
+typedef enum UseDomains {
+ USE_DOMAINS_NO,
+ USE_DOMAINS_YES,
+ USE_DOMAINS_ROUTE,
+ _USE_DOMAINS_MAX,
+ _USE_DOMAINS_INVALID = -EINVAL,
+} UseDomains;
+
+UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto);
+bool link_get_use_dns(Link *link, NetworkConfigSource proto);
+
+const char* use_domains_to_string(UseDomains p) _const_;
+UseDomains use_domains_from_string(const char *s) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_domains);
+CONFIG_PARSER_PROTOTYPE(config_parse_dns);
+CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns);
+CONFIG_PARSER_PROTOTYPE(config_parse_use_domains);
#include "conf-parser.h"
#include "networkd-conf.h"
#include "networkd-dhcp-common.h"
+#include "networkd-dns.h"
#include "networkd-manager.h"
#include "networkd-route-util.h"
%}
Network.IPv4Forwarding, config_parse_tristate, 0, offsetof(Manager, ip_forwarding[0])
Network.IPv6Forwarding, config_parse_tristate, 0, offsetof(Manager, ip_forwarding[1])
Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Manager, ipv6_privacy_extensions)
-DHCPv4.UseDomains, config_parse_default_dhcp_use_domains, 0, offsetof(Manager, dhcp_use_domains)
+Network.UseDomains, config_parse_use_domains, 0, offsetof(Manager, use_domains)
+IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Manager, ndisc_use_domains)
+DHCPv4.UseDomains, config_parse_use_domains, 0, offsetof(Manager, dhcp_use_domains)
DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp_duid)
DHCPv4.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp_duid)
-DHCPv6.UseDomains, config_parse_default_dhcp_use_domains, 0, offsetof(Manager, dhcp6_use_domains)
+DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Manager, dhcp6_use_domains)
DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp6_duid)
DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp6_duid)
DHCPServer.PersistLeases, config_parse_bool, 0, offsetof(Manager, dhcp_server_persist_leases)
#include "networkd-neighbor.h"
#include "networkd-network.h"
#include "networkd-nexthop.h"
+#include "networkd-ntp.h"
#include "networkd-route-util.h"
#include "networkd-route.h"
#include "networkd-routing-policy-rule.h"
return r;
}
- if (link->dhcp_lease && link->network->dhcp_use_dns) {
+ if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
const struct in_addr *dns;
union in_addr_union s;
int n_dns;
}
}
- if (link->dhcp6_lease && link->network->dhcp6_use_dns) {
+ if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
const struct in6_addr *dns;
union in_addr_union s;
int n_dns;
}
}
- if (link->network->ndisc_use_dns) {
+ if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) {
NDiscRDNSS *a;
SET_FOREACH(a, link->ndisc_rdnss) {
}
if (!link->ntp) {
- if (link->dhcp_lease && link->network->dhcp_use_ntp) {
+ if (link->dhcp_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
const struct in_addr *ntp;
union in_addr_union s;
int n_ntp;
}
}
- if (link->dhcp6_lease && link->network->dhcp6_use_ntp) {
+ if (link->dhcp6_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
const struct in6_addr *ntp_addr;
union in_addr_union s;
char **ntp_fqdn;
static int domains_append_json(Link *link, bool is_route, JsonVariant **v) {
_cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
OrderedSet *link_domains, *network_domains;
- DHCPUseDomains use_domains;
+ UseDomains use_domains;
union in_addr_union s;
char **domains;
const char *domain;
link_domains = is_route ? link->route_domains : link->search_domains;
network_domains = is_route ? link->network->route_domains : link->network->search_domains;
- use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES;
+ use_domains = is_route ? USE_DOMAINS_ROUTE : USE_DOMAINS_YES;
ORDERED_SET_FOREACH(domain, link_domains ?: network_domains) {
r = domain_append_json(AF_UNSPEC, domain,
if (!link_domains) {
if (link->dhcp_lease &&
- link->network->dhcp_use_domains == use_domains) {
+ link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) {
r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in);
if (r < 0)
return r;
}
if (link->dhcp6_lease &&
- link->network->dhcp6_use_domains == use_domains) {
+ link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) {
r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &s.in6);
if (r < 0)
return r;
}
}
- if (link->network->ndisc_use_domains == use_domains) {
+ if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) {
NDiscDNSSL *a;
SET_FOREACH(a, link->ndisc_dnssl) {
.manage_foreign_nexthops = true,
.ethtool_fd = -EBADF,
.persistent_storage_fd = persistent_storage_open(),
+ .dhcp_use_domains = _USE_DOMAINS_INVALID,
+ .dhcp6_use_domains = _USE_DOMAINS_INVALID,
+ .ndisc_use_domains = _USE_DOMAINS_INVALID,
.dhcp_duid.type = DUID_TYPE_EN,
.dhcp6_duid.type = DUID_TYPE_EN,
.duid_product_uuid.type = DUID_TYPE_UUID,
OrderedSet *address_pools;
Set *dhcp_pd_subnet_ids;
- DHCPUseDomains dhcp_use_domains;
- DHCPUseDomains dhcp6_use_domains;
+ UseDomains use_domains; /* default for all protocols */
+ UseDomains dhcp_use_domains;
+ UseDomains dhcp6_use_domains;
+ UseDomains ndisc_use_domains;
DUID dhcp_duid;
DUID dhcp6_duid;
if (!link_may_have_ipv6ll(link, /* check_multicast = */ true))
return false;
+ /* Honor explicitly specified value. */
if (link->network->ndisc >= 0)
return link->network->ndisc;
+ /* Disable if RADV is enabled. */
+ if (link_radv_enabled(link))
+ return false;
+
/* Accept RAs if IPv6 forwarding is disabled, and ignore RAs if IPv6 forwarding is enabled. */
int t = link_get_ip_forwarding(link, AF_INET6);
if (t >= 0)
assert(link->network);
assert(rt);
- if (!link->network->ndisc_use_dns)
+ if (!link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC))
return 0;
r = sd_ndisc_router_get_sender_address(rt, &router);
assert(link->network);
assert(rt);
- if (link->network->ndisc_use_domains == DHCP_USE_DOMAINS_NO)
+ if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) <= 0)
return 0;
r = sd_ndisc_router_get_sender_address(rt, &router);
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES);
-DEFINE_CONFIG_PARSE_ENUM(config_parse_ndisc_use_domains, dhcp_use_domains, DHCPUseDomains,
- "Failed to parse UseDomains= setting");
DEFINE_CONFIG_PARSE_ENUM(config_parse_ndisc_start_dhcp6_client, ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client,
"Failed to parse DHCPv6Client= setting");
int ndisc_reconfigure_address(Address *address, Link *link);
CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_start_dhcp6_client);
-CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_use_domains);
#include "networkd-dhcp-server.h"
#include "networkd-dhcp4.h"
#include "networkd-dhcp6.h"
+#include "networkd-dns.h"
#include "networkd-ipv4ll.h"
#include "networkd-ipv6-proxy-ndp.h"
#include "networkd-ipv6ll.h"
#include "networkd-network.h"
#include "networkd-neighbor.h"
#include "networkd-nexthop.h"
+#include "networkd-ntp.h"
#include "networkd-radv.h"
#include "networkd-route.h"
#include "networkd-routing-policy-rule.h"
Network.Address, config_parse_address, 0, 0
Network.Gateway, config_parse_gateway, 0, 0
Network.Domains, config_parse_domains, 0, 0
+Network.UseDomains, config_parse_use_domains, 0, offsetof(Network, use_domains)
Network.DNS, config_parse_dns, 0, 0
Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route)
Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr)
NextHop.Group, config_parse_nexthop_group, 0, 0
DHCPv4.RequestAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_request_address)
DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
-DHCPv4.UseDNS, config_parse_dhcp_use_dns, AF_INET, 0
+DHCPv4.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp_use_dns)
DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns)
-DHCPv4.UseNTP, config_parse_dhcp_use_ntp, AF_INET, 0
+DHCPv4.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp_use_ntp)
DHCPv4.RoutesToNTP, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_ntp)
DHCPv4.UseSIP, config_parse_bool, 0, offsetof(Network, dhcp_use_sip)
DHCPv4.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp_use_captive_portal)
DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
-DHCPv4.UseDomains, config_parse_dhcp_use_domains, AF_INET, 0
+DHCPv4.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp_use_domains)
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.RapidCommit, config_parse_tristate, 0, offsetof(Network, dhcp_use_rapid_commit)
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
-DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
+DHCPv6.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp6_use_dns)
DHCPv6.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp6_use_hostname)
-DHCPv6.UseDomains, config_parse_dhcp_use_domains, AF_INET6, 0
-DHCPv6.UseNTP, config_parse_dhcp_use_ntp, AF_INET6, 0
+DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp6_use_domains)
+DHCPv6.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp6_use_ntp)
DHCPv6.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp6_use_captive_portal)
DHCPv6.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp6_mudurl)
DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_autonomous_prefix)
IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_onlink_prefix)
IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ndisc_use_pref64)
-IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ndisc_use_dns)
-IPv6AcceptRA.UseDomains, config_parse_ndisc_use_domains, 0, offsetof(Network, ndisc_use_domains)
+IPv6AcceptRA.UseDNS, config_parse_tristate, 0, offsetof(Network, ndisc_use_dns)
+IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Network, ndisc_use_domains)
IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ndisc_use_mtu)
IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ndisc_use_hop_limit)
IPv6AcceptRA.UseReachableTime, config_parse_bool, 0, offsetof(Network, ndisc_use_reachable_time)
IPv6PrefixDelegation.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec)
DHCPv4.BlackList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip)
DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
-DHCP.UseDNS, config_parse_dhcp_use_dns, AF_UNSPEC, 0
-DHCP.UseNTP, config_parse_dhcp_use_ntp, AF_UNSPEC, 0
+DHCP.UseDNS, config_parse_tristate, 0, offsetof(Network, compat_dhcp_use_dns)
+DHCP.UseNTP, config_parse_tristate, 0, offsetof(Network, compat_dhcp_use_ntp)
DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
-DHCP.UseDomains, config_parse_dhcp_use_domains, AF_UNSPEC, 0
-DHCP.UseDomainName, config_parse_dhcp_use_domains, AF_UNSPEC, 0
+DHCP.UseDomains, config_parse_use_domains, 0, offsetof(Network, compat_dhcp_use_domains)
+DHCP.UseDomainName, config_parse_use_domains, 0, offsetof(Network, compat_dhcp_use_domains)
DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
DHCP.SendHostname, config_parse_dhcp_send_hostname, AF_UNSPEC, 0
DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
DHCP.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0
-DHCPv4.UseDomainName, config_parse_dhcp_use_domains, AF_INET, 0
+DHCPv4.UseDomainName, config_parse_use_domains, 0, offsetof(Network, dhcp_use_domains)
DHCPv4.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical)
DHCPv6.RouteMetric, config_parse_ndisc_route_metric, AF_INET6, 0
DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0
#include "strv.h"
#include "tclass.h"
-/* Let's assume that anything above this number is a user misconfiguration. */
-#define MAX_NTP_SERVERS 128U
-
static int network_resolve_netdev_one(Network *network, const char *name, NetDevKind kind, NetDev **ret) {
const char *kind_string;
NetDev *netdev;
.keep_configuration = manager->keep_configuration,
+ .use_domains = _USE_DOMAINS_INVALID,
+
+ .compat_dhcp_use_domains = _USE_DOMAINS_INVALID,
+ .compat_dhcp_use_dns = -1,
+ .compat_dhcp_use_ntp = -1,
+
.dhcp_duid.type = _DUID_TYPE_INVALID,
.dhcp_critical = -1,
- .dhcp_use_ntp = true,
+ .dhcp_use_ntp = -1,
.dhcp_routes_to_ntp = true,
.dhcp_use_sip = true,
.dhcp_use_captive_portal = true,
- .dhcp_use_dns = true,
+ .dhcp_use_dns = -1,
.dhcp_routes_to_dns = true,
- .dhcp_use_domains = manager->dhcp_use_domains,
+ .dhcp_use_domains = _USE_DOMAINS_INVALID,
.dhcp_use_hostname = true,
.dhcp_use_routes = true,
.dhcp_use_gateway = -1,
.dhcp6_use_address = true,
.dhcp6_use_pd_prefix = true,
- .dhcp6_use_dns = true,
- .dhcp6_use_domains = manager->dhcp6_use_domains,
+ .dhcp6_use_dns = -1,
+ .dhcp6_use_domains = _USE_DOMAINS_INVALID,
.dhcp6_use_hostname = true,
- .dhcp6_use_ntp = true,
+ .dhcp6_use_ntp = -1,
.dhcp6_use_captive_portal = true,
.dhcp6_use_rapid_commit = true,
.dhcp6_send_hostname = true,
.ndisc = -1,
.ndisc_use_redirect = true,
- .ndisc_use_dns = true,
+ .ndisc_use_dns = -1,
.ndisc_use_gateway = true,
.ndisc_use_captive_portal = true,
.ndisc_use_route_prefix = true,
.ndisc_use_hop_limit = true,
.ndisc_use_reachable_time = true,
.ndisc_use_retransmission_time = true,
+ .ndisc_use_domains = _USE_DOMAINS_INVALID,
.ndisc_route_table = RT_TABLE_MAIN,
.ndisc_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH,
.ndisc_route_metric_medium = IPV6RA_ROUTE_METRIC_MEDIUM,
return 0;
}
-int config_parse_domains(
- 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 *n = ASSERT_PTR(userdata);
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- if (isempty(rvalue)) {
- n->search_domains = ordered_set_free(n->search_domains);
- n->route_domains = ordered_set_free(n->route_domains);
- return 0;
- }
-
- for (const char *p = rvalue;;) {
- _cleanup_free_ char *w = NULL, *normalized = NULL;
- const char *domain;
- bool is_route;
-
- r = extract_first_word(&p, &w, NULL, 0);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to extract search or route domain, ignoring: %s", rvalue);
- return 0;
- }
- if (r == 0)
- return 0;
-
- is_route = w[0] == '~';
- domain = is_route ? w + 1 : w;
-
- if (dns_name_is_root(domain) || streq(domain, "*")) {
- /* If the root domain appears as is, or the special token "*" is found, we'll
- * consider this as routing domain, unconditionally. */
- is_route = true;
- domain = "."; /* make sure we don't allow empty strings, thus write the root
- * domain as "." */
- } else {
- r = dns_name_normalize(domain, 0, &normalized);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "'%s' is not a valid domain name, ignoring.", domain);
- continue;
- }
-
- domain = normalized;
-
- if (is_localhost(domain)) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "'localhost' domain may not be configured as search or route domain, ignoring assignment: %s",
- domain);
- continue;
- }
- }
-
- OrderedSet **set = is_route ? &n->route_domains : &n->search_domains;
- r = ordered_set_put_strdup(set, domain);
- if (r == -EEXIST)
- continue;
- if (r < 0)
- return log_oom();
- }
-}
-
-int config_parse_timezone(
- 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) {
-
- char **tz = ASSERT_PTR(data);
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- if (isempty(rvalue)) {
- *tz = mfree(*tz);
- return 0;
- }
-
- r = verify_timezone(rvalue, LOG_WARNING);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Timezone is not valid, ignoring assignment: %s", rvalue);
- return 0;
- }
-
- return free_and_strdup_warn(tz, rvalue);
-}
-
-int config_parse_dns(
- 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 *n = ASSERT_PTR(userdata);
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- if (isempty(rvalue)) {
- for (unsigned i = 0; i < n->n_dns; i++)
- in_addr_full_free(n->dns[i]);
- n->dns = mfree(n->dns);
- n->n_dns = 0;
- return 0;
- }
-
- for (const char *p = rvalue;;) {
- _cleanup_(in_addr_full_freep) struct in_addr_full *dns = NULL;
- _cleanup_free_ char *w = NULL;
- struct in_addr_full **m;
-
- r = extract_first_word(&p, &w, NULL, 0);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid syntax, ignoring: %s", rvalue);
- return 0;
- }
- if (r == 0)
- return 0;
-
- r = in_addr_full_new_from_string(w, &dns);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to parse dns server address, ignoring: %s", w);
- continue;
- }
-
- if (IN_SET(dns->port, 53, 853))
- dns->port = 0;
-
- m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_full*));
- if (!m)
- return log_oom();
-
- m[n->n_dns++] = TAKE_PTR(dns);
- n->dns = m;
- }
-}
-
-int config_parse_dnssec_negative_trust_anchors(
- 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) {
-
- Set **nta = ASSERT_PTR(data);
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- if (isempty(rvalue)) {
- *nta = set_free_free(*nta);
- return 0;
- }
-
- for (const char *p = rvalue;;) {
- _cleanup_free_ char *w = NULL;
-
- r = extract_first_word(&p, &w, NULL, 0);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to extract negative trust anchor domain, ignoring: %s", rvalue);
- return 0;
- }
- if (r == 0)
- return 0;
-
- r = dns_name_is_valid(w);
- if (r <= 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "%s is not a valid domain name, ignoring.", w);
- continue;
- }
-
- r = set_ensure_consume(nta, &dns_name_hash_ops, TAKE_PTR(w));
- if (r < 0)
- return log_oom();
- }
-}
-
-int config_parse_ntp(
- 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) {
-
- char ***l = ASSERT_PTR(data);
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- if (isempty(rvalue)) {
- *l = strv_free(*l);
- return 0;
- }
-
- for (const char *p = rvalue;;) {
- _cleanup_free_ char *w = NULL;
-
- r = extract_first_word(&p, &w, NULL, 0);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to extract NTP server name, ignoring: %s", rvalue);
- return 0;
- }
- if (r == 0)
- return 0;
-
- r = dns_name_is_valid_or_address(w);
- if (r <= 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "%s is not a valid domain name or IP address, ignoring.", w);
- continue;
- }
-
- if (strv_length(*l) > MAX_NTP_SERVERS) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "More than %u NTP servers specified, ignoring \"%s\" and any subsequent entries.",
- MAX_NTP_SERVERS, w);
- return 0;
- }
-
- r = strv_consume(l, TAKE_PTR(w));
- if (r < 0)
- return log_oom();
- }
-}
-
int config_parse_required_for_online(
const char *unit,
const char *filename,
#include "networkd-dhcp-common.h"
#include "networkd-dhcp4.h"
#include "networkd-dhcp6.h"
+#include "networkd-dns.h"
#include "networkd-ipv6ll.h"
#include "networkd-lldp-rx.h"
#include "networkd-ndisc.h"
bool default_route_on_device;
AddressFamily ip_masquerade;
+ /* Protocol independent settings */
+ UseDomains use_domains;
+
+ /* For backward compatibility, only applied to DHCPv4 and DHCPv6. */
+ UseDomains compat_dhcp_use_domains;
+ int compat_dhcp_use_dns;
+ int compat_dhcp_use_ntp;
+
/* DHCP Client Support */
AddressFamily dhcp;
struct in_addr dhcp_request_address;
int dhcp_broadcast;
int dhcp_ipv6_only_mode;
int dhcp_use_rapid_commit;
- bool dhcp_use_dns;
- bool dhcp_use_dns_set;
+ int dhcp_use_dns;
bool dhcp_routes_to_dns;
- bool dhcp_use_ntp;
- bool dhcp_use_ntp_set;
+ int dhcp_use_ntp;
bool dhcp_routes_to_ntp;
bool dhcp_use_sip;
bool dhcp_use_captive_portal;
bool dhcp_use_6rd;
bool dhcp_send_release;
bool dhcp_send_decline;
- DHCPUseDomains dhcp_use_domains;
- bool dhcp_use_domains_set;
+ UseDomains dhcp_use_domains;
Set *dhcp_deny_listed_ip;
Set *dhcp_allow_listed_ip;
Set *dhcp_request_options;
bool dhcp6_use_pd_prefix;
bool dhcp6_send_hostname;
bool dhcp6_send_hostname_set;
- bool dhcp6_use_dns;
- bool dhcp6_use_dns_set;
+ int dhcp6_use_dns;
bool dhcp6_use_hostname;
- bool dhcp6_use_ntp;
- bool dhcp6_use_ntp_set;
+ int dhcp6_use_ntp;
bool dhcp6_use_captive_portal;
bool dhcp6_use_rapid_commit;
- DHCPUseDomains dhcp6_use_domains;
- bool dhcp6_use_domains_set;
+ UseDomains dhcp6_use_domains;
uint32_t dhcp6_iaid;
bool dhcp6_iaid_set;
bool dhcp6_iaid_set_explicitly;
/* NDisc support */
int ndisc;
bool ndisc_use_redirect;
- bool ndisc_use_dns;
+ int ndisc_use_dns;
bool ndisc_use_gateway;
bool ndisc_use_route_prefix;
bool ndisc_use_autonomous_prefix;
bool ndisc_use_pref64;
bool active_slave;
bool primary_slave;
- DHCPUseDomains ndisc_use_domains;
+ UseDomains ndisc_use_domains;
IPv6AcceptRAStartDHCP6Client ndisc_start_dhcp6_client;
uint32_t ndisc_route_table;
bool ndisc_route_table_set;
CONFIG_PARSER_PROTOTYPE(config_parse_stacked_netdev);
CONFIG_PARSER_PROTOTYPE(config_parse_tunnel);
-CONFIG_PARSER_PROTOTYPE(config_parse_domains);
-CONFIG_PARSER_PROTOTYPE(config_parse_dns);
-CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
-CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors);
-CONFIG_PARSER_PROTOTYPE(config_parse_ntp);
CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online);
CONFIG_PARSER_PROTOTYPE(config_parse_required_family_for_online);
CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "dns-domain.h"
+#include "networkd-network.h"
+#include "networkd-ntp.h"
+#include "parse-util.h"
+#include "strv.h"
+
+/* Let's assume that anything above this number is a user misconfiguration. */
+#define MAX_NTP_SERVERS 128U
+
+bool link_get_use_ntp(Link *link, NetworkConfigSource proto) {
+ int n, c;
+
+ assert(link);
+
+ if (!link->network)
+ return false;
+
+ switch (proto) {
+ case NETWORK_CONFIG_SOURCE_DHCP4:
+ n = link->network->dhcp_use_ntp;
+ c = link->network->compat_dhcp_use_ntp;
+ break;
+ case NETWORK_CONFIG_SOURCE_DHCP6:
+ n = link->network->dhcp6_use_ntp;
+ c = link->network->compat_dhcp_use_ntp;
+ break;
+ default:
+ assert_not_reached();
+ }
+
+ /* If per-network and per-protocol setting is specified, use it. */
+ if (n >= 0)
+ return n;
+
+ /* If compat setting is specified, use it. */
+ if (c >= 0)
+ return c;
+
+ /* Otherwise, defaults to yes. */
+ return true;
+}
+
+int config_parse_ntp(
+ 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) {
+
+ char ***l = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *l = strv_free(*l);
+ return 0;
+ }
+
+ for (const char *p = rvalue;;) {
+ _cleanup_free_ char *w = NULL;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to extract NTP server name, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = dns_name_is_valid_or_address(w);
+ if (r <= 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "%s is not a valid domain name or IP address, ignoring.", w);
+ continue;
+ }
+
+ if (strv_length(*l) > MAX_NTP_SERVERS) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "More than %u NTP servers specified, ignoring \"%s\" and any subsequent entries.",
+ MAX_NTP_SERVERS, w);
+ return 0;
+ }
+
+ r = strv_consume(l, TAKE_PTR(w));
+ if (r < 0)
+ return log_oom();
+ }
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "conf-parser.h"
+#include "networkd-util.h"
+
+typedef struct Link Link;
+
+bool link_get_use_ntp(Link *link, NetworkConfigSource proto);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_ntp);
}
int radv_update_mac(Link *link) {
- bool restart;
- int r;
-
assert(link);
if (!link->radv)
if (link->hw_addr.length != ETH_ALEN)
return 0;
- restart = sd_radv_is_running(link->radv);
-
- r = sd_radv_stop(link->radv);
- if (r < 0)
- return r;
-
- r = sd_radv_set_mac(link->radv, &link->hw_addr.ether);
- if (r < 0)
- return r;
-
- if (restart) {
- r = sd_radv_start(link->radv);
- if (r < 0)
- return r;
- }
-
- return 0;
+ return sd_radv_set_mac(link->radv, &link->hw_addr.ether);
}
static int radv_is_ready_to_configure(Link *link) {
return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m");
}
+ r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address);
+ if (r < 0)
+ return r;
+
log_link_debug(link, "Starting IPv6 Router Advertisements");
return sd_radv_start(link->radv);
}
#include "networkd-manager-bus.h"
#include "networkd-manager.h"
#include "networkd-network.h"
+#include "networkd-ntp.h"
#include "networkd-state-file.h"
#include "ordered-set.h"
#include "set.h"
if (r < 0)
return r;
- if (link->dhcp_lease && link->network->dhcp_use_dns) {
+ if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
const struct in_addr *addresses;
r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
}
}
- if (link->dhcp6_lease && link->network->dhcp6_use_dns) {
+ if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
const struct in6_addr *addresses;
r = sd_dhcp6_lease_get_dns(link->dhcp6_lease, &addresses);
}
}
- if (link->network->ndisc_use_dns) {
+ if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) {
NDiscRDNSS *a;
SET_FOREACH(a, link->ndisc_rdnss) {
if (r < 0)
return r;
- if (link->dhcp_lease && link->network->dhcp_use_ntp) {
+ if (link->dhcp_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
const struct in_addr *addresses;
r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
}
}
- if (link->dhcp6_lease && link->network->dhcp6_use_ntp) {
+ if (link->dhcp6_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
const struct in6_addr *addresses;
char **fqdn;
static int link_put_domains(Link *link, bool is_route, OrderedSet **s) {
OrderedSet *link_domains, *network_domains;
- DHCPUseDomains use_domains;
+ UseDomains use_domains;
int r;
assert(link);
link_domains = is_route ? link->route_domains : link->search_domains;
network_domains = is_route ? link->network->route_domains : link->network->search_domains;
- use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES;
+ use_domains = is_route ? USE_DOMAINS_ROUTE : USE_DOMAINS_YES;
if (link_domains)
return ordered_set_put_string_set(s, link_domains);
if (r < 0)
return r;
- if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) {
+ if (link->dhcp_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) {
const char *domainname;
char **domains;
}
}
- if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) {
+ if (link->dhcp6_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) {
char **domains;
r = sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains);
}
}
- if (link->network->ndisc_use_domains == use_domains) {
+ if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) {
NDiscDNSSL *a;
SET_FOREACH(a, link->ndisc_dnssl) {
fputc('\n', f);
}
-static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, DHCPUseDomains use_domains) {
+static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, UseDomains use_domains) {
bool space = false;
const char *p;
ORDERED_SET_FOREACH(p, static_domains)
fputs_with_separator(f, p, NULL, &space);
- if (use_domains == DHCP_USE_DOMAINS_NO)
+ if (use_domains == USE_DOMAINS_NO)
return;
- if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) {
+ if (link->dhcp_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) {
const char *domainname;
char **domains;
fputstrv(f, domains, NULL, &space);
}
- if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) {
+ if (link->dhcp6_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) {
char **domains;
if (sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains) >= 0)
fputstrv(f, domains, NULL, &space);
}
- if (link->network->ndisc_use_domains == use_domains) {
+ if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) {
NDiscDNSSL *dd;
SET_FOREACH(dd, link->ndisc_dnssl)
serialize_addresses(f, NULL, &space,
NULL,
link->dhcp_lease,
- link->network->dhcp_use_dns,
+ link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4),
SD_DHCP_LEASE_DNS,
link->dhcp6_lease,
- link->network->dhcp6_use_dns,
+ link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6),
sd_dhcp6_lease_get_dns,
NULL);
- if (link->network->ndisc_use_dns) {
+ if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) {
NDiscRDNSS *dd;
SET_FOREACH(dd, link->ndisc_rdnss)
serialize_addresses(f, "NTP", NULL,
link->network->ntp,
link->dhcp_lease,
- link->network->dhcp_use_ntp,
+ link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4),
SD_DHCP_LEASE_NTP,
link->dhcp6_lease,
- link->network->dhcp6_use_ntp,
+ link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6),
sd_dhcp6_lease_get_ntp_addrs,
sd_dhcp6_lease_get_ntp_fqdn);
fputs("DOMAINS=", f);
if (link->search_domains)
- link_save_domains(link, f, link->search_domains, DHCP_USE_DOMAINS_NO);
+ link_save_domains(link, f, link->search_domains, USE_DOMAINS_NO);
else
- link_save_domains(link, f, link->network->search_domains, DHCP_USE_DOMAINS_YES);
+ link_save_domains(link, f, link->network->search_domains, USE_DOMAINS_YES);
fputc('\n', f);
/************************************************************/
fputs("ROUTE_DOMAINS=", f);
if (link->route_domains)
- link_save_domains(link, f, link->route_domains, DHCP_USE_DOMAINS_NO);
+ link_save_domains(link, f, link->route_domains, USE_DOMAINS_NO);
else
- link_save_domains(link, f, link->network->route_domains, DHCP_USE_DOMAINS_ROUTE);
+ link_save_domains(link, f, link->network->route_domains, USE_DOMAINS_ROUTE);
fputc('\n', f);
/************************************************************/
#ManageForeignNextHops=yes
#RouteTable=
#IPv6PrivacyExtensions=no
+#UseDomains=no
+
+[IPv6AcceptRA]
+#UseDomains=
[DHCPv4]
#DUIDType=vendor
#DUIDRawData=
-#UseDomains=no
+#UseDomains=
[DHCPv6]
#DUIDType=vendor
#DUIDRawData=
-#UseDomains=no
+#UseDomains=
[DHCPServer]
#PersistLeases=yes
test_table(bond_xmit_hash_policy, NETDEV_BOND_XMIT_HASH_POLICY);
test_table(dhcp6_message_status, DHCP6_STATUS);
test_table_sparse(dhcp6_message_type, DHCP6_MESSAGE_TYPE); /* enum starts from 1 */
- test_table(dhcp_use_domains, DHCP_USE_DOMAINS);
+ test_table(use_domains, USE_DOMAINS);
test_table(duplex, DUP);
test_table(ip6tnl_mode, NETDEV_IP6_TNL_MODE);
test_table(ipv6_privacy_extensions, IPV6_PRIVACY_EXTENSIONS);
Exec.CPUAffinity, config_parse_cpu_affinity, 0, 0
Exec.ResolvConf, config_parse_resolv_conf, 0, offsetof(Settings, resolv_conf)
Exec.LinkJournal, config_parse_link_journal, 0, 0
-Exec.Timezone, config_parse_timezone, 0, offsetof(Settings, timezone)
+Exec.Timezone, config_parse_timezone_mode, 0, offsetof(Settings, timezone)
Exec.SuppressSync, config_parse_tristate, 0, offsetof(Settings, suppress_sync)
Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only)
Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode)
DEFINE_CONFIG_PARSE_ENUM(config_parse_resolv_conf, resolv_conf_mode, ResolvConfMode, "Failed to parse resolv.conf mode");
static const char *const resolv_conf_mode_table[_RESOLV_CONF_MODE_MAX] = {
- [RESOLV_CONF_OFF] = "off",
- [RESOLV_CONF_COPY_HOST] = "copy-host",
- [RESOLV_CONF_COPY_STATIC] = "copy-static",
- [RESOLV_CONF_COPY_UPLINK] = "copy-uplink",
- [RESOLV_CONF_COPY_STUB] = "copy-stub",
- [RESOLV_CONF_REPLACE_HOST] = "replace-host",
+ [RESOLV_CONF_OFF] = "off",
+ [RESOLV_CONF_COPY_HOST] = "copy-host",
+ [RESOLV_CONF_COPY_STATIC] = "copy-static",
+ [RESOLV_CONF_COPY_UPLINK] = "copy-uplink",
+ [RESOLV_CONF_COPY_STUB] = "copy-stub",
+ [RESOLV_CONF_REPLACE_HOST] = "replace-host",
[RESOLV_CONF_REPLACE_STATIC] = "replace-static",
[RESOLV_CONF_REPLACE_UPLINK] = "replace-uplink",
- [RESOLV_CONF_REPLACE_STUB] = "replace-stub",
- [RESOLV_CONF_BIND_HOST] = "bind-host",
- [RESOLV_CONF_BIND_STATIC] = "bind-static",
- [RESOLV_CONF_BIND_UPLINK] = "bind-uplink",
- [RESOLV_CONF_BIND_STUB] = "bind-stub",
- [RESOLV_CONF_DELETE] = "delete",
- [RESOLV_CONF_AUTO] = "auto",
+ [RESOLV_CONF_REPLACE_STUB] = "replace-stub",
+ [RESOLV_CONF_BIND_HOST] = "bind-host",
+ [RESOLV_CONF_BIND_STATIC] = "bind-static",
+ [RESOLV_CONF_BIND_UPLINK] = "bind-uplink",
+ [RESOLV_CONF_BIND_STUB] = "bind-stub",
+ [RESOLV_CONF_DELETE] = "delete",
+ [RESOLV_CONF_AUTO] = "auto",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolv_conf_mode, ResolvConfMode, RESOLV_CONF_AUTO);
return 0;
}
-DEFINE_CONFIG_PARSE_ENUM(config_parse_timezone, timezone_mode, TimezoneMode, "Failed to parse timezone mode");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_timezone_mode, timezone_mode, TimezoneMode, "Failed to parse timezone mode");
static const char *const timezone_mode_table[_TIMEZONE_MODE_MAX] = {
- [TIMEZONE_OFF] = "off",
- [TIMEZONE_COPY] = "copy",
- [TIMEZONE_BIND] = "bind",
+ [TIMEZONE_OFF] = "off",
+ [TIMEZONE_COPY] = "copy",
+ [TIMEZONE_BIND] = "bind",
[TIMEZONE_SYMLINK] = "symlink",
- [TIMEZONE_DELETE] = "delete",
- [TIMEZONE_AUTO] = "auto",
+ [TIMEZONE_DELETE] = "delete",
+ [TIMEZONE_AUTO] = "auto",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(timezone_mode, TimezoneMode, TIMEZONE_AUTO);
DEFINE_CONFIG_PARSE_ENUM(config_parse_userns_ownership, user_namespace_ownership, UserNamespaceOwnership, "Failed to parse user namespace ownership mode");
static const char *const user_namespace_ownership_table[_USER_NAMESPACE_OWNERSHIP_MAX] = {
- [USER_NAMESPACE_OWNERSHIP_OFF] = "off",
+ [USER_NAMESPACE_OWNERSHIP_OFF] = "off",
[USER_NAMESPACE_OWNERSHIP_CHOWN] = "chown",
- [USER_NAMESPACE_OWNERSHIP_MAP] = "map",
- [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto",
+ [USER_NAMESPACE_OWNERSHIP_MAP] = "map",
+ [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto",
};
DEFINE_STRING_TABLE_LOOKUP(user_namespace_ownership, UserNamespaceOwnership);
CONFIG_PARSER_PROTOTYPE(config_parse_cpu_affinity);
CONFIG_PARSER_PROTOTYPE(config_parse_resolv_conf);
CONFIG_PARSER_PROTOTYPE(config_parse_link_journal);
-CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
+CONFIG_PARSER_PROTOTYPE(config_parse_timezone_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_userns_chown);
CONFIG_PARSER_PROTOTYPE(config_parse_userns_ownership);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_user);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Must provide all PCR values when using TPM2 device key.");
} else {
- r = tpm2_context_new(arg_tpm2_device, &tpm2_context);
+ r = tpm2_context_new_or_warn(arg_tpm2_device, &tpm2_context);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
if (!tpm2_pcr_values_has_all_values(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values)) {
r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values);
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
int r;
- r = tpm2_context_new(arg_tpm2_device, &c);
+ r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
if (r < 0)
return r;
#include "random-util.h"
#include "recovery-key.h"
#include "sort-util.h"
+#include "string-table.h"
#include "terminal-util.h"
#include "tpm2-util.h"
#include "unaligned.h"
#include "varlink-io.systemd.PCRLock.h"
#include "verbs.h"
+typedef enum RecoveryPinMode {
+ RECOVERY_PIN_HIDE, /* generate a recovery PIN automatically, but don't show it (alias: "no") */
+ RECOVERY_PIN_SHOW, /* generate a recovery PIN automatically, and display it to the user */
+ RECOVERY_PIN_QUERY, /* asks the user for a PIN to use interactively (alias: "yes") */
+ _RECOVERY_PIN_MODE_MAX,
+ _RECOVERY_PIN_MODE_INVALID = -EINVAL,
+} RecoveryPinMode;
+
static PagerFlags arg_pager_flags = 0;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF|JSON_FORMAT_NEWLINE;
static char **arg_components = NULL;
static char *arg_location_start = NULL;
static char *arg_location_end = NULL;
static TPM2_HANDLE arg_nv_index = 0;
-static bool arg_recovery_pin = false;
+static RecoveryPinMode arg_recovery_pin = RECOVERY_PIN_HIDE;
static char *arg_policy_path = NULL;
static bool arg_force = false;
static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
(UINT32_C(1) << TPM2_PCR_SHIM_POLICY) | \
(UINT32_C(1) << TPM2_PCR_SYSTEM_IDENTITY))
+static const char* recovery_pin_mode_table[_RECOVERY_PIN_MODE_MAX] = {
+ [RECOVERY_PIN_HIDE] = "hide",
+ [RECOVERY_PIN_SHOW] = "show",
+ [RECOVERY_PIN_QUERY] = "query",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(recovery_pin_mode, RecoveryPinMode, RECOVERY_PIN_QUERY);
+
typedef struct EventLogRecordBank EventLogRecordBank;
typedef struct EventLogRecord EventLogRecord;
typedef struct EventLogRegisterBank EventLogRegisterBank;
assert(el);
- r = tpm2_context_new(NULL, &tc);
+ r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
if (r < 0)
return r;
assert_se(a = tpm2_hash_alg_to_string(*pa));
assert_se(md = EVP_get_digestbyname(a));
hash_ssize = EVP_MD_size(md);
- assert_se(hash_ssize > 0);
+ assert(hash_ssize > 0);
hash_usize = hash_ssize;
hash = malloc(hash_usize);
return 0;
}
+static void evp_md_ctx_free_all(EVP_MD_CTX *(*md)[TPM2_N_HASH_ALGORITHMS]) {
+ assert(md);
+ FOREACH_ARRAY(alg, *md, TPM2_N_HASH_ALGORITHMS)
+ if (*alg)
+ EVP_MD_CTX_free(*alg);
+}
+
+static int make_pcrlock_record_from_stream(
+ uint32_t pcr_mask,
+ FILE *f,
+ JsonVariant **ret_records) {
+
+ _cleanup_(json_variant_unrefp) JsonVariant *digests = NULL;
+ _cleanup_(evp_md_ctx_free_all) EVP_MD_CTX *mdctx[TPM2_N_HASH_ALGORITHMS] = {};
+ int r;
+
+ assert(f);
+ assert(ret_records);
+
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+ const char *a;
+ const EVP_MD *md;
+
+ assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
+ assert_se(md = EVP_get_digestbyname(a));
+
+ mdctx[i] = EVP_MD_CTX_new();
+ if (!mdctx[i])
+ return log_oom();
+
+ if (EVP_DigestInit_ex(mdctx[i], md, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to initialize message digest for %s.", a);
+ }
+
+ for (;;) {
+ uint8_t buffer[64*1024];
+ size_t n;
+
+ n = fread(buffer, 1, sizeof(buffer), f);
+ if (ferror(f))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to read file: %m");
+ if (n == 0 && feof(f))
+ break;
+
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
+ if (EVP_DigestUpdate(mdctx[i], buffer, n) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
+ }
+
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+ const char *a;
+ int hash_ssize;
+ unsigned hash_usize;
+
+ assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
+ hash_ssize = EVP_MD_CTX_size(mdctx[i]);
+ assert(hash_ssize > 0 && hash_ssize <= EVP_MAX_MD_SIZE);
+ hash_usize = hash_ssize;
+ unsigned char hash[hash_usize];
+
+ if (EVP_DigestFinal_ex(mdctx[i], hash, &hash_usize) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to finalize hash context for algorithn '%s'.", a);
+
+ r = json_variant_append_arrayb(
+ &digests,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("hashAlg", JSON_BUILD_STRING(a)),
+ JSON_BUILD_PAIR("digest", JSON_BUILD_HEX(hash, hash_usize))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON digest object: %m");
+ }
+
+ for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
+ _cleanup_(json_variant_unrefp) JsonVariant *record = NULL;
+
+ if (!FLAGS_SET(pcr_mask, UINT32_C(1) << i))
+ continue;
+
+ r = json_build(&record,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("pcr", JSON_BUILD_UNSIGNED(i)),
+ JSON_BUILD_PAIR("digests", JSON_BUILD_VARIANT(digests))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to build record object: %m");
+
+ r = json_variant_append_array(ret_records, record);
+ if (r < 0)
+ return log_error_errno(r, "Failed to append to JSON array: %m");
+ }
+
+ return 0;
+}
+
static const char *pcrlock_path(const char *default_pcrlock_path) {
return arg_pcrlock_path ?: arg_pcrlock_auto ? default_pcrlock_path : NULL;
}
}
static int verb_lock_raw(int argc, char *argv[], void *userdata) {
- _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
- _cleanup_free_ char *data = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *records = NULL;
_cleanup_fclose_ FILE *f = NULL;
- size_t size;
int r;
if (arg_pcr_mask == 0)
return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
}
- r = read_full_stream(f ?: stdin, &data, &size);
+ r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records);
if (r < 0)
- return log_error_errno(r, "Failed to read data from stdin: %m");
-
- for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
- _cleanup_(json_variant_unrefp) JsonVariant *record = NULL;
-
- if (!FLAGS_SET(arg_pcr_mask, UINT32_C(1) << i))
- continue;
-
- r = make_pcrlock_record(i, data, size, &record);
- if (r < 0)
- return r;
-
- r = json_variant_append_array(&array, record);
- if (r < 0)
- return log_error_errno(r, "Failed to append to JSON array: %m");
- }
+ return r;
- return write_pcrlock(array, NULL);
+ return write_pcrlock(records, NULL);
}
static int verb_unlock_simple(int argc, char *argv[], void *userdata) {
/* Generates expected records from the current SecureBoot state, as readable in the EFI variables
* right now. */
- FOREACH_ARRAY(vv, variables, ELEMENTSOF(variables)) {
+ FOREACH_ELEMENT(vv, variables) {
_cleanup_(json_variant_unrefp) JsonVariant *record = NULL;
_cleanup_free_ char *name = NULL;
}
static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) {
- _cleanup_(json_variant_unrefp) JsonVariant *record = NULL, *array = NULL;
- _cleanup_free_ void *data = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *records = NULL;
_cleanup_fclose_ FILE *f = NULL;
- size_t size;
+ uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD;
int r;
if (argc >= 2) {
return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
}
- r = read_full_stream(f ?: stdin, (char**) &data, &size);
- if (r < 0)
- return log_error_errno(r, "Failed to read data from stdin: %m");
-
- r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, data, size, &record);
+ r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records);
if (r < 0)
return r;
- r = json_variant_new_array(&array, &record, 1);
- if (r < 0)
- return log_error_errno(r, "Failed to create record array: %m");
-
- r = write_pcrlock(array, PCRLOCK_KERNEL_INITRD_PATH);
+ r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH);
if (r < 0)
return r;
return 1;
}
-static int make_policy(bool force, bool recovery_pin) {
+static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
int r;
/* Here's how this all works: after predicting all possible PCR values for next boot (with
}
_cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
- r = tpm2_context_new(NULL, &tc);
+ r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
if (r < 0)
- return log_error_errno(r, "Failed to allocate TPM2 context: %m");
+ return r;
if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing.");
/* Acquire a recovery PIN, either from the user, or create a randomized one */
_cleanup_(erase_and_freep) char *pin = NULL;
- if (recovery_pin) {
+ if (recovery_pin_mode == RECOVERY_PIN_QUERY) {
r = getenv_steal_erase("PIN", &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire PIN from environment: %m");
}
} else if (!have_old_policy) {
- char rnd[256];
-
- r = crypto_random_bytes(rnd, sizeof(rnd));
+ r = make_recovery_key(&pin);
if (r < 0)
return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m");
- (void) base64mem(rnd, sizeof(rnd), &pin);
- explicit_bzero_safe(rnd, sizeof(rnd));
- if (!pin)
- return log_oom();
+ if (recovery_pin_mode == RECOVERY_PIN_SHOW)
+ printf("%s Selected recovery PIN is: %s%s%s\n",
+ special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY),
+ ansi_highlight_cyan(),
+ pin,
+ ansi_normal());
}
_cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
CLEANUP_ERASE(auth);
if (pin) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
if (r < 0)
return log_error_errno(r, "Failed to hash PIN: %m");
} else {
log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1));
}
- TPM2B_NV_PUBLIC nv_public = {};
+ /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL;
+ r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle);
+ if (r < 0)
+ return r;
+ TPM2B_NV_PUBLIC nv_public = {};
usec_t nv_index_start_usec = now(CLOCK_MONOTONIC);
if (!iovec_is_set(&nv_blob)) {
+ _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL;
+ r = tpm2_get_name(
+ tc,
+ pin_handle,
+ &pin_name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get name of PIN from TPM2: %m");
+
TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
- r = tpm2_calculate_policy_auth_value(&recovery_policy_digest);
+ r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name);
if (r < 0)
- return log_error_errno(r, "Failed to calculate authentication value policy: %m");
+ return log_error_errno(r, "Failed to calculate PolicySigned policy: %m");
log_debug("Allocating NV index to write PCR policy to...");
r = tpm2_define_policy_nv_index(
encryption_session,
arg_nv_index,
&recovery_policy_digest,
- pin,
- &auth,
&nv_index,
&nv_handle,
&nv_public);
return log_error_errno(r, "Failed to allocate NV index: %m");
}
- r = tpm2_set_auth_binary(tc, nv_handle, &auth);
- if (r < 0)
- return log_error_errno(r, "Failed to set authentication value on NV index: %m");
-
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
r = tpm2_make_policy_session(
tc,
if (r < 0)
return log_error_errno(r, "Failed to allocate policy session: %m");
- r = tpm2_policy_auth_value(
+ r = tpm2_policy_signed_hmac_sha256(
tc,
policy_session,
+ pin_handle,
+ &IOVEC_MAKE(auth.buffer, auth.size),
/* ret_policy_digest= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to submit authentication value policy: %m");
assert(srk_blob);
_cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
- r = tpm2_context_new(NULL, &tc);
+ r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
if (r < 0)
return r;
}
case ARG_RECOVERY_PIN:
- r = parse_boolean_argument("--recovery-pin", optarg, &arg_recovery_pin);
- if (r < 0)
- return r;
+ arg_recovery_pin = recovery_pin_mode_from_string(optarg);
+ if (arg_recovery_pin < 0)
+ return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", optarg);
break;
case ARG_PCRLOCK:
if (r != 0)
return r;
- r = make_policy(p.force, /* recovery_key= */ false);
+ r = make_policy(p.force, /* recovery_key= */ RECOVERY_PIN_HIDE);
if (r < 0)
return r;
if (r == 0)
#include "strv.h"
#include "tmpfile-util.h"
#include "user-util.h"
+#include "vpick.h"
/* Markers used in the first line of our 20-portable.conf unit file drop-in to determine, that a) the unit file was
* dropped there by the portable service logic and b) for which image it was dropped there. */
_cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL, *confext_level = NULL;
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_strv_free_ char **valid_prefixes = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
assert(name_or_path);
- r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
+ /* If we get a path, then check if it can be resolved with vpick. We need this as we might just
+ * get a simple image name, which would make vpick error out. */
+ if (path_is_absolute(name_or_path)) {
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ name_or_path,
+ &pick_filter_image_any,
+ PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
+ &result);
+ if (r < 0)
+ return r;
+ if (!result.path)
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(ENOENT),
+ "No matching entry in .v/ directory %s found.",
+ name_or_path);
+
+ name_or_path = result.path;
+ }
+
+ r = image_find_harder(IMAGE_PORTABLE, name_or_path, /* root= */ NULL, &image);
if (r < 0)
return r;
}
STRV_FOREACH(p, extension_image_paths) {
+ _cleanup_(pick_result_done) PickResult ext_result = PICK_RESULT_NULL;
_cleanup_(image_unrefp) Image *new = NULL;
+ const char *path = *p;
+
+ if (path_is_absolute(*p)) {
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ *p,
+ &pick_filter_image_any,
+ PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
+ &ext_result);
+ if (r < 0)
+ return r;
+ if (!ext_result.path)
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(ENOENT),
+ "No matching entry in .v/ directory %s found.",
+ *p);
+
+ path = ext_result.path;
+ }
- r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new);
+ r = image_find_harder(IMAGE_PORTABLE, path, NULL, &new);
if (r < 0)
return r;
while (!isempty(marker))
STRV_FOREACH(image_name_or_path, root_and_extensions) {
_cleanup_free_ char *image = NULL, *base_image = NULL, *base_image_name_or_path = NULL;
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
if (r < 0)
return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", image);
- r = path_extract_image_name(*image_name_or_path, &base_image_name_or_path);
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ *image_name_or_path,
+ &pick_filter_image_any,
+ PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
+ &result);
+ if (r < 0)
+ return r;
+ if (!result.path)
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(ENOENT),
+ "No matching entry in .v/ directory %s found.",
+ *image_name_or_path);
+
+ r = path_extract_image_name(result.path, &base_image_name_or_path);
if (r < 0)
- return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", *image_name_or_path);
+ return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", result.path);
if (!streq(base_image, base_image_name_or_path)) {
if (match_all)
static bool arg_no_block = false;
static char **arg_extension_images = NULL;
static bool arg_force = false;
+static bool arg_clean = false;
STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep);
return 0;
}
-static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
+static int maybe_clean_units(sd_bus *bus, char **units) {
+ int r;
+
+ assert(bus);
+
+ if (!arg_clean)
+ return 0;
+
+ STRV_FOREACH(name, units) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "CleanUnit");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", *name);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_strv(m, STRV_MAKE("all", "fdstore"));
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, m, 0, &error, NULL);
+ if (r < 0)
+ return log_error_errno(
+ r,
+ "Failed to call CleanUnit on portable service %s: %s",
+ *name,
+ bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int maybe_stop_disable_clean(sd_bus *bus, char *image, char *argv[]) {
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_strv_free_ char **matches = NULL;
+ _cleanup_strv_free_ char **matches = NULL, **units = NULL;
int r;
- if (!arg_enable && !arg_now)
+ if (!arg_enable && !arg_now && !arg_clean)
return 0;
r = determine_matches(argv[1], argv + 2, true, &matches);
(void) maybe_start_stop_restart(bus, name, "StopUnit", wait);
(void) maybe_enable_disable(bus, name, false);
+
+ r = strv_extend(&units, name);
+ if (r < 0)
+ return log_oom();
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return r;
+ /* Need to ensure all units are stopped before calling CleanUnit, as files might be in use. */
+ (void) maybe_clean_units(bus, units);
+
return 0;
}
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- (void) maybe_stop_disable(bus, image, argv);
+ (void) maybe_stop_disable_clean(bus, image, argv);
method = strv_isempty(arg_extension_images) && !arg_force ? "DetachImage" : "DetachImageWithExtensions";
" --extension=PATH Extend the image with an overlay\n"
" --force Skip 'already active' check when attaching or\n"
" detaching an image (with extensions)\n"
+ " --clean When detaching, also remove configuration, state,\n"
+ " cache, logs or runtime data of the portable\n"
+ " service(s)\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ARG_NO_BLOCK,
ARG_EXTENSION,
ARG_FORCE,
+ ARG_CLEAN,
};
static const struct option options[] = {
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "extension", required_argument, NULL, ARG_EXTENSION },
{ "force", no_argument, NULL, ARG_FORCE },
+ { "clean", no_argument, NULL, ARG_CLEAN },
{}
};
arg_force = true;
break;
+ case ARG_CLEAN:
+ arg_clean = true;
+ break;
+
case '?':
return -EINVAL;
assert(manager);
- ERR_load_crypto_strings();
- SSL_load_error_strings();
-
manager->dnstls_data.ctx = SSL_CTX_new(TLS_client_method());
if (!manager->dnstls_data.ctx)
return -ENOMEM;
}
if (!isempty(result))
- FOREACH_ARRAY(i, explanations, ELEMENTSOF(explanations))
+ FOREACH_ELEMENT(i, explanations)
if (streq(result, i->result)) {
log_error("Job for %s failed because %s.\n"
"See \"%s status %s\" and \"%s -xeu %s\" for details.\n",
sd_bus_slot *slot_get_all;
sd_bus_slot *slot_properties_changed;
- bus_wait_for_units_unit_callback unit_callback;
+ bus_wait_for_units_unit_callback_t unit_callback;
void *userdata;
char *active_state;
Hashmap *items;
- bus_wait_for_units_ready_callback ready_callback;
- void *userdata;
-
- WaitForItem *current;
-
BusWaitForUnitsState state;
bool has_failed:1;
} BusWaitForUnits;
}
assert_se(hashmap_remove_value(item->parent->items, item->bus_path, item));
-
- if (item->parent->current == item)
- item->parent->current = NULL;
}
sd_bus_slot_unref(item->slot_properties_changed);
DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free);
static void call_unit_callback_and_wait(BusWaitForUnits *d, WaitForItem *item, bool good) {
- d->current = item;
-
if (item->unit_callback)
item->unit_callback(d, item->bus_path, good, item->userdata);
log_warning("D-Bus connection terminated while waiting for unit.");
bus_wait_for_units_clear(d);
-
- if (d->ready_callback)
- d->ready_callback(d, BUS_WAIT_FAILURE, d->userdata);
- else /* If no ready callback is specified close the connection so that the event loop exits */
- sd_bus_close(sd_bus_message_get_bus(m));
+ sd_bus_close(sd_bus_message_get_bus(m));
return 0;
}
return hashmap_isempty(d->items);
}
-void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) {
- assert(d);
-
- d->ready_callback = callback;
- d->userdata = userdata;
-}
-
static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
assert(d);
return;
d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS;
-
- if (d->ready_callback)
- d->ready_callback(d, d->state, d->userdata);
}
static void wait_for_item_check_ready(WaitForItem *item) {
bus_wait_for_units_check_ready(d);
}
-static int property_map_job(
+static int property_map_job_id(
sd_bus *bus,
const char *member,
sd_bus_message *m,
sd_bus_error *error,
void *userdata) {
- WaitForItem *item = ASSERT_PTR(userdata);
- const char *path;
- uint32_t id;
- int r;
+ uint32_t *job_id = ASSERT_PTR(userdata);
- r = sd_bus_message_read(m, "(uo)", &id, &path);
- if (r < 0)
- return r;
+ assert(m);
- item->job_id = id;
- return 0;
+ return sd_bus_message_read(m, "(uo)", job_id, /* path = */ NULL);
}
static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) {
static const struct bus_properties_map map[] = {
- { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) },
- { "Job", "(uo)", property_map_job, 0 },
- { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) },
+ { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) },
+ { "Job", "(uo)", property_map_job_id, offsetof(WaitForItem, job_id) },
+ { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) },
{}
};
BusWaitForUnits *d,
const char *unit,
BusWaitForUnitsFlags flags,
- bus_wait_for_units_unit_callback callback,
+ bus_wait_for_units_unit_callback_t callback,
void *userdata) {
_cleanup_(wait_for_item_freep) WaitForItem *item = NULL;
+ _cleanup_free_ char *bus_path = NULL;
int r;
assert(d);
assert(unit);
+ assert((flags & _BUS_WAIT_FOR_TARGET) != 0);
- assert(flags != 0);
+ bus_path = unit_dbus_path_from_name(unit);
+ if (!bus_path)
+ return -ENOMEM;
- r = hashmap_ensure_allocated(&d->items, &string_hash_ops);
- if (r < 0)
- return r;
+ if (hashmap_contains(d->items, bus_path))
+ return 0;
item = new(WaitForItem, 1);
if (!item)
*item = (WaitForItem) {
.flags = flags,
- .bus_path = unit_dbus_path_from_name(unit),
+ .bus_path = TAKE_PTR(bus_path),
.unit_callback = callback,
.userdata = userdata,
.job_id = UINT32_MAX,
};
- if (!item->bus_path)
- return -ENOMEM;
-
if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) {
r = sd_bus_call_method_async(
d->bus,
if (r < 0)
return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit);
- r = hashmap_put(d->items, item->bus_path, item);
+ r = hashmap_ensure_put(&d->items, &string_hash_ops, item->bus_path, item);
if (r < 0)
return r;
+ assert(r > 0);
d->state = BUS_WAIT_RUNNING;
item->parent = d;
TAKE_PTR(item);
- return 0;
+
+ return 1;
}
int bus_wait_for_units_run(BusWaitForUnits *d) {
BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */
BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */
BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */
+ _BUS_WAIT_FOR_TARGET = BUS_WAIT_FOR_MAINTENANCE_END|BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB,
} BusWaitForUnitsFlags;
-typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata);
-typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata);
+typedef void (*bus_wait_for_units_unit_callback_t)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata);
int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret);
BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d);
DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free);
-BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d);
-void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata);
-int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata);
+int bus_wait_for_units_add_unit(
+ BusWaitForUnits *d,
+ const char *unit,
+ BusWaitForUnitsFlags flags,
+ bus_wait_for_units_unit_callback_t callback,
+ void *userdata);
+
int bus_wait_for_units_run(BusWaitForUnits *d);
+BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d);
if (fd < 0)
return -errno;
- /* This leaves the timezone fields of struct tm
- * uninitialized! */
+ /* This leaves the timezone fields of struct tm uninitialized! */
if (ioctl(fd, RTC_RD_TIME, tm) < 0)
- return -errno;
+ /* Some drivers return -EINVAL in case the time could not be kept, i.e. power loss
+ * happened. Let's turn that into a clearly recognizable error */
+ return errno == EINVAL ? -ENODATA : -errno;
/* We don't know daylight saving, so we reset this in order not
* to confuse mktime(). */
* parse_compare_operator() are use on the same string? */
return _COMPARE_OPERATOR_INVALID;
- FOREACH_ARRAY(i, table, ELEMENTSOF(table)) {
+ FOREACH_ELEMENT(i, table) {
const char *e;
if (i->need_mask != 0 && !FLAGS_SET(flags, i->need_mask))
DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value");
DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value");
DEFINE_CONFIG_PARSE_PTR(config_parse_sec_fix_0, parse_sec_fix_0, usec_t, "Failed to parse time value");
+
+int config_parse_timezone(
+ 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) {
+
+ char **tz = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *tz = mfree(*tz);
+ return 0;
+ }
+
+ r = verify_timezone(rvalue, LOG_WARNING);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Timezone is not valid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ return free_and_strdup_warn(tz, rvalue);
+}
CONFIG_PARSER_PROTOTYPE(config_parse_permyriad);
CONFIG_PARSER_PROTOTYPE(config_parse_pid);
CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
+CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
typedef enum Disabled {
DISABLED_CONFIGURATION,
if (try_cfr) {
n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u);
if (n < 0) {
- if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF))
+ if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF, -EOPNOTSUPP))
return n;
try_cfr = false;
tpm2_pubkey_pcr_mask = 0;
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
- r = tpm2_context_new(tpm2_device, &tpm2_context);
+ r = tpm2_context_new_or_warn(tpm2_device, &tpm2_context);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
r = tpm2_get_best_pcr_bank(tpm2_context, tpm2_hash_pcr_mask | tpm2_pubkey_pcr_mask, &tpm2_pcr_bank);
if (r < 0)
}
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
- r = tpm2_context_new(device, &tpm2_context);
+ r = tpm2_context_new_or_warn(device, &tpm2_context);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
if (!(flags & TPM2_FLAGS_USE_PIN)) {
r = tpm2_unseal(tpm2_context,
* to lock down these nodes as much as we can, but otherwise try to match them as closely as possible with the
* underlying file, i.e. in the best case we offer the same node type as the underlying node. */
- FOREACH_ARRAY(m, table, ELEMENTSOF(table)) {
+ FOREACH_ELEMENT(m, table) {
_cleanup_free_ char *path = NULL;
mode_t inode_type = *m;
const char *fn;
.type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
.basename = name,
.architecture = _ARCHITECTURE_INVALID,
- .suffix = suffix,
+ .suffix = STRV_MAKE(suffix),
};
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
.type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
.basename = pretty,
.architecture = _ARCHITECTURE_INVALID,
- .suffix = suffix,
+ .suffix = STRV_MAKE(suffix),
};
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
/* Checks if this mount point is considered "API", and hence
* should be ignored */
- FOREACH_ARRAY(i, mount_table, ELEMENTSOF(mount_table))
+ FOREACH_ELEMENT(i, mount_table)
if (path_equal(path, i->where))
return true;
if (!cg_is_legacy_force_enabled())
return -ERFKILL;
- FOREACH_ARRAY(mp, cgroupv1_mount_table, ELEMENTSOF(cgroupv1_mount_table)) {
+ FOREACH_ELEMENT(mp, cgroupv1_mount_table) {
r = mount_one(mp, loaded_policy);
if (r < 0)
return r;
#include "string-util.h"
#if HAVE_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ec.h>
+
+# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+# include <openssl/engine.h>
+DISABLE_WARNING_DEPRECATED_DECLARATIONS;
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
+REENABLE_WARNING;
+# endif
+
/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error
* string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL
* errors." This logs at level debug. Returns -EIO (or -ENOMEM). */
assert(private_key_uri);
assert(ret);
+#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
_cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine);
if (!e)
*ret = TAKE_PTR(private_key);
return 0;
+#else
+ return -EOPNOTSUPP;
+#endif
}
int openssl_load_key_from_token(
# include <openssl/bio.h>
# include <openssl/bn.h>
# include <openssl/crypto.h>
-# include <openssl/engine.h>
# include <openssl/err.h>
# include <openssl/evp.h>
# include <openssl/opensslv.h>
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL);
-DISABLE_WARNING_DEPRECATED_DECLARATIONS;
-DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
-REENABLE_WARNING;
+
#if OPENSSL_VERSION_MAJOR >= 3
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL);
return false;
}
- FOREACH_ARRAY(i, operations, ELEMENTSOF(operations)) {
+ FOREACH_ELEMENT(i, operations) {
r = sleep_supported_internal(sleep_config, *i, /* check_allowed = */ false, &support);
if (r < 0)
return r;
* and switch_root() nevertheless. */
(void) base_filesystem_create_fd(new_root_fd, new_root, UID_INVALID, GID_INVALID);
- FOREACH_ARRAY(transfer, transfer_table, ELEMENTSOF(transfer_table)) {
+ FOREACH_ELEMENT(transfer, transfer_table) {
_cleanup_free_ char *chased = NULL;
unsigned long mount_flags;
void test_setup_logging(int level) {
log_set_assert_return_is_critical(true);
log_set_max_level(level);
- log_parse_environment();
- log_open();
+ log_setup();
}
int write_tmpfile(char *pattern, const char *contents) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- _cleanup_free_ char *scope = NULL;
+ _cleanup_free_ char *scope = NULL, *cgroup_root = NULL;
const char *object;
int r;
/* Let's try to run this test in a scope of its own, with delegation turned on, so that PID 1 doesn't
* interfere with our cgroup management. */
+ if (cg_pid_get_path(NULL, 0, &cgroup_root) >= 0 && cg_is_delegated(cgroup_root) && stderr_is_journal()) {
+ log_debug("Already running as a unit with delegated cgroup, not allocating a cgroup subroot.");
+ return 0;
+ }
r = sd_bus_default_system(&bus);
if (r < 0)
#include "sd-daemon.h"
#include "argv-util.h"
+#include "errno-util.h"
#include "macro.h"
#include "process-util.h"
#include "rlimit-util.h"
} \
})
+#define ASSERT_ERROR(expr1, expr2) \
+ ({ \
+ int _expr1 = (expr1); \
+ int _expr2 = (expr2); \
+ if (_expr1 >= 0) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \
+ abort(); \
+ } else if (-_expr1 != _expr2) { \
+ log_error_errno(_expr1, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_ERROR_ERRNO(expr1, expr2) \
+ ({ \
+ int _expr1 = (expr1); \
+ int _expr2 = (expr2); \
+ if (_expr1 >= 0) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \
+ abort(); \
+ } else if (errno != _expr2) { \
+ log_error_errno(errno, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(errno)); \
+ abort(); \
+ } \
+ })
+
#define ASSERT_TRUE(expr) \
({ \
if (!(expr)) { \
#include "recurse-dir.h"
#include "sha256.h"
#include "sort-util.h"
+#include "sparse-endian.h"
#include "stat-util.h"
#include "string-table.h"
#include "sync-util.h"
#include "tpm2-util.h"
#include "virt.h"
+#if HAVE_OPENSSL
+# include <openssl/hmac.h>
+#endif
+
#if HAVE_TPM2
static void *libtss2_esys_dl = NULL;
static void *libtss2_rc_dl = NULL;
static DLSYM_FUNCTION(Esys_PolicyGetDigest);
static DLSYM_FUNCTION(Esys_PolicyOR);
static DLSYM_FUNCTION(Esys_PolicyPCR);
+static DLSYM_FUNCTION(Esys_PolicySigned);
static DLSYM_FUNCTION(Esys_ReadPublic);
static DLSYM_FUNCTION(Esys_StartAuthSession);
static DLSYM_FUNCTION(Esys_Startup);
static DLSYM_FUNCTION(Esys_TR_Serialize);
static DLSYM_FUNCTION(Esys_TR_SetAuth);
static DLSYM_FUNCTION(Esys_TRSess_GetAttributes);
+static DLSYM_FUNCTION(Esys_TRSess_GetNonceTPM);
static DLSYM_FUNCTION(Esys_TRSess_SetAttributes);
static DLSYM_FUNCTION(Esys_Unseal);
static DLSYM_FUNCTION(Esys_VerifySignature);
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyOR),
DLSYM_ARG(Esys_PolicyPCR),
+ DLSYM_ARG(Esys_PolicySigned),
DLSYM_ARG(Esys_ReadPublic),
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
DLSYM_ARG(Esys_TR_Serialize),
DLSYM_ARG(Esys_TR_SetAuth),
DLSYM_ARG(Esys_TRSess_GetAttributes),
+ DLSYM_ARG(Esys_TRSess_GetNonceTPM),
DLSYM_ARG(Esys_TRSess_SetAttributes),
DLSYM_ARG(Esys_Unseal),
DLSYM_ARG(Esys_VerifySignature));
context->tcti_dl = dlopen(fn, RTLD_NOW);
if (!context->tcti_dl)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror());
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to load %s: %s", fn, dlerror());
log_debug("Loaded '%s' via dlopen()", fn);
log_debug("Loaded TCTI module '%s' (%s) [Version %" PRIu32 "]", info->name, info->description, info->version);
- rc = info->init(NULL, &sz, NULL);
+ rc = info->init(/* context= */ NULL, &sz, /* param= */ NULL);
if (rc != TPM2_RC_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc));
/* We require AES and CFB support for session encryption. */
if (!tpm2_supports_alg(context, TPM2_ALG_AES))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES.");
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM does not support AES.");
if (!tpm2_supports_alg(context, TPM2_ALG_CFB))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support CFB.");
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM does not support CFB.");
if (!tpm2_supports_tpmt_sym_def(context, &SESSION_TEMPLATE_SYM_AES_128_CFB))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES-128-CFB.");
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM does not support AES-128-CFB.");
*ret_context = TAKE_PTR(context);
return 0;
}
+int tpm2_context_new_or_warn(const char *device, Tpm2Context **ret_context) {
+ int r;
+
+ assert(ret_context);
+
+ r = tpm2_context_new(device, ret_context);
+ if (r == -EOPNOTSUPP)
+ return log_error_errno(r, "TPM device not usable as it does not support the required functionality (AES-128-CFB missing?).");
+ if (r == -ENOPKG)
+ return log_error_errno(r, "TPM TCTI driver not available.");
+ if (r == -ENOENT)
+ return log_error_errno(r, "TPM device not found.");
+ if (r < 0)
+ return log_error_errno(r, "Failed to create TPM2 context: %m");
+
+ return 0;
+}
+
static void tpm2_handle_cleanup(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, bool flush) {
TSS2_RC rc;
#if HAVE_TSS2_ESYS3
/* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested
* hierarchy, older versions need TPM2_RH_* instead. */
- ESYS_TR_RH_OWNER,
+ private ? ESYS_TR_RH_NULL : ESYS_TR_RH_OWNER,
#else
- TPM2_RH_OWNER,
+ private ? TPM2_RH_NULL : TPM2_RH_OWNER,
#endif
&handle->esys_handle);
if (rc != TSS2_RC_SUCCESS)
log_debug("authValue ends in 0, trimming as required by the TPM2 specification Part 1 section 'HMAC Computation' authValue Note 2.");
}
-int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) {
+int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) {
TPM2B_AUTH auth = {};
int r;
CLEANUP_ERASE(auth);
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
if (r < 0)
return r;
*
* The handle must reference a key already present in the TPM. It may be either a public key only, or a
* public/private keypair. */
-static int tpm2_get_name(
+int tpm2_get_name(
Tpm2Context *c,
const Tpm2Handle *handle,
TPM2B_NAME **ret_name) {
return tpm2_get_policy_digest(c, session, ret_policy_digest);
}
+/* Extend 'digest' with the PolicySigned calculated hash. */
+int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) {
+ TPM2_CC command = TPM2_CC_PolicySigned;
+ TSS2_RC rc;
+ int r;
+
+ assert(digest);
+ assert(digest->size == SHA256_DIGEST_SIZE);
+ assert(name);
+
+ r = dlopen_tpm2();
+ if (r < 0)
+ return log_debug_errno(r, "TPM2 support not installed: %m");
+
+ uint8_t buf[sizeof(command)];
+ size_t offset = 0;
+
+ rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal PolicySigned command: %s", sym_Tss2_RC_Decode(rc));
+
+ if (offset != sizeof(command))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Offset 0x%zx wrong after marshalling PolicySigned command", offset);
+
+ struct iovec data[] = {
+ IOVEC_MAKE(buf, offset),
+ IOVEC_MAKE(name->name, name->size),
+ };
+
+ r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true);
+ if (r < 0)
+ return r;
+
+ const TPM2B_NONCE policyRef = {}; /* For now, we do not make use of the policyRef stuff */
+
+ r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, policyRef.buffer, policyRef.size, /* extend= */ true);
+ if (r < 0)
+ return r;
+
+ tpm2_log_debug_digest(digest, "PolicySigned calculated digest");
+
+ return 0;
+}
+
+int tpm2_policy_signed_hmac_sha256(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ const Tpm2Handle *hmac_key_handle,
+ const struct iovec *hmac_key,
+ TPM2B_DIGEST **ret_policy_digest) {
+
+#if HAVE_OPENSSL
+ TSS2_RC rc;
+ int r;
+
+ assert(c);
+ assert(session);
+ assert(hmac_key_handle);
+ assert(iovec_is_set(hmac_key));
+
+ /* This sends a TPM2_PolicySigned command to the tpm. As signature key we use an HMAC-SHA256 key
+ * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and
+ * referenced in hmac_key_handle. */
+
+ log_debug("Submitting PolicySigned policy for HMAC-SHA256.");
+
+ /* Acquire the nonce from the TPM that we shall sign */
+ _cleanup_(Esys_Freep) TPM2B_NONCE *nonce = NULL;
+ rc = sym_Esys_TRSess_GetNonceTPM(
+ c->esys_context,
+ session->esys_handle,
+ &nonce);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to determine NoneTPM of auth session: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ be32_t expiration = htobe64(0);
+ const TPM2B_DIGEST cpHashA = {}; /* For now, we do not make use of the cpHashA stuff */
+ const TPM2B_NONCE policyRef = {}; /* ditto, we do not bother with policyRef */
+
+ /* Put together the data to sign, as per TPM2 Spec Part 3, 23.3.1 */
+ struct iovec data_to_sign[] = {
+ IOVEC_MAKE(nonce->buffer, nonce->size),
+ IOVEC_MAKE(&expiration, sizeof(expiration)),
+ IOVEC_MAKE(cpHashA.buffer, cpHashA.size),
+ IOVEC_MAKE(policyRef.buffer, policyRef.size),
+ };
+
+ /* Now calculate the digest of the data we put together */
+ TPM2B_DIGEST digest_to_sign;
+ r = tpm2_digest_many(TPM2_ALG_SHA256, &digest_to_sign, data_to_sign, ELEMENTSOF(data_to_sign), /* extend= */ false);
+ if (r < 0)
+ return r;
+
+ unsigned char hmac_signature[SHA256_DIGEST_SIZE];
+ unsigned hmac_signature_size = sizeof(hmac_signature);
+
+ /* And sign this with our key */
+ if (!HMAC(EVP_sha256(),
+ hmac_key->iov_base,
+ hmac_key->iov_len,
+ digest_to_sign.buffer,
+ digest_to_sign.size,
+ hmac_signature,
+ &hmac_signature_size))
+ return -ENOTRECOVERABLE;
+
+ /* Now bring the signature into a format that the TPM understands */
+ TPMT_SIGNATURE sig = {
+ .sigAlg = TPM2_ALG_HMAC,
+ .signature.hmac.hashAlg = TPM2_ALG_SHA256,
+ };
+ assert(hmac_signature_size == sizeof(sig.signature.hmac.digest.sha256));
+ memcpy(sig.signature.hmac.digest.sha256, hmac_signature, hmac_signature_size);
+
+ /* And submit the whole shebang to the TPM */
+ rc = sym_Esys_PolicySigned(
+ c->esys_context,
+ hmac_key_handle->esys_handle,
+ session->esys_handle,
+ /* shandle1= */ ESYS_TR_NONE,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ nonce,
+ &cpHashA,
+ &policyRef,
+ expiration,
+ &sig,
+ /* timeout= */ NULL,
+ /* policyTicket= */ NULL);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to add PolicySigned policy to TPM: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ return tpm2_get_policy_digest(c, session, ret_policy_digest);
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
+}
+
int tpm2_calculate_policy_authorize_nv(
const TPM2B_NV_PUBLIC *public_info,
TPM2B_DIGEST *digest) {
static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_ECC_CURVE *ret) {
assert(ret);
- FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table))
+ FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table)
if (t->openssl_ecc_curve_id == openssl_ecc_curve_id) {
*ret = t->tpm2_ecc_curve_id;
return 0;
static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE tpm2_ecc_curve_id, int *ret) {
assert(ret);
- FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table))
+ FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table)
if (t->tpm2_ecc_curve_id == tpm2_ecc_curve_id) {
*ret = t->openssl_ecc_curve_id;
return 0;
TPM2B_AUTH auth = {};
if (pin) {
- r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth);
+ r = tpm2_auth_value_from_pin(parent->publicArea.nameAlg, pin, &auth);
if (r < 0)
return r;
}
CLEANUP_ERASE(hmac_sensitive);
if (pin) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth);
if (r < 0)
return r;
}
const Tpm2Handle *session,
TPM2_HANDLE requested_nv_index,
const TPM2B_DIGEST *write_policy,
- const char *pin,
- const TPM2B_AUTH *auth,
TPM2_HANDLE *ret_nv_index,
Tpm2Handle **ret_nv_handle,
TPM2B_NV_PUBLIC *ret_nv_public) {
int r;
assert(c);
- assert(pin || auth);
+
+ /* Allocates an nvindex to store a policy for use in PolicyAuthorizeNV in. This is where pcrlock then
+ * stores its predicted PCR policies in. If 'requested_nv_index' will try to allocate the specified
+ * nvindex, otherwise will find a free one, and use that. */
r = tpm2_handle_new(c, &new_handle);
if (r < 0)
new_handle->flush = false; /* This is a persistent NV index, don't flush hence */
- TPM2B_AUTH _auth = {};
- CLEANUP_ERASE(_auth);
-
- if (!auth) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &_auth);
- if (r < 0)
- return r;
-
- auth = &_auth;
- }
-
for (unsigned try = 0; try < 25U; try++) {
TPM2_HANDLE nv_index;
/* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
/* shandle2= */ ESYS_TR_NONE,
/* shandle3= */ ESYS_TR_NONE,
- auth,
+ /* auth= */ NULL,
&public_info,
&new_handle->esys_handle);
*ret = device_key_public;
return 0;
}
+
+int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret) {
+ int r;
+
+ assert(c);
+ assert(pin);
+ assert(ret);
+
+ log_debug("Converting PIN into TPM2 HMAC-SHA256 object.");
+
+ /* Load the PIN (which we have stored in the "auth" TPM2B_AUTH) into the TPM as an HMAC key so that
+ * we can use it in a TPM2_PolicySigned() to write to the nvindex. For that we'll prep a pair of
+ * TPM2B_PUBLIC and TPM2B_SENSITIVE that defines an HMAC-SHA256 keyed hash function, and initialize
+ * it based on on the provided PIN data. */
+
+ TPM2B_PUBLIC auth_hmac_public = {
+ .publicArea = {
+ .type = TPM2_ALG_KEYEDHASH,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_SIGN_ENCRYPT,
+ .parameters.keyedHashDetail.scheme = {
+ .scheme = TPM2_ALG_HMAC,
+ .details.hmac.hashAlg = TPM2_ALG_SHA256,
+ },
+ .unique.keyedHash.size = SHA256_DIGEST_SIZE,
+ },
+ };
+
+ TPM2B_SENSITIVE auth_hmac_private = {
+ .sensitiveArea = {
+ .sensitiveType = TPM2_ALG_KEYEDHASH,
+ .sensitive.bits.size = pin->size,
+ .seedValue.size = SHA256_DIGEST_SIZE,
+ },
+ };
+
+ /* Copy in the key data */
+ memcpy_safe(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, pin->buffer, pin->size);
+
+ /* NB: We initialize the seed of the TPMT_SENSITIVE structure to all zeroes, since we want a stable
+ * "name" of the PIN object */
+
+ /* Now calculate the "unique" field for the public area, based on the sensitive data, according to
+ * the algorithm in the TPM2 spec, part 1, Section 27.5.3.2 */
+ struct iovec sensitive_data[] = {
+ IOVEC_MAKE(auth_hmac_private.sensitiveArea.seedValue.buffer, auth_hmac_private.sensitiveArea.seedValue.size),
+ IOVEC_MAKE(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, auth_hmac_private.sensitiveArea.sensitive.bits.size),
+ };
+ r = tpm2_digest_many(
+ auth_hmac_public.publicArea.nameAlg,
+ &auth_hmac_public.publicArea.unique.keyedHash,
+ sensitive_data,
+ ELEMENTSOF(sensitive_data),
+ /* extend= */ false);
+ if (r < 0)
+ return r;
+
+ /* And now load the public/private parts into the TPM and get a handle back */
+ r = tpm2_load_external(
+ c,
+ session,
+ &auth_hmac_public,
+ &auth_hmac_private,
+ ret);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load PIN into TPM2: %m");
+
+ return 0;
+}
#endif
char *tpm2_pcr_mask_to_string(uint32_t mask) {
} Tpm2Context;
int tpm2_context_new(const char *device, Tpm2Context **ret_context);
+int tpm2_context_new_or_warn(const char *device, Tpm2Context **ret_context);
Tpm2Context *tpm2_context_ref(Tpm2Context *context);
Tpm2Context *tpm2_context_unref(Tpm2Context *context);
DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref);
int tpm2_unmarshal_nv_public(const void *data, size_t size, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size);
int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private, TPM2B_ENCRYPTED_SECRET *ret_seed);
+int tpm2_get_name(Tpm2Context *c, const Tpm2Handle *handle, TPM2B_NAME **ret_name);
bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg);
bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command);
int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values);
int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values);
-int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth);
+int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth);
int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin);
int tpm2_set_auth_binary(Tpm2Context *c, const Tpm2Handle *handle, const TPM2B_AUTH *auth);
int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_super_pcr(Tpm2Context *c, const Tpm2Handle *session, const Tpm2PCRPrediction *prediction, uint16_t algorithm);
+int tpm2_policy_signed_hmac_sha256(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *hmac_key_handle, const struct iovec *hmac_key, TPM2B_DIGEST **ret_policy_digest);
int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name);
int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name);
int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
+int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name);
int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest);
int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent);
int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret);
int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fingerprint, size_t *ret_fingerprint_size);
-int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, const char *pin, const TPM2B_AUTH *auth, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
+int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest);
int tpm2_undefine_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle);
int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret);
+int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret);
+
/* The tpm2-tss library has many structs that are simply a combination of an array (or object) and
* size. These macros allow easily initializing or assigning instances of such structs from an existing
* buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the
if (!error)
return 0;
- FOREACH_ARRAY(t, table, ELEMENTSOF(table))
+ FOREACH_ELEMENT(t, table)
if (streq(error, t->error))
return t->value;
if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */
return -ENOEXEC;
+ if (strv_length(filter->suffix) > 1) /* suffix is not deterministic? */
+ return -ENOEXEC;
/* The format for names we match goes like this:
*
return -ENOMEM;
}
- if (filter->suffix && !strextend(&fn, filter->suffix))
- return -ENOMEM;
+ if (!strv_isempty(filter->suffix))
+ if (!strextend(&fn, filter->suffix[0]))
+ return -ENOMEM;
if (!filename_is_valid(fn))
return -EINVAL;
if (!result.path)
return log_oom_debug();
- if (filter->version) {
- result.version = strdup(filter->version);
- if (!result.version)
- return log_oom_debug();
- }
+ r = strdup_to(&result.version, filter->version);
+ if (r < 0)
+ return r;
*ret = TAKE_PICK_RESULT(result);
return 1;
return log_oom_debug();
r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd);
- if (r < 0) {
- if (r != -ENOENT)
- return log_debug_errno(r, "Failed to open '%s': %m", prefix_roota(toplevel_path, p));
-
+ if (r == -ENOENT) {
*ret = PICK_RESULT_NULL;
return 0;
}
+ if (r < 0)
+ return log_debug_errno(r, "Failed to open '%s': %m", prefix_roota(toplevel_path, p));
return pin_choice(
toplevel_path,
} else
e = dname;
- if (!isempty(filter->suffix)) {
- char *sfx = endswith(e, filter->suffix);
+ if (!strv_isempty(filter->suffix)) {
+ char *sfx = endswith_strv(e, filter->suffix);
if (!sfx)
continue;
PickResult *ret) {
_cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL;
- const char *filter_suffix, *enumeration_path;
+ char * const *filter_suffix_strv = NULL;
+ const char *filter_suffix = NULL, *enumeration_path;
uint32_t filter_type_mask;
int r;
if (!filter_bname)
return -ENOMEM;
- if (filter->suffix) {
- /* Chop off suffix, if specified */
- char *f = endswith(filter_bname, filter->suffix);
- if (f)
- *f = 0;
- }
+ /* Chop off suffix, if specified */
+ char *f = endswith_strv(filter_bname, filter->suffix);
+ if (f)
+ *f = 0;
- filter_suffix = filter->suffix;
+ filter_suffix_strv = filter->suffix;
filter_type_mask = filter->type_mask;
enumeration_path = path;
.basename = filter_bname,
.version = filter->version,
.architecture = filter->architecture,
- .suffix = filter_suffix,
+ .suffix = filter_suffix_strv ?: STRV_MAKE(filter_suffix),
},
flags,
ret);
const PickFilter pick_filter_image_raw = {
.type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
.architecture = _ARCHITECTURE_INVALID,
- .suffix = ".raw",
+ .suffix = STRV_MAKE(".raw"),
};
const PickFilter pick_filter_image_dir = {
.type_mask = UINT32_C(1) << DT_DIR,
.architecture = _ARCHITECTURE_INVALID,
};
+
+const PickFilter pick_filter_image_any = {
+ .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) | (UINT32_C(1) << DT_DIR),
+ .architecture = _ARCHITECTURE_INVALID,
+ .suffix = STRV_MAKE(".raw", ""),
+};
const char *basename; /* Can be overridden by search pattern */
const char *version;
Architecture architecture;
- const char *suffix; /* Can be overridden by search pattern */
+ char * const *suffix; /* Can be overridden by search pattern */
} PickFilter;
typedef struct PickResult {
extern const PickFilter pick_filter_image_raw;
extern const PickFilter pick_filter_image_dir;
+extern const PickFilter pick_filter_image_any;
#include "process-util.h"
#include "rm-rf.h"
#include "sort-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "terminal-util.h"
#include "user-util.h"
#include "verbs.h"
typedef enum MutableMode {
- MUTABLE_YES,
MUTABLE_NO,
+ MUTABLE_YES,
MUTABLE_AUTO,
MUTABLE_IMPORT,
MUTABLE_EPHEMERAL,
_MUTABLE_INVALID = -EINVAL,
} MutableMode;
+static const char* const mutable_mode_table[_MUTABLE_MAX] = {
+ [MUTABLE_NO] = "no",
+ [MUTABLE_YES] = "yes",
+ [MUTABLE_AUTO] = "auto",
+ [MUTABLE_IMPORT] = "import",
+ [MUTABLE_EPHEMERAL] = "ephemeral",
+ [MUTABLE_EPHEMERAL_IMPORT] = "ephemeral-import",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(mutable_mode, MutableMode, MUTABLE_YES);
+
static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
static char *arg_root = NULL;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
static ImageClass arg_image_class = IMAGE_SYSEXT;
-static const char *mutable_extensions_base_dir = "/var/lib/extensions.mutable";
+#define MUTABLE_EXTENSIONS_BASE_DIR "/var/lib/extensions.mutable"
STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
};
static int parse_mutable_mode(const char *p) {
- int r;
-
- assert(p);
-
- if (streq(p, "auto"))
- return MUTABLE_AUTO;
-
- if (streq(p, "import"))
- return MUTABLE_IMPORT;
-
- if (streq(p, "ephemeral"))
- return MUTABLE_EPHEMERAL;
-
- if (streq(p, "ephemeral-import"))
- return MUTABLE_EPHEMERAL_IMPORT;
-
- r = parse_boolean(p);
- if (r < 0)
- return r;
-
- return r ? MUTABLE_YES : MUTABLE_NO;
+ return mutable_mode_from_string(p);
}
static int is_our_mount_point(
return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[image_class].dot_directory_name, p);
if (lstat(p, &st) < 0)
- return log_error_errno(r, "Failed to stat %s: %m", p);
+ return log_error_errno(errno, "Failed to stat %s: %m", p);
if (st.st_dev != dev) {
log_debug("Hierarchy '%s' reports a different device major/minor than what we are seeing, assuming offline copy.", p);
return log_oom();
if (stat(*p, &st) < 0)
- return log_error_errno(r, "Failed to stat() '%s': %m", *p);
+ return log_error_errno(errno, "Failed to stat() '%s': %m", *p);
r = table_add_many(
t,
assert(path1);
assert(path2);
- if (stat(path1, &st1))
+ if (stat(path1, &st1) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", path1);
- if (stat(path2, &st2))
+ if (stat(path2, &st2) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", path2);
return st1.st_dev == st2.st_dev;
typedef struct OverlayFSPaths {
char *hierarchy;
+ mode_t hierarchy_mode;
char *resolved_hierarchy;
char *resolved_mutable_directory;
return 0;
}
+static int mutable_directory_mode_matches_hierarchy(
+ const char *root_or_null,
+ const char *path,
+ mode_t hierarchy_mode) {
+
+ _cleanup_free_ char *path_in_root = NULL;
+ struct stat st;
+ mode_t actual_mode;
+
+ assert(path);
+
+ path_in_root = path_join(root_or_null, path);
+ if (!path_in_root)
+ return log_oom();
+
+ if (stat(path_in_root, &st) < 0) {
+ if (errno == ENOENT)
+ return 0;
+ return log_error_errno(errno, "Failed to stat mutable directory '%s': %m", path_in_root);
+ }
+
+ actual_mode = st.st_mode & 0777;
+ if (actual_mode != hierarchy_mode)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mutable directory '%s' has mode %04o, ought to have mode %04o", path_in_root, actual_mode, hierarchy_mode);
+
+ return 0;
+}
+
static int resolve_mutable_directory(
const char *hierarchy,
+ mode_t hierarchy_mode,
const char *workspace,
char **ret_resolved_mutable_directory) {
_cleanup_free_ char *path = NULL, *resolved_path = NULL, *dir_name = NULL;
- const char *root = arg_root, *base = mutable_extensions_base_dir;
+ const char *root = arg_root, *base = MUTABLE_EXTENSIONS_BASE_DIR;
int r;
assert(hierarchy);
if (!path)
return log_oom();
+ if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_AUTO)) {
+ /* If there already is a mutable directory, check if its mode matches hierarchy. Merged
+ * hierarchy will have the same mode as the mutable directory, so we want no surprising mode
+ * changes here. */
+ r = mutable_directory_mode_matches_hierarchy(root, path, hierarchy_mode);
+ if (r < 0)
+ return r;
+ }
+
if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) {
_cleanup_free_ char *path_in_root = NULL;
static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path, OverlayFSPaths **ret_op) {
_cleanup_free_ char *hierarchy_copy = NULL, *resolved_hierarchy = NULL, *resolved_mutable_directory = NULL;
+ mode_t hierarchy_mode;
+
int r;
assert (hierarchy);
r = resolve_hierarchy(hierarchy, &resolved_hierarchy);
if (r < 0)
return r;
- r = resolve_mutable_directory(hierarchy, workspace_path, &resolved_mutable_directory);
+
+ if (resolved_hierarchy) {
+ struct stat st;
+
+ if (stat(resolved_hierarchy, &st) < 0)
+ return log_error_errno(errno, "Failed to stat '%s': %m", resolved_hierarchy);
+ hierarchy_mode = st.st_mode & 0777;
+ } else
+ hierarchy_mode = 0755;
+
+ r = resolve_mutable_directory(hierarchy, hierarchy_mode, workspace_path, &resolved_mutable_directory);
if (r < 0)
return r;
*op = (OverlayFSPaths) {
.hierarchy = TAKE_PTR(hierarchy_copy),
+ .hierarchy_mode = hierarchy_mode,
.resolved_hierarchy = TAKE_PTR(resolved_hierarchy),
.resolved_mutable_directory = TAKE_PTR(resolved_mutable_directory),
};
return 0;
}
-static int resolved_paths_equal(const char *resolved_a, const char *resolved_b) {
- /* Returns true if paths are of the same entry, false if not, <0 on error. */
+static int determine_used_extensions(const char *hierarchy, char **paths, char ***ret_used_paths, size_t *ret_extensions_used) {
+ _cleanup_strv_free_ char **used_paths = NULL;
+ size_t n = 0;
+ int r;
+
+ assert(hierarchy);
+ assert(paths);
+ assert(ret_used_paths);
+ assert(ret_extensions_used);
- if (path_equal(resolved_a, resolved_b))
- return 1;
+ STRV_FOREACH(p, paths) {
+ _cleanup_free_ char *resolved = NULL;
- if (!resolved_a || !resolved_b)
- return 0;
+ r = chase(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
+ if (r == -ENOENT) {
+ log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy, *p);
+ continue;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy, *p);
+
+ r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
+ if (r > 0) {
+ log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", hierarchy, *p);
+ continue;
+ }
+
+ r = strv_consume_with_size (&used_paths, &n, TAKE_PTR(resolved));
+ if (r < 0)
+ return log_oom();
+ }
- return inode_same(resolved_a, resolved_b, 0);
+ *ret_used_paths = TAKE_PTR(used_paths);
+ *ret_extensions_used = n;
+ return 0;
}
static int maybe_import_mutable_directory(OverlayFSPaths *op) {
if (arg_mutable != MUTABLE_IMPORT || !op->resolved_mutable_directory)
return 0;
- r = resolved_paths_equal(op->resolved_hierarchy, op->resolved_mutable_directory);
+ r = path_equal_or_inode_same_full(op->resolved_hierarchy, op->resolved_mutable_directory, 0);
if (r < 0)
return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
if (r > 0)
if (!dir_name)
return log_oom();
- path = path_join(mutable_extensions_base_dir, dir_name);
+ path = path_join(MUTABLE_EXTENSIONS_BASE_DIR, dir_name);
if (!path)
return log_oom();
r = chase(path, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
- if (r < 0 && r != -ENOENT)
+ if (r == -ENOENT) {
+ log_debug("Mutable directory for %s does not exist, not importing", op->hierarchy);
+ return 0;
+ }
+ if (r < 0)
return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path);
- r = resolved_paths_equal(op->resolved_hierarchy, resolved_path);
+ r = path_equal_or_inode_same_full(op->resolved_hierarchy, resolved_path, 0);
if (r < 0)
return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
return 0;
}
-static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths, size_t *ret_extensions_used) {
- size_t n = 0;
+static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths) {
int r;
assert(op);
assert(paths);
- assert(ret_extensions_used);
-
- /* Put the extensions in the middle */
- STRV_FOREACH(p, paths) {
- _cleanup_free_ char *resolved = NULL;
-
- r = chase(op->hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
- if (r == -ENOENT) {
- log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", op->hierarchy, *p);
- continue;
- }
- if (r < 0)
- return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", op->hierarchy, *p);
- r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false);
- if (r < 0)
- return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p);
- if (r > 0) {
- log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", op->hierarchy, *p);
- continue;
- }
-
- r = strv_consume(&op->lower_dirs, TAKE_PTR(resolved));
- if (r < 0)
- return log_oom();
- ++n;
- }
+ /* The paths were already determined in determine_used_extensions, so we just take them as is. */
+ r = strv_extend_strv(&op->lower_dirs, paths, false);
+ if (r < 0)
+ return log_oom ();
- *ret_extensions_used = n;
return 0;
}
return 0;
}
- r = resolved_paths_equal(op->resolved_hierarchy, op->resolved_mutable_directory);
+ r = path_equal_or_inode_same_full(op->resolved_hierarchy, op->resolved_mutable_directory, 0);
if (r < 0)
return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
if (r > 0) {
static int determine_lower_dirs(
OverlayFSPaths *op,
char **paths,
- const char *meta_path,
- size_t *ret_extensions_used) {
+ const char *meta_path) {
int r;
assert(op);
assert(paths);
assert(meta_path);
- assert(ret_extensions_used);
r = determine_top_lower_dirs(op, meta_path);
if (r < 0)
return r;
- r = determine_middle_lower_dirs(op, paths, ret_extensions_used);
+ r = determine_middle_lower_dirs(op, paths);
if (r < 0)
return r;
const char *meta_path) {
int r;
+ const char *top_layer = NULL;
assert(op);
assert(overlay_path);
r = mkdir_p(op->work_dir, 0700);
if (r < 0)
return log_error_errno(r, "Failed to make directory '%s': %m", op->work_dir);
+ top_layer = op->upper_dir;
+ } else {
+ assert(!strv_isempty(op->lower_dirs));
+ top_layer = op->lower_dirs[0];
}
+ /* Overlayfs merged directory has the same mode as the top layer (either first lowerdir in options in
+ * read-only case, or upperdir for mutable case. Set up top overlayfs layer to the same mode as the
+ * unmerged hierarchy, otherwise we might end up with merged hierarchy owned by root and with mode
+ * being 0700. */
+ if (chmod(top_layer, op->hierarchy_mode) < 0)
+ return log_error_errno(errno, "Failed to set permissions of '%s' to %04o: %m", top_layer, op->hierarchy_mode);
+
r = mount_overlayfs(image_class, noexec, overlay_path, op->lower_dirs, op->upper_dir, op->work_dir);
if (r < 0)
return r;
const char *workspace_path) {
_cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL;
+ _cleanup_strv_free_ char **used_paths = NULL;
size_t extensions_used = 0;
int r;
assert(overlay_path);
assert(workspace_path);
- r = overlayfs_paths_new(hierarchy, workspace_path, &op);
+ r = determine_used_extensions(hierarchy, paths, &used_paths, &extensions_used);
if (r < 0)
return r;
- r = determine_lower_dirs(op, paths, meta_path, &extensions_used);
+ if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */
+ return 0;
+
+ r = overlayfs_paths_new(hierarchy, workspace_path, &op);
if (r < 0)
return r;
- if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */
- return 0;
+ r = determine_lower_dirs(op, used_paths, meta_path);
+ if (r < 0)
+ return r;
r = determine_upper_dir(op);
if (r < 0)
if (r == 123) /* exit code 123 means: didn't do anything */
return 0;
if (r > 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Failed to merge hierarchies");
+ return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to merge hierarchies");
r = need_reload(image_class, hierarchies, no_reload);
if (r < 0)
}
static int run(int argc, char *argv[]) {
- const char* env_var;
+ const char *env_var;
int r;
log_setup();
uint64_t runtime_max_sec;
+ sd_id128_t invocation_id;
+
bool need_daemon_reload;
bool transient;
} else
printf("\n");
+ if (!sd_id128_is_null(i->invocation_id))
+ printf(" Invocation: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(i->invocation_id));
+
STRV_FOREACH(t, i->triggered_by) {
UnitActiveState state = _UNIT_ACTIVE_STATE_INVALID;
{ "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitStatusInfo, inactive_exit_timestamp_monotonic) },
{ "ActiveEnterTimestamp", "t", NULL, offsetof(UnitStatusInfo, active_enter_timestamp) },
{ "ActiveExitTimestamp", "t", NULL, offsetof(UnitStatusInfo, active_exit_timestamp) },
- { "RuntimeMaxUSec", "t", NULL, offsetof(UnitStatusInfo, runtime_max_sec) },
{ "InactiveEnterTimestamp", "t", NULL, offsetof(UnitStatusInfo, inactive_enter_timestamp) },
+ { "RuntimeMaxUSec", "t", NULL, offsetof(UnitStatusInfo, runtime_max_sec) },
+ { "InvocationID", "s", bus_map_id128, offsetof(UnitStatusInfo, invocation_id) },
{ "NeedDaemonReload", "b", NULL, offsetof(UnitStatusInfo, need_daemon_reload) },
{ "Transient", "b", NULL, offsetof(UnitStatusInfo, transient) },
{ "ExecMainPID", "u", NULL, offsetof(UnitStatusInfo, main_pid) },
{ "force-reload", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */
};
-static const char *verb_to_method(const char *verb) {
- for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++)
- if (streq_ptr(unit_actions[i].verb, verb))
- return unit_actions[i].method;
+static const char* verb_to_method(const char *verb) {
+ assert(verb);
- return "StartUnit";
+ FOREACH_ELEMENT(i, unit_actions)
+ if (streq(i->verb, verb))
+ return i->method;
+
+ return "StartUnit";
}
-static const char *verb_to_job_type(const char *verb) {
- for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++)
- if (streq_ptr(unit_actions[i].verb, verb))
- return unit_actions[i].job_type;
+static const char* verb_to_job_type(const char *verb) {
+ assert(verb);
+
+ FOREACH_ELEMENT(i, unit_actions)
+ if (streq(i->verb, verb))
+ return i->job_type;
- return "start";
+ return "start";
}
static int start_unit_one(
};
enum action verb_to_action(const char *verb) {
+ assert(verb);
+
for (enum action i = 0; i < _ACTION_MAX; i++)
if (streq_ptr(action_table[i].verb, verb))
return i;
}
if (arg_wait) {
- r = bus_call_method_async(bus, NULL, bus_systemd_mgr, "Subscribe", NULL, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to enable subscription: %m");
-
r = bus_wait_for_units_new(bus, &wu);
if (r < 0)
return log_error_errno(r, "Failed to allocate unit watch context: %m");
}
if (!arg_no_block) {
- const char* extra_args[4];
+ const char *extra_args[4];
WaitJobsFlags flags = 0;
SET_FLAG(flags, BUS_WAIT_JOBS_LOG_ERROR, !arg_quiet);
}
void release_busses(void) {
- for (BusFocus w = 0; w < _BUS_FOCUS_MAX; w++)
- buses[w] = sd_bus_flush_close_unref(buses[w]);
+ FOREACH_ARRAY(w, buses, _BUS_FOCUS_MAX)
+ *w = sd_bus_flush_close_unref(*w);
}
void ask_password_agent_open_maybe(void) {
int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags);
#endif
int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret);
+int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret);
int sd_event_source_set_memory_pressure_type(sd_event_source *e, const char *ty);
int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec);
int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback);
int sd_id128_get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret);
int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret);
int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret);
+int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret);
#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
{ .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \
int sd_radv_start(sd_radv *ra);
int sd_radv_stop(sd_radv *ra);
int sd_radv_is_running(sd_radv *ra);
+int sd_radv_send(sd_radv *ra);
int sd_radv_set_ifindex(sd_radv *ra, int interface_index);
int sd_radv_set_ifname(sd_radv *ra, const char *interface_name);
int sd_radv_get_ifname(sd_radv *ra, const char **ret);
+int sd_radv_set_link_local_address(sd_radv *ra, const struct in6_addr *addr);
int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr);
int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu);
int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit);
int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec);
int sd_radv_set_router_lifetime(sd_radv *ra, uint64_t usec);
-int sd_radv_set_managed_information(sd_radv *ra, int managed);
-int sd_radv_set_other_information(sd_radv *ra, int other);
-int sd_radv_set_preference(sd_radv *ra, unsigned preference);
+int sd_radv_set_managed_information(sd_radv *ra, int b);
+int sd_radv_set_other_information(sd_radv *ra, int b);
+int sd_radv_set_preference(sd_radv *ra, uint8_t preference);
int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p);
int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p);
int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p);
if (r == -ENOMEDIUM)
return log_tests_skipped("cgroupfs not available");
+ r = find_executable("ping", NULL);
+ if (r < 0)
+ return log_tests_skipped_errno(r, "Can't find ping binary: %m");
+
_cleanup_free_ char *unit_dir = NULL;
ASSERT_OK(get_testdata_dir("units", &unit_dir));
ASSERT_OK(set_unit_path(unit_dir));
ASSERT_OK(manager_startup(m, NULL, NULL, NULL));
/* We need to enable access to the filesystem where the binary is so we
- * add @common-block */
- ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("@common-block")), 0);
- ASSERT_OK(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block")));
- ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "~tracefs")), 0);
- ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("@common-block")), 0);
- ASSERT_OK(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("debugfs", "@common-block")));
+ * add @common-block and @application */
+ ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("@common-block", "@application")), 0);
+ ASSERT_OK(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "@application")));
+ ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "@application", "~tracefs")), 0);
+ ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("@common-block", "@application")), 0);
+ ASSERT_OK(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("debugfs", "@common-block", "@application")));
ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("~debugfs")), 0);
return 0;
};
/* Generates the right <match/> expressions for these credentials according to the shared mime-info spec */
- FOREACH_ARRAY(t, tags, ELEMENTSOF(tags)) {
+ FOREACH_ELEMENT(t, tags) {
_cleanup_free_ char *encoded = NULL;
assert_se(base64mem(t, sizeof(sd_id128_t), &encoded) >= 0);
usec_t ts;
usec_t timeout = 2 * USEC_PER_MINUTE;
- assert_se(m);
- assert_se(unit);
+ ASSERT_NOT_NULL(m);
+ ASSERT_NOT_NULL(unit);
/* Bump the timeout when running in plain QEMU, as some more involved tests might start hitting the
* default 2m timeout (like exec-dynamicuser-statedir.service) */
usec_t n;
r = sd_event_run(m->event, 100 * USEC_PER_MSEC);
- assert_se(r >= 0);
+ ASSERT_OK(r);
n = now(CLOCK_MONOTONIC);
if (ts + timeout < n) {
Manager *m, Unit *unit, int status_expected, int code_expected) {
Service *service = NULL;
- assert_se(m);
- assert_se(unit);
+ ASSERT_NOT_NULL(m);
+ ASSERT_NOT_NULL(unit);
wait_for_service_finish(m, unit);
Manager *m, Unit *unit, ServiceResult result_expected) {
Service *service = NULL;
- assert_se(m);
- assert_se(unit);
+ ASSERT_NOT_NULL(m);
+ ASSERT_NOT_NULL(unit);
wait_for_service_finish(m, unit);
struct passwd *p;
struct group *g;
- assert_se(name);
+ ASSERT_NOT_NULL(name);
p = getpwnam(name);
if (!p ||
if (slice) {
start_parent_slices(slice);
int r = unit_start(slice, NULL);
- assert_se(r >= 0 || r == -EALREADY);
+ if (r != -EALREADY)
+ ASSERT_OK(r);
}
}
FORK_CLOSE_ALL_FDS |
FORK_DEATHSIG_SIGKILL,
&pid);
- assert(r >= 0);
+ ASSERT_OK(r);
if (r == 0) {
/* Keep CAP_SYS_ADMIN if we have it to ensure we give an
* accurate result to the caller. Some kernels have a
Manager *m, const char *unit_name, int status_expected, int code_expected) {
Unit *unit;
- assert_se(unit_name);
+ ASSERT_NOT_NULL(unit_name);
- assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0);
+ ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit));
/* We need to start the slices as well otherwise the slice cgroups might be pruned
* in on_cgroup_empty_event. */
start_parent_slices(unit);
- assert_se(unit_start(unit, NULL) >= 0);
+ ASSERT_OK(unit_start(unit, NULL));
check_main_result(file, line, func, m, unit, status_expected, code_expected);
++n_ran_tests;
Manager *m, const char *unit_name, ServiceResult result_expected) {
Unit *unit;
- assert_se(unit_name);
+ ASSERT_NOT_NULL(unit_name);
- assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0);
- assert_se(unit_start(unit, NULL) >= 0);
+ ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit));
+ ASSERT_OK(unit_start(unit, NULL));
check_service_result(file, line, func, m, unit, result_expected);
}
#define test_service(m, unit_name, result_expected) \
_test_service(PROJECT_FILE, __LINE__, __func__, m, unit_name, result_expected)
static void test_exec_bindpaths(Manager *m) {
- assert_se(mkdir_p("/tmp/test-exec-bindpaths", 0755) >= 0);
- assert_se(mkdir_p("/tmp/test-exec-bindreadonlypaths", 0755) >= 0);
+ ASSERT_OK(mkdir_p("/tmp/test-exec-bindpaths", 0755));
+ ASSERT_OK(mkdir_p("/tmp/test-exec-bindreadonlypaths", 0755));
test(m, "exec-bindpaths.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED);
static void test_exec_cpuaffinity(Manager *m) {
_cleanup_(cpu_set_reset) CPUSet c = {};
- assert_se(cpu_set_realloc(&c, 8192) >= 0); /* just allocate the maximum possible size */
- assert_se(sched_getaffinity(0, c.allocated, c.set) >= 0);
+ ASSERT_OK(cpu_set_realloc(&c, 8192)); /* just allocate the maximum possible size */
+ ASSERT_OK_ERRNO(sched_getaffinity(0, c.allocated, c.set));
if (!CPU_ISSET_S(0, c.allocated, c.set)) {
log_notice("Cannot use CPU 0, skipping %s", __func__);
}
static void test_exec_workingdirectory(Manager *m) {
- assert_se(mkdir_p("/tmp/test-exec_workingdirectory", 0755) >= 0);
+ ASSERT_OK(mkdir_p("/tmp/test-exec_workingdirectory", 0755));
test(m, "exec-workingdirectory.service", 0, CLD_EXITED);
test(m, "exec-workingdirectory-trailing-dot.service", 0, CLD_EXITED);
}
static void test_exec_execsearchpath(Manager *m) {
- assert_se(mkdir_p("/tmp/test-exec_execsearchpath", 0755) >= 0);
+ ASSERT_OK(mkdir_p("/tmp/test-exec_execsearchpath", 0755));
- assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, COPY_REPLACE) >= 0);
+ ASSERT_OK(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, COPY_REPLACE));
test(m, "exec-execsearchpath.service", 0, CLD_EXITED);
- assert_se(rm_rf("/tmp/test-exec_execsearchpath", REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+ ASSERT_OK(rm_rf("/tmp/test-exec_execsearchpath", REMOVE_ROOT|REMOVE_PHYSICAL));
test(m, "exec-execsearchpath.service", EXIT_EXEC, CLD_EXITED);
}
int r;
r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile.conf", path_not_set, WRITE_STRING_FILE_CREATE);
-
- assert_se(r == 0);
+ ASSERT_OK(r);
test(m, "exec-execsearchpath-environmentfile.service", 0, CLD_EXITED);
r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile-set.conf", path_set, WRITE_STRING_FILE_CREATE);
-
- assert_se(r == 0);
+ ASSERT_OK(r);
test(m, "exec-execsearchpath-environmentfile-set.service", 0, CLD_EXITED);
}
static void test_exec_execsearchpath_passenvironment(Manager *m) {
- assert_se(setenv("VAR1", "word1 word2", 1) == 0);
- assert_se(setenv("VAR2", "word3", 1) == 0);
- assert_se(setenv("VAR3", "$word 5 6", 1) == 0);
- assert_se(setenv("VAR4", "new\nline", 1) == 0);
- assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0);
+ ASSERT_OK_ERRNO(setenv("VAR1", "word1 word2", 1));
+ ASSERT_OK_ERRNO(setenv("VAR2", "word3", 1));
+ ASSERT_OK_ERRNO(setenv("VAR3", "$word 5 6", 1));
+ ASSERT_OK_ERRNO(setenv("VAR4", "new\nline", 1));
+ ASSERT_OK_ERRNO(setenv("VAR5", "passwordwithbackslashes", 1));
test(m, "exec-execsearchpath-passenvironment.service", 0, CLD_EXITED);
- assert_se(setenv("PATH", "/usr", 1) == 0);
+ ASSERT_OK_ERRNO(setenv("PATH", "/usr", 1));
test(m, "exec-execsearchpath-passenvironment-set.service", 0, CLD_EXITED);
- assert_se(unsetenv("VAR1") == 0);
- assert_se(unsetenv("VAR2") == 0);
- assert_se(unsetenv("VAR3") == 0);
- assert_se(unsetenv("VAR4") == 0);
- assert_se(unsetenv("VAR5") == 0);
- assert_se(unsetenv("PATH") == 0);
+ ASSERT_OK_ERRNO(unsetenv("VAR1"));
+ ASSERT_OK_ERRNO(unsetenv("VAR2"));
+ ASSERT_OK_ERRNO(unsetenv("VAR3"));
+ ASSERT_OK_ERRNO(unsetenv("VAR4"));
+ ASSERT_OK_ERRNO(unsetenv("VAR5"));
+ ASSERT_OK_ERRNO(unsetenv("PATH"));
}
static void test_exec_personality(Manager *m) {
}
static void test_exec_privatetmp(Manager *m) {
- assert_se(touch("/tmp/test-exec_privatetmp") >= 0);
+ ASSERT_OK(touch("/tmp/test-exec_privatetmp"));
if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) {
test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
char buf[4096];
ssize_t l;
- assert_se(s);
- assert_se(fd >= 0);
+ ASSERT_NOT_NULL(s);
+ ASSERT_GT(fd, 0);
l = read(fd, buf, sizeof(buf) - 1);
if (l < 0) {
buf[l] = '\0';
if (result)
- assert_se(strextend(result, buf));
+ ASSERT_NOT_NULL(strextend(result, buf));
else
log_error("ldd: %s", buf);
reenable:
/* Re-enable the event source if we did not encounter EOF */
- assert_se(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT) >= 0);
+ ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT));
return 0;
}
static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
pid_t *pid = userdata;
- assert_se(pid);
+ ASSERT_NOT_NULL(pid);
(void) kill(*pid, SIGKILL);
static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) {
int ret = -EIO;
- assert_se(si);
+ ASSERT_NOT_NULL(si);
if (si->si_code == CLD_EXITED)
ret = si->si_status;
pid_t pid;
int r;
- assert_se(exec);
- assert_se(ret);
+ ASSERT_NOT_NULL(exec);
+ ASSERT_NOT_NULL(ret);
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
+ ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD));
- assert_se(pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) == 0);
- assert_se(pipe2(errpipe, O_NONBLOCK|O_CLOEXEC) == 0);
+ ASSERT_OK_ERRNO(pipe2(outpipe, O_NONBLOCK|O_CLOEXEC));
+ ASSERT_OK_ERRNO(pipe2(errpipe, O_NONBLOCK|O_CLOEXEC));
r = safe_fork_full("(spawn-ldd)",
(int[]) { -EBADF, outpipe[1], errpipe[1] },
NULL, 0,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, &pid);
- assert_se(r >= 0);
+ ASSERT_OK(r);
if (r == 0) {
execlp("ldd", "ldd", exec, NULL);
_exit(EXIT_FAILURE);
outpipe[1] = safe_close(outpipe[1]);
errpipe[1] = safe_close(errpipe[1]);
- assert_se(sd_event_new(&e) >= 0);
+ ASSERT_OK(sd_event_new(&e));
- assert_se(sd_event_add_time_relative(e, NULL, CLOCK_MONOTONIC,
- 10 * USEC_PER_SEC, USEC_PER_SEC, on_spawn_timeout, &pid) >= 0);
- assert_se(sd_event_add_io(e, &stdout_source, outpipe[0], EPOLLIN, on_spawn_io, &result) >= 0);
- assert_se(sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT) >= 0);
- assert_se(sd_event_add_io(e, &stderr_source, errpipe[0], EPOLLIN, on_spawn_io, NULL) >= 0);
- assert_se(sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT) >= 0);
- assert_se(sd_event_add_child(e, &sigchld_source, pid, WEXITED, on_spawn_sigchld, NULL) >= 0);
+ ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_MONOTONIC,
+ 10 * USEC_PER_SEC, USEC_PER_SEC, on_spawn_timeout, &pid));
+ ASSERT_OK(sd_event_add_io(e, &stdout_source, outpipe[0], EPOLLIN, on_spawn_io, &result));
+ ASSERT_OK(sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT));
+ ASSERT_OK(sd_event_add_io(e, &stderr_source, errpipe[0], EPOLLIN, on_spawn_io, NULL));
+ ASSERT_OK(sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT));
+ ASSERT_OK(sd_event_add_child(e, &sigchld_source, pid, WEXITED, on_spawn_sigchld, NULL));
/* SIGCHLD should be processed after IO is complete */
- assert_se(sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1) >= 0);
+ ASSERT_OK(sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1));
- assert_se(sd_event_loop(e) >= 0);
+ ASSERT_OK(sd_event_loop(e));
_cleanup_strv_free_ char **v = NULL;
- assert_se(strv_split_newlines_full(&v, result, 0) >= 0);
+ ASSERT_OK(strv_split_newlines_full(&v, result, 0));
STRV_FOREACH(q, v) {
_cleanup_free_ char *word = NULL;
const char *p = *q;
r = extract_first_word(&p, &word, NULL, 0);
- assert_se(r >= 0);
+ ASSERT_OK(r);
if (r == 0)
continue;
if (path_is_absolute(word)) {
- assert_se(strv_consume(&libraries, TAKE_PTR(word)) >= 0);
+ ASSERT_OK(strv_consume(&libraries, TAKE_PTR(word)));
continue;
}
word = mfree(word);
r = extract_first_word(&p, &word, NULL, 0);
- assert_se(r >= 0);
+ ASSERT_OK(r);
if (r == 0)
continue;
word = mfree(word);
r = extract_first_word(&p, &word, NULL, 0);
- assert_se(r >= 0);
+ ASSERT_OK(r);
if (r == 0)
continue;
if (path_is_absolute(word)) {
- assert_se(strv_consume(&libraries, TAKE_PTR(word)) >= 0);
+ ASSERT_OK(strv_consume(&libraries, TAKE_PTR(word)));
continue;
}
}
_cleanup_strv_free_ char **libraries = NULL, **libraries_test = NULL;
int r;
- assert_se(user_runtime_unit_dir);
+ ASSERT_NOT_NULL(user_runtime_unit_dir);
r = find_executable("ldd", NULL);
if (r < 0) {
if (MANAGER_IS_USER(m) && !have_userns_privileges())
return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__);
- assert_se(find_libraries(fullpath_touch, &libraries) >= 0);
- assert_se(find_libraries(fullpath_test, &libraries_test) >= 0);
- assert_se(strv_extend_strv(&libraries, libraries_test, true) >= 0);
+ ASSERT_OK(find_libraries(fullpath_touch, &libraries));
+ ASSERT_OK(find_libraries(fullpath_test, &libraries_test));
+ ASSERT_OK(strv_extend_strv(&libraries, libraries_test, true));
- assert_se(strextend(&data, "[Service]\n"));
- assert_se(strextend(&data, "ExecStart=", fullpath_touch, " /aaa\n"));
- assert_se(strextend(&data, "ExecStart=", fullpath_test, " -f /aaa\n"));
- assert_se(strextend(&data, "BindReadOnlyPaths=", fullpath_touch, "\n"));
- assert_se(strextend(&data, "BindReadOnlyPaths=", fullpath_test, "\n"));
+ ASSERT_NOT_NULL(strextend(&data, "[Service]\n"));
+ ASSERT_NOT_NULL(strextend(&data, "ExecStart=", fullpath_touch, " /aaa\n"));
+ ASSERT_NOT_NULL(strextend(&data, "ExecStart=", fullpath_test, " -f /aaa\n"));
+ ASSERT_NOT_NULL(strextend(&data, "BindReadOnlyPaths=", fullpath_touch, "\n"));
+ ASSERT_NOT_NULL(strextend(&data, "BindReadOnlyPaths=", fullpath_test, "\n"));
STRV_FOREACH(p, libraries)
- assert_se(strextend(&data, "BindReadOnlyPaths=", *p, "\n"));
+ ASSERT_NOT_NULL(strextend(&data, "BindReadOnlyPaths=", *p, "\n"));
- assert_se(write_drop_in(user_runtime_unit_dir, "exec-mount-apivfs-no.service", 10, "bind-mount", data) >= 0);
+ ASSERT_OK(write_drop_in(user_runtime_unit_dir, "exec-mount-apivfs-no.service", 10, "bind-mount", data));
- assert_se(mkdir_p("/tmp/test-exec-mount-apivfs-no/root", 0755) >= 0);
+ ASSERT_OK(mkdir_p("/tmp/test-exec-mount-apivfs-no/root", 0755));
test(m, "exec-mount-apivfs-no.service", can_unshare || !MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
_cleanup_free_ char *p = NULL;
struct stat st;
- assert_se(p = path_join(m->prefix[dt], "private"));
+ ASSERT_NOT_NULL(p = path_join(m->prefix[dt], "private"));
if (stat(p, &st) >= 0 &&
(st.st_mode & (S_IRWXG|S_IRWXO)))
int r;
r = write_string_file("/tmp/test-exec_environmentfile.conf", e, WRITE_STRING_FILE_CREATE);
- assert_se(r == 0);
+ ASSERT_OK(r);
test(m, "exec-environmentfile.service", 0, CLD_EXITED);
* This is still a good approximation of how a test for MANAGER_SYSTEM
* would work.
*/
- assert_se(setenv("VAR1", "word1 word2", 1) == 0);
- assert_se(setenv("VAR2", "word3", 1) == 0);
- assert_se(setenv("VAR3", "$word 5 6", 1) == 0);
- assert_se(setenv("VAR4", "new\nline", 1) == 0);
- assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0);
+ ASSERT_OK_ERRNO(setenv("VAR1", "word1 word2", 1));
+ ASSERT_OK_ERRNO(setenv("VAR2", "word3", 1));
+ ASSERT_OK_ERRNO(setenv("VAR3", "$word 5 6", 1));
+ ASSERT_OK_ERRNO(setenv("VAR4", "new\nline", 1));
+ ASSERT_OK_ERRNO(setenv("VAR5", "passwordwithbackslashes", 1));
test(m, "exec-passenvironment.service", 0, CLD_EXITED);
test(m, "exec-passenvironment-repeated.service", 0, CLD_EXITED);
test(m, "exec-passenvironment-empty.service", 0, CLD_EXITED);
- assert_se(unsetenv("VAR1") == 0);
- assert_se(unsetenv("VAR2") == 0);
- assert_se(unsetenv("VAR3") == 0);
- assert_se(unsetenv("VAR4") == 0);
- assert_se(unsetenv("VAR5") == 0);
+ ASSERT_OK_ERRNO(unsetenv("VAR1"));
+ ASSERT_OK_ERRNO(unsetenv("VAR2"));
+ ASSERT_OK_ERRNO(unsetenv("VAR3"));
+ ASSERT_OK_ERRNO(unsetenv("VAR4"));
+ ASSERT_OK_ERRNO(unsetenv("VAR5"));
test(m, "exec-passenvironment-absent.service", 0, CLD_EXITED);
}
{},
};
- assert_se(unsetenv("USER") == 0);
- assert_se(unsetenv("LOGNAME") == 0);
- assert_se(unsetenv("SHELL") == 0);
- assert_se(unsetenv("HOME") == 0);
- assert_se(unsetenv("TMPDIR") == 0);
+ ASSERT_OK_ERRNO(unsetenv("USER"));
+ ASSERT_OK_ERRNO(unsetenv("LOGNAME"));
+ ASSERT_OK_ERRNO(unsetenv("SHELL"));
+ ASSERT_OK_ERRNO(unsetenv("HOME"));
+ ASSERT_OK_ERRNO(unsetenv("TMPDIR"));
/* Unset VARx, especially, VAR1, VAR2 and VAR3, which are used in the PassEnvironment test cases,
* otherwise (and if they are present in the environment), `manager_default_environment` will copy
* them into the default environment which is passed to each created job, which will make the tests
* that expect those not to be present to fail. */
- assert_se(unsetenv("VAR1") == 0);
- assert_se(unsetenv("VAR2") == 0);
- assert_se(unsetenv("VAR3") == 0);
- assert_se(unsetenv("VAR4") == 0);
- assert_se(unsetenv("VAR5") == 0);
+ ASSERT_OK_ERRNO(unsetenv("VAR1"));
+ ASSERT_OK_ERRNO(unsetenv("VAR2"));
+ ASSERT_OK_ERRNO(unsetenv("VAR3"));
+ ASSERT_OK_ERRNO(unsetenv("VAR4"));
+ ASSERT_OK_ERRNO(unsetenv("VAR5"));
- assert_se(runtime_dir = setup_fake_runtime_dir());
- assert_se(user_runtime_unit_dir = path_join(runtime_dir, "systemd/user"));
- assert_se(unit_paths = strjoin(PRIVATE_UNIT_DIR, ":", user_runtime_unit_dir));
- assert_se(set_unit_path(unit_paths) >= 0);
+ ASSERT_NOT_NULL(runtime_dir = setup_fake_runtime_dir());
+ ASSERT_NOT_NULL(user_runtime_unit_dir = path_join(runtime_dir, "systemd/user"));
+ ASSERT_NOT_NULL(unit_paths = strjoin(PRIVATE_UNIT_DIR, ":", user_runtime_unit_dir));
+ ASSERT_OK(set_unit_path(unit_paths));
r = manager_new(scope, MANAGER_TEST_RUN_BASIC, &m);
if (manager_errno_skip_test(r))
return (void) log_tests_skipped_errno(r, "manager_new");
- assert_se(r >= 0);
+ ASSERT_OK(r);
m->defaults.std_output = EXEC_OUTPUT_INHERIT; /* don't rely on host journald */
- assert_se(manager_startup(m, NULL, NULL, NULL) >= 0);
+ ASSERT_OK(manager_startup(m, NULL, NULL, NULL));
/* Uncomment below if you want to make debugging logs stored to journal. */
//manager_override_log_target(m, LOG_TARGET_AUTO);
FORK_NEW_MOUNTNS |
FORK_MOUNTNS_SLAVE,
NULL);
- assert_se(r >= 0);
+ ASSERT_OK(r);
if (r == 0) {
_cleanup_free_ char *unit_dir = NULL, *build_dir = NULL, *build_dir_mount = NULL;
int ret;
/* Make "/" read-only. */
- assert_se(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) >= 0);
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL));
/* Creating a new user namespace in the above means all MS_SHARED mounts become MS_SLAVE.
* Let's put them back to MS_SHARED here, since that's what we want as defaults. (This will
* not reconnect propagation, but simply create new peer groups for all our mounts). */
- assert_se(mount_follow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_SHARED|MS_REC, NULL) >= 0);
+ ASSERT_OK(mount_follow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_SHARED|MS_REC, NULL));
- assert_se(mkdir_p(PRIVATE_UNIT_DIR, 0755) >= 0);
- assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", PRIVATE_UNIT_DIR, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0);
+ ASSERT_OK(mkdir_p(PRIVATE_UNIT_DIR, 0755));
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", PRIVATE_UNIT_DIR, "tmpfs", MS_NOSUID|MS_NODEV, NULL));
/* Mark our test "playground" as MS_SLAVE, so we can MS_MOVE mounts underneath it. */
- assert_se(mount_nofollow_verbose(LOG_DEBUG, NULL, PRIVATE_UNIT_DIR, NULL, MS_SLAVE, NULL) >= 0);
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, NULL, PRIVATE_UNIT_DIR, NULL, MS_SLAVE, NULL));
/* Copy unit files to make them accessible even when unprivileged. */
- assert_se(get_testdata_dir("test-execute/", &unit_dir) >= 0);
- assert_se(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY) >= 0);
+ ASSERT_OK(get_testdata_dir("test-execute/", &unit_dir));
+ ASSERT_OK(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY));
/* Mount tmpfs on the following directories to make not StateDirectory= or friends disturb the host. */
ret = get_build_exec_dir(&build_dir);
- assert_se(ret >= 0 || ret == -ENOEXEC);
+ if (ret != -ENOEXEC)
+ ASSERT_OK(ret);
if (build_dir) {
/* Account for a build directory being in one of the soon-to-be-tmpfs directories. If we
* overmount it with an empty tmpfs, manager_new() will pin the wrong systemd-executor binary,
* which can then lead to unexpected (and painful to debug) test fails. */
- assert_se(access(build_dir, F_OK) >= 0);
- assert_se(build_dir_mount = path_join(PRIVATE_UNIT_DIR, "build_dir"));
- assert_se(mkdir_p(build_dir_mount, 0755) >= 0);
- assert_se(mount_nofollow_verbose(LOG_DEBUG, build_dir, build_dir_mount, NULL, MS_BIND, NULL) >= 0);
+ ASSERT_OK_ERRNO(access(build_dir, F_OK));
+ ASSERT_NOT_NULL(build_dir_mount = path_join(PRIVATE_UNIT_DIR, "build_dir"));
+ ASSERT_OK(mkdir_p(build_dir_mount, 0755));
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, build_dir, build_dir_mount, NULL, MS_BIND, NULL));
}
FOREACH_STRING(p, "/dev/shm", "/root", "/tmp", "/var/tmp", "/var/lib")
- assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0);
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, NULL));
if (build_dir_mount) {
ret = RET_NERRNO(access(build_dir, F_OK));
- assert_se(ret >= 0 || ret == -ENOENT);
+ if (ret != -ENOENT)
+ ASSERT_OK(ret);
if (ret == -ENOENT) {
/* The build directory got overmounted by tmpfs, so let's use the "backup" bind mount to
* bring it back. */
- assert_se(mkdir_p(build_dir, 0755) >= 0);
- assert_se(mount_nofollow_verbose(LOG_DEBUG, build_dir_mount, build_dir, NULL, MS_MOVE, NULL) >= 0);
+ ASSERT_OK(mkdir_p(build_dir, 0755));
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, build_dir_mount, build_dir, NULL, MS_MOVE, NULL));
}
}
/* Prepare credstore like tmpfiles.d/credstore.conf for LoadCredential= tests. */
FOREACH_STRING(p, "/run/credstore", "/run/credstore.encrypted") {
- assert_se(mkdir_p(p, 0) >= 0);
- assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, "mode=0000") >= 0);
+ ASSERT_OK(mkdir_p(p, 0));
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, "mode=0000"));
}
- assert_se(write_string_file("/run/credstore/test-execute.load-credential", "foo", WRITE_STRING_FILE_CREATE) >= 0);
+ ASSERT_OK(write_string_file("/run/credstore/test-execute.load-credential", "foo", WRITE_STRING_FILE_CREATE));
}
return r;
return (void) log_tests_skipped("unshare() is disabled");
/* safe_fork() clears saved_argv in the child process. Let's copy it. */
- assert_se(filters = strv_copy(strv_skip(saved_argv, 1)));
+ ASSERT_NOT_NULL(filters = strv_copy(strv_skip(saved_argv, 1)));
if (prepare_ns("(test-execute-root)") == 0) {
can_unshare = true;
return (void) log_tests_skipped("Seccomp not available, cannot run unshare() filtered tests");
/* safe_fork() clears saved_argv in the child process. Let's copy it. */
- assert_se(filters = strv_copy(strv_skip(saved_argv, 1)));
+ ASSERT_NOT_NULL(filters = strv_copy(strv_skip(saved_argv, 1)));
if (prepare_ns("(test-execute-without-unshare)") == 0) {
_cleanup_hashmap_free_ Hashmap *s = NULL;
r = seccomp_syscall_resolve_name("unshare");
- assert_se(r != __NR_SCMP_ERROR);
- assert_se(hashmap_ensure_put(&s, NULL, UINT32_TO_PTR(r + 1), INT_TO_PTR(-1)) >= 0);
- assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EOPNOTSUPP), true) >= 0);
+ ASSERT_NE(r, __NR_SCMP_ERROR);
+ ASSERT_OK(hashmap_ensure_put(&s, NULL, UINT32_TO_PTR(r + 1), INT_TO_PTR(-1)));
+ ASSERT_OK(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EOPNOTSUPP), true));
/* Check unshare() is actually filtered. */
- assert_se(unshare(CLONE_NEWNS) < 0);
- assert_se(errno == EOPNOTSUPP);
+ ASSERT_ERROR_ERRNO(unshare(CLONE_NEWNS), EOPNOTSUPP);
can_unshare = false;
run_tests(RUNTIME_SCOPE_SYSTEM, filters);
return (void) log_tests_skipped("unshare() is disabled");
/* safe_fork() clears saved_argv in the child process. Let's copy it. */
- assert_se(filters = strv_copy(strv_skip(saved_argv, 1)));
+ ASSERT_NOT_NULL(filters = strv_copy(strv_skip(saved_argv, 1)));
if (prepare_ns("(test-execute-unprivileged)") == 0) {
- assert_se(capability_bounding_set_drop(0, /* right_now = */ true) >= 0);
+ ASSERT_OK(capability_bounding_set_drop(0, /* right_now = */ true));
can_unshare = false;
run_tests(RUNTIME_SCOPE_USER, filters);
copyfd = fdset_put_dup(fdset, fd);
assert_se(copyfd >= 0);
+ /* fdset_close_others() will close any logging file descriptors as well, so close them beforehand
+ * and reopen them again afterwards. */
+ log_close();
assert_se(fdset_close_others(fdset) >= 0);
+
flags = fcntl(fd, F_GETFD);
assert_se(flags < 0);
+
+ /* Open log again after checking that fd is invalid, since reopening the log might make fd a valid
+ * file descriptor again. */
+ (void) log_open();
+
flags = fcntl(copyfd, F_GETFD);
assert_se(flags >= 0);
}
}
TEST(sd_id128_get_invocation) {
- sd_id128_t id;
+ sd_id128_t id = SD_ID128_NULL;
int r;
/* Query the invocation ID */
log_warning_errno(r, "Failed to get invocation ID, ignoring: %m");
else
log_info("Invocation ID: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
+
+ sd_id128_t appid = SD_ID128_NULL;
+ r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(59,36,e9,92,fd,11,42,fe,87,c9,e9,b5,6c,9e,4f,04), &appid);
+ if (r < 0)
+ log_warning_errno(r, "Failed to get invocation ID, ignoring: %m");
+ else {
+ assert(!sd_id128_equal(id, appid));
+ log_info("Per-App Invocation ID: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(appid));
+ }
+
+ sd_id128_t appid2 = SD_ID128_NULL;
+ r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(59,36,e9,92,fd,11,42,fe,87,c9,e9,b5,6c,9e,4f,05), &appid2); /* slightly different appid */
+ if (r < 0)
+ log_warning_errno(r, "Failed to get invocation ID, ignoring: %m");
+ else {
+ assert(!sd_id128_equal(id, appid2));
+ assert(!sd_id128_equal(appid, appid2));
+ log_info("Per-App Invocation ID 2: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(appid2));
+ }
+
+ sd_id128_t appid3 = SD_ID128_NULL;
+ r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(59,36,e9,92,fd,11,42,fe,87,c9,e9,b5,6c,9e,4f,04), &appid3); /* same appid as before */
+ if (r < 0)
+ log_warning_errno(r, "Failed to get invocation ID, ignoring: %m");
+ else {
+ assert(!sd_id128_equal(id, appid3));
+ assert(sd_id128_equal(appid, appid3));
+ assert(!sd_id128_equal(appid2, appid3));
+ log_info("Per-App Invocation ID 3: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(appid3));
+ }
}
TEST(benchmark_sd_id128_get_machine_app_specific) {
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stddef.h>
+#include <sys/stat.h>
+#include "errno-util.h"
#include "log.h"
#include "macro.h"
#include "tests.h"
ASSERT_SIGNAL(ASSERT_OK_ERRNO(-1), SIGABRT);
ASSERT_SIGNAL(ASSERT_OK_ERRNO(-ENOANO), SIGABRT);
+ ASSERT_ERROR(-ENOENT, ENOENT);
+ ASSERT_ERROR(RET_NERRNO(mkdir("/i/will/fail/with/enoent", 666)), ENOENT);
+ ASSERT_SIGNAL(ASSERT_ERROR(0, ENOENT), SIGABRT);
+ ASSERT_SIGNAL(ASSERT_ERROR(RET_NERRNO(mkdir("/i/will/fail/with/enoent", 666)), ENOANO), SIGABRT);
+
+ errno = ENOENT;
+ ASSERT_ERROR_ERRNO(-1, ENOENT);
+ errno = 0;
+ ASSERT_ERROR_ERRNO(mkdir("/i/will/fail/with/enoent", 666), ENOENT);
+ ASSERT_SIGNAL(ASSERT_ERROR_ERRNO(0, ENOENT), SIGABRT);
+ errno = 0;
+ ASSERT_SIGNAL(ASSERT_ERROR_ERRNO(mkdir("/i/will/fail/with/enoent", 666), ENOANO), SIGABRT);
+
ASSERT_TRUE(true);
ASSERT_TRUE(255);
ASSERT_TRUE(getpid());
{ "/", true },
};
- FOREACH_ARRAY(i, table, ELEMENTSOF(table)) {
+ FOREACH_ELEMENT(i, table) {
r = safe_fork("(switch-root)",
FORK_RESET_SIGNALS |
FORK_CLOSE_ALL_FDS |
int r;
- FOREACH_ARRAY(t, test_table, ELEMENTSOF(test_table)) {
+ FOREACH_ELEMENT(t, test_table) {
r = safe_fork("(umount-rec)",
FORK_RESET_SIGNALS |
TEST(digest_size) {
size_t size;
- FOREACH_ARRAY(t, digest_size_table, ELEMENTSOF(digest_size_table)) {
+ FOREACH_ELEMENT(t, digest_size_table) {
assert(openssl_digest_size(t->alg, &size) >= 0);
assert_se(size == t->size);
/* Parent */
char buf[64];
struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1);
- _cleanup_close_ int fd;
+ _cleanup_close_ int fd = -EBADF;
ssize_t k;
pair[1] = safe_close(pair[1]);
S_IFSOCK,
};
- FOREACH_ARRAY(m, types, ELEMENTSOF(types))
+ FOREACH_ELEMENT(m, types)
assert_se(inode_type_from_string(inode_type_to_string(*m)) == *m);
}
check_seal_unseal_for_handle(c, 0);
check_seal_unseal_for_handle(c, TPM2_SRK_HANDLE);
- FOREACH_ARRAY(template, test_templates, ELEMENTSOF(test_templates)) {
+ FOREACH_ELEMENT(template, test_templates) {
TPM2B_PUBLIC public = {
.publicArea = **template,
.size = sizeof(**template),
PickFilter filter = {
.architecture = _ARCHITECTURE_INVALID,
- .suffix = ".raw",
+ .suffix = STRV_MAKE(".raw"),
};
if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
else if (r == -ENOENT)
log_debug("/dev/rtc not found.");
+ else if (r == -ENODATA)
+ log_debug("/dev/rtc has no valid time, power loss probably occurred?");
else if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
else
assert(ret);
- r = tpm2_context_new(arg_tpm2_device, &c);
+ r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ return r;
r = tpm2_get_or_create_srk(
c,
DEFAULT_SECTIONS_TO_SHOW = {
'.linux' : 'binary',
'.initrd' : 'binary',
+ '.ucode' : 'binary',
'.splash' : 'binary',
'.dtb' : 'binary',
'.cmdline' : 'text',
('.splash', opts.splash, True ),
('.pcrpkey', pcrpkey, True ),
('.initrd', initrd, True ),
+ ('.ucode', opts.microcode, True ),
# linux shall be last to leave breathing room for decompression.
# We'll add it later.
config_push = ConfigItem.config_list_prepend,
),
+ ConfigItem(
+ '--microcode',
+ metavar = 'UCODE',
+ type = pathlib.Path,
+ help = 'microcode file [.ucode section]',
+ config_key = 'UKI/Microcode',
+ ),
+
ConfigItem(
('--config', '-c'),
metavar = 'PATH',
assert(c);
for (unsigned n_attempts = 0;;) {
- FOREACH_ARRAY(e, table, ELEMENTSOF(table)) {
+ FOREACH_ELEMENT(e, table) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *state = NULL, *path = NULL;
assert(table);
- FOREACH_ARRAY(i, uid_range_table, ELEMENTSOF(uid_range_table)) {
+ FOREACH_ELEMENT(i, uid_range_table) {
_cleanup_free_ char *name = NULL, *comment = NULL;
if (!uid_range_covers(p, i->first, i->last - i->first + 1))
assert(table);
- FOREACH_ARRAY(i, uid_range_table, ELEMENTSOF(uid_range_table)) {
+ FOREACH_ELEMENT(i, uid_range_table) {
_cleanup_free_ char *name = NULL, *comment = NULL;
if (!uid_range_covers(p, i->first, i->last - i->first + 1))
log_warning("Couldn't find OVMF firmware blob with Secure Boot support, "
"falling back to OVMF firmware blobs without Secure Boot support.");
- shm = arg_directory ? ",memory-backend=mem" : "";
+ shm = arg_directory || arg_runtime_mounts.n_mounts != 0 ? ",memory-backend=mem" : "";
if (ARCHITECTURE_SUPPORTS_SMM)
machine = strjoin("type=" QEMU_MACHINE_TYPE ",smm=", on_off(ovmf_config->supports_sb), shm);
else
if (strv_extend_many(&cmdline, "-uuid", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0)
return log_oom();
+ /* Derive a vmgenid automatically from the invocation ID, in a deterministic way. */
+ sd_id128_t vmgenid;
+ r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(bd,84,6d,e3,e4,7d,4b,6c,a6,85,4a,87,0f,3c,a3,a0), &vmgenid);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to get invocation ID, making up randomized vmgenid: %m");
+
+ r = sd_id128_randomize(&vmgenid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make up randomized vmgenid: %m");
+ }
+
+ _cleanup_free_ char *vmgenid_device = NULL;
+ if (asprintf(&vmgenid_device, "vmgenid,guid=" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)) < 0)
+ return log_oom();
+
+ if (strv_extend_many(&cmdline, "-device", vmgenid_device) < 0)
+ return log_oom();
+
/* if we are going to be starting any units with state then create our runtime dir */
if (arg_tpm != 0 || arg_directory || arg_runtime_mounts.n_mounts != 0) {
r = runtime_directory(&arg_runtime_directory, arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, "systemd/vmspawn");
pass_fds[n_pass_fds++] = device_fd;
}
- r = strv_extend_many(&cmdline, "-cpu", "max");
+ r = strv_extend_many(&cmdline, "-cpu",
+#ifdef __x86_64__
+ "max,hv_relaxed,hv-vapic,hv-time"
+#else
+ "max"
+#endif
+ );
if (r < 0)
return log_oom();
return log_error_errno(r, "Failed to call getsockname on VSOCK: %m");
}
+ const char *e = secure_getenv("SYSTEMD_VMSPAWN_QEMU_EXTRA");
+ if (e) {
+ _cleanup_strv_free_ char **extra = NULL;
+
+ r = strv_split_full(&extra, e, /* separator= */ NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to split $SYSTEMD_VMSPAWN_QEMU_EXTRA environment variable: %m");
+
+ if (strv_extend_strv(&cmdline, extra, /* filter_duplicates= */ false) < 0)
+ return log_oom();
+ }
+
if (DEBUG_LOGGING) {
_cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY);
if (!joined)
.basename = arg_filter_basename,
.version = arg_filter_version,
.architecture = arg_filter_architecture,
- .suffix = arg_filter_suffix,
+ .suffix = STRV_MAKE(arg_filter_suffix),
.type_mask = arg_filter_type_mask,
},
arg_flags,
$ sudo make -C test/TEST-01-BASIC clean setup run
+To run the meson-based integration test config
+enable integration tests and options for required commands with the following:
+
+$ meson configure build -Dintegration-tests=true -Dremote=enabled -Dopenssl=enabled -Dblkid=enabled -Dtpm2=enabled
+
+Once enabled the integration tests can be run with:
+
+$ sudo meson test -C build/ --suite integration-tests --num-processes "$((nproc / 2))"
+
+As usual, specific tests can be run in meson by appending the name of the test
+which is usually the name of the directory e.g.
+
+$ sudo meson test -C build/ --suite integration-tests --num-processes "$((nproc / 2))" TEST-01-BASIC
+
+See `meson introspect build --tests` for a list of tests.
+
Specifying the build directory
==============================
--- /dev/null
+test_params += {
+ 'mkosi_args': test_params['mkosi_args'] + [
+ '--kernel-command-line-extra=' + ' '.join([
+ '''
+frobnicate!
+
+systemd.setenv=TEST_CMDLINE_NEWLINE=foo
+systemd.setenv=TEST_CMDLINE_NEWLINE=bar
+
+''',
+ ]),
+ ],
+}
--- /dev/null
+#!/usr/bin/python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+'''Test wrapper command for driving integration tests.
+
+Note: This is deliberately rough and only intended to drive existing tests
+with the expectation that as part of formally defining the API it will be tidy.
+
+'''
+
+import argparse
+import logging
+import os
+from pathlib import Path
+import shlex
+import subprocess
+
+
+TEST_EXIT_DROPIN = """\
+[Unit]
+SuccessAction=exit
+FailureAction=exit
+"""
+
+
+EMERGENCY_EXIT_DROPIN = """\
+[Unit]
+Wants=emergency-exit.service
+"""
+
+
+EMERGENCY_EXIT_SERVICE = """\
+[Unit]
+DefaultDependencies=no
+Conflicts=shutdown.target
+Conflicts=rescue.service
+Before=shutdown.target
+Before=rescue.service
+FailureAction=exit
+
+[Service]
+ExecStart=false
+"""
+
+
+parser = argparse.ArgumentParser(description=__doc__)
+parser.add_argument('--test-name', required=True)
+parser.add_argument('--mkosi-image-name', required=True)
+parser.add_argument('--mkosi-output-path', required=True, type=Path)
+parser.add_argument('--test-number', required=True)
+parser.add_argument('--no-emergency-exit',
+ dest='emergency_exit', default=True, action='store_false',
+ help="Disable emergency exit drop-ins for interactive debugging")
+parser.add_argument('mkosi_args', nargs="*")
+
+def main():
+ logging.basicConfig(level=logging.DEBUG)
+ args = parser.parse_args()
+
+ test_unit_name = f"testsuite-{args.test_number}.service"
+ # Machine names shouldn't have / since it's used as a file name
+ # and it must be a valid hostname so 64 chars max
+ machine_name = args.test_name.replace('/', '_')[:64]
+
+ logging.debug(f"test name: {args.test_name}\n"
+ f"test number: {args.test_number}\n"
+ f"image: {args.mkosi_image_name}\n"
+ f"mkosi output path: {args.mkosi_output_path}\n"
+ f"mkosi args: {args.mkosi_args}\n"
+ f"emergency exit: {args.emergency_exit}")
+
+ journal_file = Path(f"{machine_name}.journal").absolute()
+ logging.info(f"Capturing journal to {journal_file}")
+
+ mkosi_args = [
+ 'mkosi',
+ '--directory', Path('..').resolve(),
+ '--output-dir', args.mkosi_output_path.absolute(),
+ '--machine', machine_name,
+ '--image', args.mkosi_image_name,
+ '--format=disk',
+ '--runtime-build-sources=no',
+ '--ephemeral',
+ '--forward-journal', journal_file,
+ *(
+ [
+ '--credential',
+ f"systemd.extra-unit.emergency-exit.service={shlex.quote(EMERGENCY_EXIT_SERVICE)} "
+ f"systemd.unit-dropin.emergency.target={shlex.quote(EMERGENCY_EXIT_DROPIN)}",
+ ]
+ if args.emergency_exit
+ else []
+ ),
+ f"--credential=systemd.unit-dropin.{test_unit_name}={shlex.quote(TEST_EXIT_DROPIN)}",
+ '--append',
+ '--kernel-command-line-extra',
+ ' '.join([
+ 'systemd.hostname=H',
+ f"SYSTEMD_UNIT_PATH=/usr/lib/systemd/tests/testdata/testsuite-{args.test_number}.units:/usr/lib/systemd/tests/testdata/units:",
+ 'systemd.unit=testsuite.target',
+ f"systemd.wants={test_unit_name}",
+ ]),
+ *args.mkosi_args,
+ ]
+
+ mkosi_args += ['qemu']
+
+ logging.debug(f"Running {shlex.join(os.fspath(a) for a in mkosi_args)}")
+
+ try:
+ subprocess.run(mkosi_args, check=True)
+ except subprocess.CalledProcessError as e:
+ if e.returncode not in (0, 77):
+ suggested_command = [
+ 'journalctl',
+ '--all',
+ '--no-hostname',
+ '-o', 'short-monotonic',
+ '--file', journal_file,
+ f"_SYSTEMD_UNIT={test_unit_name}",
+ '+', f"SYSLOG_IDENTIFIER=testsuite-{args.test_number}.sh",
+ '+', 'PRIORITY=4',
+ '+', 'PRIORITY=3',
+ '+', 'PRIORITY=2',
+ '+', 'PRIORITY=1',
+ '+', 'PRIORITY=0',
+ ]
+ logging.info("Test failed, relevant logs can be viewed with: "
+ f"{shlex.join(os.fspath(a) for a in suggested_command)}")
+ exit(e.returncode)
+
+
+if __name__ == '__main__':
+ main()
depends : deps,
suite : 'kernel-install')
endif
+
+############################################################
+
+if get_option('integration-tests') != false
+ integration_test_wrapper = find_program('integration_test_wrapper.py')
+ integration_tests = {
+ '01': 'TEST-01-BASIC',
+ '02': 'TEST-02-UNITTESTS',
+ }
+ foreach test_number, dirname : integration_tests
+ test_unit_name = f'testsuite-@test_number@.service'
+ test_params = {
+ 'test_name' : dirname,
+ 'mkosi_image_name' : 'system',
+ 'mkosi_output_path' : system_mkosi,
+ 'test_number' : test_number,
+ 'mkosi_args' : [],
+ 'depends' : [system_mkosi],
+ 'timeout' : 600,
+ }
+
+ # TODO: This fs.exists call isn't included in rebuild logic
+ # so if you add a new meson.build in a subdir
+ # you need to touch another build file to get it to reparse.
+ if fs.exists(dirname / 'meson.build')
+ subdir(dirname)
+ endif
+ args = ['--test-name', test_params['test_name'],
+ '--mkosi-image-name', test_params['mkosi_image_name'],
+ '--mkosi-output-path', test_params['mkosi_output_path'],
+ '--test-number', test_params['test_number']]
+ args += ['--'] + test_params['mkosi_args']
+ test(test_params['test_name'],
+ integration_test_wrapper,
+ env: test_env,
+ args : args,
+ depends : test_params['depends'],
+ timeout : test_params['timeout'],
+ suite : 'integration-tests')
+ endforeach
+endif
print(output)
self.assertRegex(output, r'.*elements = { [^}]*' + contents + r'[^}]* }.*')
+ def check_networkd_log(self, contents, since=None, trial=20):
+ for _ in range(trial):
+ if contents in read_networkd_log(since=since):
+ break
+ time.sleep(0.5)
+ else:
+ self.fail(f'"{contents}" not found in journal.')
+
class NetworkctlTests(unittest.TestCase, Utilities):
def setUp(self):
self.assertIn('Network File: /run/systemd/network/11-test-unit-file.network', output)
self.assertIn('/run/systemd/network/11-test-unit-file.network.d/dropin.conf', output)
- output = read_networkd_log()
- self.assertIn('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).', output)
+ self.check_networkd_log('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).')
# 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.
self.wait_online('bond99:off')
self.wait_operstate('vlan99', operstate='off', setup_state='configuring', setup_timeout=10)
- # The commit b05e52000b4eee764b383cc3031da0a3739e996e adds ", ignoring". To make it easily confirmed
- # that the issue is fixed by the commit, let's allow to match both string.
- log_re = re.compile('vlan99: Could not bring up interface(, ignoring|): Network is down$', re.MULTILINE)
- for i in range(20):
- if i > 0:
- time.sleep(0.5)
- if log_re.search(read_networkd_log()):
- break
- else:
- self.fail()
+ self.check_networkd_log('vlan99: Could not bring up interface, ignoring: Network is down')
copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '21-dummy-bond-slave.network')
networkctl_reload()
self.assertIn('2002:da8:1:1:1a:2b:3c:4d via fe80::1 proto redirect', output)
self.assertIn('2002:da8:1:2:1a:2b:3c:4d via fe80::2 proto redirect', output)
+ # Check if sd-radv refuses RS from the same interface.
+ # See https://github.com/systemd/systemd/pull/32267#discussion_r1566721306
+ since = datetime.datetime.now()
+ check_output(f'{test_ndisc_send} --interface veth-peer --type rs --dest {veth_peer_ipv6ll}')
+ self.check_networkd_log('veth-peer: RADV: Received RS from the same interface, ignoring.', since=since)
+
def check_ndisc_mtu(self, mtu):
for _ in range(20):
output = read_ipv6_sysctl_attr('veth99', 'mtu')
start_networkd()
self.wait_online('veth-peer:degraded')
- for _ in range(20):
- output = read_networkd_log()
- if 'veth99: NDISC: Started IPv6 Router Solicitation client' in output:
- break
- time.sleep(0.5)
- else:
- self.fail('sd-ndisc does not started on veth99.')
+ self.check_networkd_log('veth99: NDISC: Started IPv6 Router Solicitation client')
check_output(f'{test_ndisc_send} --interface veth-peer --type ra --lifetime 1hour --mtu 1400')
self.check_ndisc_mtu(1400)
self.wait_online('bridge-relay:routable', 'client-peer:enslaved')
# For issue #30763.
- expect = 'bridge-relay: DHCPv4 server: STARTED'
- for _ in range(20):
- if expect in read_networkd_log():
- break
- time.sleep(0.5)
- else:
- self.fail()
+ self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED')
class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
since = datetime.datetime.now()
start_dnsmasq()
- expect = 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.'
- for _ in range(20):
- if expect in read_networkd_log(since=since):
- break
- time.sleep(0.5)
- else:
- self.fail()
+ self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since)
copy_network_unit('25-dhcp-client-allow-list.network.d/00-allow-list.conf')
since = datetime.datetime.now()
networkctl_reload()
- expect = 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.'
- for _ in range(20):
- if expect in read_networkd_log(since=since):
- break
- time.sleep(0.5)
- else:
- self.fail()
+ self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since)
copy_network_unit('25-dhcp-client-allow-list.network.d/10-deny-list.conf')
since = datetime.datetime.now()
networkctl_reload()
- expect = 'veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.'
- for _ in range(20):
- if expect in read_networkd_log(since=since):
- break
- time.sleep(0.5)
- else:
- self.fail()
+ self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', since=since)
@unittest.skipUnless("--dhcp-rapid-commit" in run("dnsmasq --help").stdout, reason="dnsmasq is missing dhcp-rapid-commit support")
def test_dhcp_client_rapid_commit(self):
check(self, True, False)
check(self, False, True)
check(self, False, False)
-
- def test_dhcp_client_default_use_domains(self):
- def check(self, ipv4, ipv6):
+
+ def test_dhcp_client_default_use_domains(self):
+ def check(self, common, ipv4, ipv6):
mkdir_p(networkd_conf_dropin_dir)
with open(os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), mode='w', encoding='utf-8') as f:
+ f.write('[Network]\nUseDomains=')
+ f.write('yes\n' if common else 'no\n')
f.write('[DHCPv4]\nUseDomains=')
f.write('yes\n' if ipv4 else 'no\n')
f.write('[DHCPv6]\nUseDomains=')
f.write('yes\n' if ipv6 else 'no\n')
-
+
restart_networkd()
self.wait_online('veth-peer:carrier')
start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1',
for _ in range(20):
output = resolvectl('domain', 'veth99')
- if ipv4 or ipv6:
+ if common or ipv4 or ipv6:
if 'example.com' in output:
break
else:
time.sleep(0.5)
else:
print(output)
+ print(read_link_state_file('veth99'))
self.fail('unexpected domain setting in resolved...')
-
+
stop_dnsmasq()
remove_networkd_conf_dropin('default_use_domains.conf')
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
- check(self, True, True)
- check(self, True, False)
- check(self, False, True)
- check(self, False, False)
+ check(self, True, False, False)
+ check(self, False, True, True)
+ check(self, False, True, False)
+ check(self, False, False, True)
+ check(self, False, False, False)
def test_dhcp_client_use_captive_portal(self):
def check(self, ipv4, ipv6):
portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
+# Ensure vpick works, including reattaching to a new image
+mkdir -p /tmp/app1.v/
+cp /usr/share/app1.raw /tmp/app1.v/app1_1.0.raw
+cp /tmp/app1_2.raw /tmp/app1.v/app1_2.0.raw
+portablectl "${ARGS[@]}" attach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_1.raw app1
+
+systemctl is-active app1.service
+status="$(portablectl is-attached --extension app1_2.0.raw minimal_1)"
+[[ "${status}" == "running-runtime" ]]
+
+rm -f /tmp/app1.v/app1_2.0.raw
+portablectl "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_1.raw app1
+
+systemctl is-active app1.service
+status="$(portablectl is-attached --extension app1_1.0.raw minimal_1)"
+[[ "${status}" == "running-runtime" ]]
+
+portablectl detach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_0.raw app1
+rm -f /tmp/app1.v/app1_1.0.raw
+
# Ensure that the combination of read-only images, state directory and dynamic user works, and that
# state is retained. Check after detaching, as on slow systems (eg: sanitizers) it might take a while
# after the service is attached before the file appears.
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app1.service.d/20-portable.conf
-portablectl detach --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
+portablectl detach --clean --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
+
+# Ensure --clean remove state and other directories belonging to the portable image being detached
+test ! -d /var/lib/app0
+test ! -d /run/app0
# Ensure that mixed mode copies the images and units (client-owned) but symlinks the profile (OS owned)
portablectl "${ARGS[@]}" attach --copy=mixed --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
FAKE_ROOTS_DIR="$(mktemp -d --tmpdir="" fake-roots-XXX)"
+shopt -s nullglob
+
# shellcheck disable=SC2317
at_exit() {
set +ex
trap at_exit EXIT
+# Clears the trap command - it needs to be invoked for every test-case subshell
+# so prepending commands with prepend_trap inside the subshell won't preserve
+# the trap commands from outer shell.
+init_trap() {
+ trap - EXIT
+}
+
+prepend_trap() {
+ set +x
+
+ local command=${1}; shift
+ local previous_commands
+
+ previous_commands=$(trap -p EXIT)
+ if [[ -z $previous_commands ]]; then
+ previous_commands=':'
+ else
+ previous_commands=${previous_commands#'trap -- '}
+ previous_commands=${previous_commands%' EXIT'}
+ previous_commands=$(xargs <<<"$previous_commands")
+ fi
+
+ # shellcheck disable=SC2064 # We use double quotes on purpose here.
+ trap "${command}; ${previous_commands}" EXIT
+
+ set -x
+}
+
prepare_root() {
- local root=${1:?}
+ local root=${1:-}
local hierarchy=${2:?}
local dir
- if [[ -d $root ]]; then
+ if [[ -n $root ]] && [[ -d $root ]]; then
echo >&2 "Directory $root already exists, possible copy-paste error?"
exit 1
fi
+ local -a leftovers=( "$root/var/lib/extensions/"* "$root/var/lib/extensions.mutable/"* )
+ if [[ ${#leftovers[@]} -gt 0 ]]; then
+ echo >&2 "Leftovers remained, make sure to clean them up in the test case: ${leftovers[*]}"
+ exit 1
+ fi
+
for dir in "$hierarchy" "/usr/lib" "/var/lib/extensions/" "/var/lib/extensions.mutable"; do
mkdir -p "$root$dir"
done
+ if [[ -e $root/usr/lib/os-release ]]; then
+ mv "$root/usr/lib/os-release" "$root/usr/lib/os-release.orig"
+ fi
+
{
echo "ID=testtest"
echo "VERSION=1.2.3"
} >"$root/usr/lib/os-release"
+
+ prepend_trap "cleanup_os_release ${root@Q}"
+}
+
+cleanup_os_release() {
+ # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above.
+ local root=${1:-}
+
+ # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above.
+ rm -f "$root/usr/lib/os-release"
+ # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above.
+ if [[ -e $root/usr/lib/os-release.orig ]]; then
+ # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above.
+ mv "$root/usr/lib/os-release.orig" "$root/usr/lib/os-release"
+ fi
}
prepare_extension_image() {
- local root="${1:?}"
- local hierarchy="${2:?}"
+ local root=${1:-}
+ local hierarchy=${2:?}
local ext_dir ext_release name
name="test-extension"
echo "ID=_any" >"$ext_release"
mkdir -p "$ext_dir/$hierarchy"
touch "$ext_dir$hierarchy/preexisting-file-in-extension-image"
+
+ prepend_trap "rm -rf ${ext_dir@Q}"
}
prepare_extension_mutable_dir() {
mkdir -p "$dir"
touch "$dir/preexisting-file-in-extensions-mutable"
+ prepend_trap "rm -rf ${dir@Q}"
}
make_read_only() {
- local root="${1:?}"
- local hierarchy="${2:?}"
+ local root=${1:-}
+ local hierarchy=${2:?}
mount -o bind,ro "$root$hierarchy" "$root$hierarchy"
+ prepend_trap "umount ${root@Q}${hierarchy@Q}"
}
prepare_hierarchy() {
- local root="${1:?}"
- local hierarchy="${2:?}"
+ local root=${1:-}
+ local hierarchy=${2:?}
+ local file
- touch "$root$hierarchy/preexisting-file-in-hierarchy"
+ file="$root$hierarchy/preexisting-file-in-hierarchy"
+ touch "$file"
+ prepend_trap "rm -f ${file@Q}"
}
prepare_read_only_hierarchy() {
- local root="${1:?}"
- local hierarchy="${2:?}"
+ local root=${1:-}
+ local hierarchy=${2:?}
prepare_hierarchy "$root" "$hierarchy"
make_read_only "$root" "$hierarchy"
}
+move_existing_hierarchy_aside() {
+ local root=${1:-}
+ local hierarchy=${2:?}
+
+ if [[ -z $root ]] && [[ $hierarchy = /usr ]]; then
+ echo >&2 "Hell no, not moving /usr aside"
+ exit 1
+ fi
+
+ local path=$root$hierarchy
+
+ if [[ -e $path ]]; then
+ mv "$path" "$path.orig"
+ prepend_trap "mv ${path@Q}.orig ${path@Q}"
+ fi
+}
+
# Extra arguments:
# -e: check for a preexisting file in extension
# -h: check for a preexisting file in hierarchy
# -u: check for a preexisting file in upperdir
extension_verify() {
- local root="${1:?}"
- local hierarchy="${2:?}"
- local message="${3:?}"
+ local root=${1:-}
+ local hierarchy=${2:?}
+ local message=${3:?}
shift 3
# Map each option to a pre-defined file name
local -A option_files_map=(
[h]=0
[u]=0
)
- local file full_path option
+ local file full_path opt option
while getopts "ehu" opt; do
case "$opt" in
e|h|u)
- args[$opt]=1
+ args["$opt"]=1
;;
*)
echo >&2 "Unxexpected option: $opt"
esac
done
- echo "${args[@]}"
-
for option in "${!option_files_map[@]}"; do
- file="${option_files_map[$option]}"
+ file=${option_files_map["$option"]}
full_path="$root$hierarchy/$file"
- if [[ ${args[$option]} -ne 0 ]]; then
- if [[ ! -f "$full_path" ]]; then
+ if [[ ${args["$option"]} -ne 0 ]]; then
+ if [[ ! -f $full_path ]]; then
ls -la "$root$hierarchy"
echo >&2 "Expected file '$file' to exist under $root$hierarchy $message"
exit 1
fi
else
- if [[ -f "$full_path" ]]; then
+ if [[ -f $full_path ]]; then
ls -la "$root$hierarchy"
echo >&2 "Expected file '$file' to not exist under $root$hierarchy $message"
exit 1
extension_verify_after_merge() (
set +x
- local root="${1:?}"
- local hierarchy="${2:?}"
+ local root=${1:-}
+ local hierarchy=${2:?}
shift 2
extension_verify "$root" "$hierarchy" "after merge" "$@"
extension_verify_after_unmerge() (
set +x
- local root="${1:?}"
- local hierarchy="${2:?}"
+ local root=${1:-}
+ local hierarchy=${2:?}
shift 2
extension_verify "$root" "$hierarchy" "after unmerge" "$@"
)
+run_systemd_sysext() {
+ local root=${1:-}
+ shift
+
+ local -a sysext_args
+ sysext_args=()
+
+ if [[ -n $root ]]; then
+ sysext_args+=( "--root=$root" )
+ fi
+ sysext_args+=( "$@" )
+ systemd-sysext "${sysext_args[@]}"
+}
+
# General systemd-sysext tests
+run_sysext_tests() {
+ # The roots_dir variable may be empty - in such case all the tests will run
+ # on /, otherwise they will run on $roots_dir/<SEPARATE_DIR_FOR_TEST>.
+ local roots_dir=${1}; shift
+
+ # Each test runs in a subshell, so we can use traps for cleanups without
+ # clobbering toplevel traps, and we can do skips by invoking "exit 0".
+
+( init_trap
: "No extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability disabled by default, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-read-only-hierarchy"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-read-only-hierarchy"}
+hierarchy=/opt
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge
+run_systemd_sysext "$fake_root" merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
+)
+( init_trap
: "No extension data in /var/lib/extensions.mutable/…, mutable hierarchy, mutability disabled by default, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-mutable-hierarchy"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-mutable-hierarchy"}
+hierarchy=/opt
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_hierarchy "$fake_root" "$hierarchy"
touch "$fake_root$hierarchy/should-succeed-on-mutable-fs"
-SYSTEMD_SYSEXT_HIERARCHIEe="$hierarchy" systemd-sysext --root="$fake_root" merge
+run_systemd_sysext "$fake_root" merge
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
touch "$fake_root$hierarchy/should-succeed-on-mutable-fs-again"
+)
+( init_trap
: "No extension data in /var/lib/extensions.mutable/…, no hierarchy either, mutability disabled by default, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-missing-hierarchy"
+fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-missing-hierarchy"}
hierarchy=/opt
+move_existing_hierarchy_aside "$fake_root" "$hierarchy"
prepare_root "$fake_root" "$hierarchy"
rmdir "$fake_root/$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge
+run_systemd_sysext "$fake_root" merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy"
+)
+( init_trap
: "No extension data in /var/lib/extensions.mutable/…, empty hierarchy, mutability disabled by default, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-empty-hierarchy"
+fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-empty-hierarchy"}
hierarchy=/opt
+move_existing_hierarchy_aside "$fake_root" "$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
make_read_only "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge
+run_systemd_sysext "$fake_root" merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy"
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability disabled by default, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-read-only-hierarchy-disabled"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-read-only-hierarchy-disabled"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge
+run_systemd_sysext "$fake_root" merge
(! touch "$fake_root$hierarchy/should-be-read-only")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, auto-mutability, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-read-only-hierarchy"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-read-only-hierarchy"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge
+run_systemd_sysext "$fake_root" --mutable=auto merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
test -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, missing hierarchy, auto-mutability, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-missing-hierarchy"
+fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-missing-hierarchy"}
hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
+move_existing_hierarchy_aside "$fake_root" "$hierarchy"
prepare_root "$fake_root" "$hierarchy"
rmdir "$fake_root/$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_data_dir"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge
+run_systemd_sysext "$fake_root" --mutable=auto merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -u
test -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy"
test -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, empty hierarchy, auto-mutability, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-empty-hierarchy"
+fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-empty-hierarchy"}
hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
+move_existing_hierarchy_aside "$fake_root" "$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_data_dir"
make_read_only "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge
+run_systemd_sysext "$fake_root" --mutable=auto merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -u
test -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy"
test -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "/var/lib/extensions.mutable/… is a symlink to other dir, R/O hierarchy, auto-mutability, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/mutable-symlink-with-read-only-hierarchy"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/mutable-symlink-with-read-only-hierarchy"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
extension_real_dir="$fake_root/upperdir"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_real_dir"
ln -sfTr "$extension_real_dir" "$extension_data_dir"
+prepend_trap "rm -f ${extension_data_dir@Q}"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge
+run_systemd_sysext "$fake_root" --mutable=auto merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
test -f "$extension_data_dir/now-is-mutable"
test -f "$extension_real_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test -f "$extension_data_dir/now-is-mutable"
test -f "$extension_real_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "/var/lib/extensions.mutable/… is a symlink to the hierarchy itself, mutable hierarchy, auto-mutability, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/mutable-self-upper"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/mutable-self-upper"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
extension_real_dir="$fake_root$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_real_dir"
ln -sfTr "$extension_real_dir" "$extension_data_dir"
+prepend_trap "rm -f ${extension_data_dir@Q}"
touch "$fake_root$hierarchy/preexisting-file-in-hierarchy"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge
+run_systemd_sysext "$fake_root" --mutable=auto merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
test -f "$extension_data_dir/now-is-mutable"
test -f "$extension_real_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h -u
test -f "$extension_data_dir/now-is-mutable"
test -f "$extension_real_dir/now-is-mutable"
+)
+( init_trap
: "/var/lib/extensions.mutable/… is a symlink to the hierarchy itself, R/O hierarchy, auto-mutability, expected fail"
-fake_root="$FAKE_ROOTS_DIR/failure-self-upper-ro"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/failure-self-upper-ro"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
extension_real_dir="$fake_root$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_real_dir"
ln -sfTr "$extension_real_dir" "$extension_data_dir"
+prepend_trap "rm -f ${extension_data_dir@Q}"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
-(! SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge)
+(! run_systemd_sysext "$fake_root" --mutable=auto merge)
+)
+( init_trap
: "/var/lib/extensions.mutable/… is a dangling symlink, auto-mutability, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/read-only-mutable-dangling-symlink"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/read-only-mutable-dangling-symlink"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
ln -sfTr "/should/not/exist/" "$extension_data_dir"
+prepend_trap "rm -f ${extension_data_dir@Q}"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-# run systemd-sysext
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge
+run_systemd_sysext "$fake_root" --mutable=auto merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+( init_trap
: "/var/lib/extensions.mutable/… exists but ignored, mutability disabled explicitly, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/disabled"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/disabled"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-# run systemd-sysext
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=no merge
+run_systemd_sysext "$fake_root" --mutable=no merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+( init_trap
: "/var/lib/extensions.mutable/… exists but is imported instead, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/imported"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/imported"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-# run systemd-sysext
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=import merge
+run_systemd_sysext "$fake_root" --mutable=import merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
-: "/var/lib/extensions.mutable/… doesn't exists, mutability enabled, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/enabled"
-hierarchy=/usr
+( init_trap
+: "/var/lib/extensions.mutable/… does not exist, mutability enabled, mutable merged"
+fake_root=${roots_dir:+"$roots_dir/enabled"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
+extension_data_dir_usr="$fake_root/var/lib/extensions.mutable/usr"
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
test ! -d "$extension_data_dir"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=yes merge
+run_systemd_sysext "$fake_root" --mutable=yes merge
+# systemd-sysext with --mutable=yes creates extensions.mutable directory for
+# the hierarchy, so delete it after the test
+prepend_trap "rm -rf ${extension_data_dir@Q}"
+# systemd-sysext with --mutable=yes creates extensions.mutable directory also
+# for the /usr hierarchy, because the image needs to have
+# /usr/lib/extension-release.d/extension-release.<NAME> file - this causes the
+# /usr hierarchy to also become mutable
+prepend_trap "rm -rf ${extension_data_dir_usr@Q}"
test -d "$extension_data_dir"
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
test -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
-: "/var/lib/extensions.mutable/… doesn't exists, auto-mutability, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/simple-read-only-explicit"
-hierarchy=/usr
+( init_trap
+: "/var/lib/extensions.mutable/… does not exist, auto-mutability, read-only merged"
+fake_root=${roots_dir:+"$roots_dir/simple-read-only-explicit"}
+hierarchy=/opt
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge
+run_systemd_sysext "$fake_root" --mutable=auto merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
-: "/var/lib/extensions.mutable/… doesn't exists, mutability enabled through env var, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/enabled-env-var"
-hierarchy=/usr
+( init_trap
+: "/var/lib/extensions.mutable/… does not exist, mutability enabled through env var, mutable merged"
+fake_root=${roots_dir:+"$roots_dir/enabled-env-var"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
+extension_data_dir_usr="$fake_root/var/lib/extensions.mutable/usr"
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
test ! -d "$extension_data_dir"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" merge
+# systemd-sysext with --mutable=yes creates extensions.mutable directory for
+# the hierarchy, so delete it after the test
+prepend_trap "rm -rf ${extension_data_dir@Q}"
+# systemd-sysext with --mutable=yes creates extensions.mutable directory also
+# for the /usr hierarchy, because the image needs to have
+# /usr/lib/extension-release.d/extension-release.<NAME> file - this causes the
+# /usr hierarchy to also become mutable
+prepend_trap "rm -rf ${extension_data_dir_usr@Q}"
test -d "$extension_data_dir"
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
test -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
-: "/var/lib/extensions.mutable/… doesn't exists, auto-mutability enabled through env var, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/read-only-auto-env-var"
-hierarchy=/usr
+( init_trap
+: "/var/lib/extensions.mutable/… does not exist, auto-mutability enabled through env var, read-only merged"
+fake_root=${roots_dir:+"$roots_dir/read-only-auto-env-var"}
+hierarchy=/opt
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" --mutable=auto merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" --mutable=auto merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, auto-mutability enabled through env var, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/auto-mutable-env-var"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/auto-mutable-env-var"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
test -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability disabled through env var, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/env-var-disabled"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/env-var-disabled"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=no systemd-sysext --root="$fake_root" merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=no run_systemd_sysext "$fake_root" merge
(! touch "$fake_root$hierarchy/should-be-read-only")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=no systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=no run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+( init_trap
: "/var/lib/extensions.mutable/… exists but is imported through env var, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/imported-env-var"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/imported-env-var"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=import systemd-sysext --root="$fake_root" merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=import run_systemd_sysext "$fake_root" merge
(! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=import systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=import run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability enabled through env var but overridden via CLI option, read-only merged"
-fake_root="$FAKE_ROOTS_DIR/env-var-overridden"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/env-var-overridden"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" --mutable=no merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" --mutable=no merge
(! touch "$fake_root$hierarchy/should-be-read-only")
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral mutability, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/ephemeral"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/ephemeral"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=ephemeral merge
+run_systemd_sysext "$fake_root" --mutable=ephemeral merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
test ! -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test ! -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral mutability through env var, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/ephemeral-env-var"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/ephemeral-env-var"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral systemd-sysext --root="$fake_root" merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral run_systemd_sysext "$fake_root" merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
test ! -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test ! -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral import mutability, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/ephemeral-import"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/ephemeral-import"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-# run systemd-sysext
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=ephemeral-import merge
+run_systemd_sysext "$fake_root" --mutable=ephemeral-import merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
test ! -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge
+run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test ! -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral import mutability through env var, mutable merged"
-fake_root="$FAKE_ROOTS_DIR/ephemeral-import-env-var"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/ephemeral-import-env-var"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
prepare_root "$fake_root" "$hierarchy"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
(! touch "$fake_root$hierarchy/should-fail-on-read-only-fs")
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import systemd-sysext --root="$fake_root" merge
+SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import run_systemd_sysext "$fake_root" merge
touch "$fake_root$hierarchy/now-is-mutable"
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
test ! -f "$extension_data_dir/now-is-mutable"
-SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import systemd-sysext --root="$fake_root" unmerge
+SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
test ! -f "$extension_data_dir/now-is-mutable"
test ! -f "$fake_root$hierarchy/now-is-mutable"
+)
+( init_trap
: "Extension data pointing to mutable hierarchy, ephemeral import mutability, expected fail"
-fake_root="$FAKE_ROOTS_DIR/ephemeral-import-self"
-hierarchy=/usr
+fake_root=${roots_dir:+"$roots_dir/ephemeral-import-self"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
extension_real_dir="$fake_root$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_real_dir"
ln -sfTr "$extension_real_dir" "$extension_data_dir"
+prepend_trap "rm -f ${extension_data_dir@Q}"
prepare_hierarchy "$fake_root" "$hierarchy"
touch "$fake_root$hierarchy/should-succeed-on-read-only-fs"
-(! SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=ephemeral-import merge)
+(! run_systemd_sysext "$fake_root" --mutable=ephemeral-import merge)
+)
-: "Extension data pointing to mutable hierarchy, import mutability, expected fail"
-fake_root="$FAKE_ROOTS_DIR/import-self"
-hierarchy=/usr
+( init_trap
+: "Extension data pointing to mutable hierarchy, import mutability, expected fail"
+fake_root=${roots_dir:+"$roots_dir/import-self"}
+hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
extension_real_dir="$fake_root$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_real_dir"
ln -sfTr "$extension_real_dir" "$extension_data_dir"
+prepend_trap "rm -f ${extension_data_dir@Q}"
prepare_hierarchy "$fake_root" "$hierarchy"
touch "$fake_root$hierarchy/should-succeed-on-read-only-fs"
-(! SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=import merge)
+(! run_systemd_sysext "$fake_root" --mutable=import merge)
+)
+
+
+for mutable_mode in no yes ephemeral; do
+ ( init_trap
+ : "Check if merging the hierarchy does not change its permissions, checking with --mutable=${mutable_mode}"
+
+ fake_root=${roots_dir:+"$roots_dir/perm-checks-mutable-$mutable_mode"}
+ hierarchy=/opt
+ extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
+
+ prepare_root "$fake_root" "$hierarchy"
+ prepare_extension_image "$fake_root" "$hierarchy"
+ prepare_extension_mutable_dir "$extension_data_dir"
+ prepare_read_only_hierarchy "${fake_root}" "${hierarchy}"
+
+ full_path="$fake_root$hierarchy"
+ permissions_before_merge=$(stat --format=%A "$full_path")
+
+ run_systemd_sysext "$fake_root" "--mutable=$mutable_mode" merge
+ if [[ $mutable_mode = yes ]]; then
+ # systemd-sysext with --mutable=yes creates extensions.mutable
+ # directory also for the /usr hierarchy, because the image needs to
+ # have /usr/lib/extension-release.d/extension-release.<NAME> file -
+ # this causes the /usr hierarchy to also become mutable
+ extension_data_dir_usr="$fake_root/var/lib/extensions.mutable/usr"
+ prepend_trap "rm -rf ${extension_data_dir_usr@Q}"
+ fi
+
+ permissions_after_merge=$(stat --format=%A "$full_path")
+
+ run_systemd_sysext "$fake_root" unmerge
+
+ permissions_after_unmerge=$(stat --format=%A "$full_path")
+
+ if [[ "$permissions_before_merge" != "$permissions_after_merge" ]]; then
+ echo >&2 "Broken hierarchy permissions after merging with mutable mode ${mutable_mode@Q}, expected ${permissions_before_merge@Q}, got ${permissions_after_merge@Q}"
+ exit 1
+ fi
+
+ if [[ "$permissions_before_merge" != "$permissions_after_unmerge" ]]; then
+ echo >&2 "Broken hierarchy permissions after unmerging with mutable mode ${mutable_mode@Q}, expected ${permissions_before_merge@Q}, got ${permissions_after_unmerge@Q}"
+ exit 1
+ fi
+ )
+done
+
+
+( init_trap
+: "Check if merging fails in case of invalid mutable directory permissions"
+
+fake_root=${roots_dir:+"$roots_dir/mutable-directory-with-invalid-permissions"}
+hierarchy=/opt
+extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+prepare_extension_mutable_dir "$extension_data_dir"
+prepare_hierarchy "$fake_root" "$hierarchy"
+
+old_mode=$(stat --format '%#a' "$fake_root$hierarchy")
+chmod 0755 "$fake_root$hierarchy"
+prepend_trap "chmod ${old_mode@Q} ${fake_root@Q}${hierarchy@Q}"
+chmod 0700 "$extension_data_dir"
+
+(! run_systemd_sysext "$fake_root" --mutable=yes merge)
+)
+
+} # End of run_sysext_tests
+
+
+# For preparing /, we need mutable /usr/. If it is read only, skip running the
+# sysext tests on /.
+if [[ -w /usr ]]; then
+ run_sysext_tests ''
+fi
+run_sysext_tests "$FAKE_ROOTS_DIR"
+
exit 0
"$SD_PCRLOCK" lock-uki <"$SD_STUB"
fi
-PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=yes
+PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=query
# Repeat immediately (this call will have to reuse the nvindex, rather than create it)
"$SD_PCRLOCK" make-policy --pcr="$PCRS"
"$SD_PCRLOCK" make-policy --pcr="$PCRS" --force
# work.
echo -n test70 | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/910-test70.pcrlock --pcr=16
(! "$SD_PCRLOCK" make-policy --pcr="$PCRS")
-PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=yes
+PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=query
systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless
systemd-cryptsetup detach pcrlock
# And now let's do it the clean way, and generate the right policy ahead of time.
echo -n test70-take-two | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/920-test70.pcrlock --pcr=16
"$SD_PCRLOCK" make-policy --pcr="$PCRS"
+# the next one should be skipped because redundant
+"$SD_PCRLOCK" make-policy --pcr="$PCRS"
+# but this one should not be skipped, even if redundant, because we force it
+"$SD_PCRLOCK" make-policy --pcr="$PCRS" --force --recovery-pin=show
"$SD_PCREXTEND" --pcr=16 test70-take-two