ARGS=(
"--optimization=0 -Dopenssl=disabled -Dcryptolib=gcrypt -Ddns-over-tls=gnutls -Dtpm=true -Dtpm2=enabled"
"--optimization=s -Dutmp=false"
+ "--optimization=2 -Dc_args=-Wmaybe-uninitialized -Ddns-over-tls=openssl"
"--optimization=3 -Db_lto=true -Ddns-over-tls=false"
"--optimization=3 -Db_lto=false -Dtpm2=disabled -Dlibfido2=disabled -Dp11kit=disabled"
- "--optimization=3 -Ddns-over-tls=openssl"
"--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true"
"-Db_ndebug=true"
)
for args in "${ARGS[@]}"; do
SECONDS=0
+ if [[ "$COMPILER" == clang && "$args" =~ Wmaybe-uninitialized ]]; then
+ # -Wmaybe-uninitialized is not implemented in clang
+ continue
+ fi
+
info "Checking build with $args"
# shellcheck disable=SC2086
if ! AR="$AR" \
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- - uses: systemd/mkosi@bbe715f42911f9660712377a5b39335b9391ae22
+ - uses: systemd/mkosi@dbce89aabda438ba58080366631b2c242e365f21
- name: Configure
run: |
- tee mkosi.local.conf <<- EOF
+ tee mkosi.local.conf <<EOF
[Distribution]
Distribution=${{ matrix.distro }}
Release=${{ matrix.release }}
- EOF
- tee mkosi.conf.d/99-ci.conf <<- EOF
[Content]
Environment=CI_BUILD=1
SLOW_TESTS=true
+ [Host]
+ ToolsTree=default
+ ToolsTreeDistribution=fedora
+ QemuVsock=yes
+ # Sometimes we run on a host with /dev/kvm, but it is broken, so explicitly disable it
+ QemuKvm=no
+ Ephemeral=yes
+ EOF
+
+ # These should override the options from mkosi.conf so we put them in a dropin that's ordered later
+ # instead.
+ tee mkosi.conf.d/99-ci.conf <<EOF
[Host]
KernelCommandLineExtra=systemd.unit=mkosi-check-and-shutdown.service
systemd.journald.max_level_console=debug
udev.log_level=info
# Root device can take a long time to appear, so let's bump the timeout.
systemd.default_device_timeout_sec=180
- QemuVsock=yes
- # Sometimes we run on a host with /dev/kvm, but it is broken, so explicitly disable it
- QemuKvm=no
- Ephemeral=yes
EOF
# For erofs, we have to install linux-modules-extra-azure, but that doesn't match the running kernel
# version, so we can't load the erofs module. squashfs is a builtin module so we use that instead.
mkdir -p mkosi.images/system/mkosi.repart/10-usr.conf.d
- tee mkosi.images/system/mkosi.repart/10-usr.conf.d/squashfs.conf <<- EOF
+ tee mkosi.images/system/mkosi.repart/10-usr.conf.d/squashfs.conf <<EOF
[Partition]
Format=squashfs
EOF
# eventually times out. Override it to just shutdown immediately.
mkdir -p mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/emergency.service.d/
mkdir -p mkosi.images/system/mkosi.extra/usr/lib/systemd/system/emergency.service.d/
- tee mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/emergency.service.d/poweroff.conf <<- EOF
+ tee mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/emergency.service.d/poweroff.conf <<EOF
[Unit]
FailureAction=exit
[Service]
Features:
+* extend the smbios11 logic for passing credentials so that instead of passing
+ the credential data literally it can also just reference an AF_VSOCK CID/port
+ to read them from. This way the data doesn't remain in the SMBIOS blob during
+ runtime, but only in the credentials fs.
+
* add a new ExecStart= flag that inserts the configured user's shell as first
word in the command line. (maybe use character '.'). Usecase: tool such as
uid0 can use that to spawn the target user's default shell.
* use udev rule networkd ownership property to take ownership of network
interfaces nspawn creates
+* support encrypted credentials in user context too. This is complicated by the
+ fact that the user does not have access to the TPM nor the system
+ credential. Implementation idea: extend the systemd-creds Varlink interface
+ to allow this: user must supply some per-user secret, that we'll include in
+ the encryption key.
+
* add a kernel cmdline switch (and cred?) for marking a system to be
"headless", in which case we never open /dev/console for reading, only for
writing. This would then mean: systemd-firstboot would process creds but not
- coredumpcl
- systemd-bless-boot
- systemd-measure
- - systemd-creds (allowing clients to encrypt credentials locally)
- systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such)
- systemd-dissect
- systemd-sysupdate
- systemd-pcrlock (to allow fwupd to relax policy)
- kernel-install
-* Varlink: add glue code to allow varlink clients to be authenticated via
- Polkit by passing client pidfd over.
-
* in the service manager, pick up ERRNO= + BUSERROR= + VARLINKERROR= error
identifiers, and store them along with the exit status of a server and report
via "systemctl status".
- sd_bus_creds
- unit_attach_pid_to_cgroup_via_bus()
- cg_attach() – requires new kernel feature
- - varlink_get_peer_pid()
* ddi must be listed as block device fstype
line, and then generate a mount unit for it using a udev generated symlink
based on lo_file_name.
+* teach systemd-nspawn the boot assessment logic: hook up vpick's try counters
+ with success notifications from nspawn payloads. When this is enabled,
+ automatically support reverting back to older OS version images if newer ones
+ fail to boot.
+
+* implement new "systemd-fsrebind" tool that works like gpt-auto-generator but
+ looks at a root dir and then applies vpick on various dirs/images to pick a
+ root tree, a /usr/ tree, a /home/, a /srv/, a /var/ tree and so on. Dirs
+ could also be btrfs subvols (combine with btrfs auto-snapshort approach for
+ creating versions like these automatically).
+
* remove tomoyo support, it's obsolete and unmaintained apparently
* In .socket units, add ConnectStream=, ConnectDatagram=,
* automatic boot assessment: add one more default success check that just waits
for a bit after boot, and blesses the boot if the system stayed up that long.
-* implement concept of "versioned" resources inside a dir, and write a spec for
- it. Make all tools in systemd, in particular
- RootImage=/RootDirectory=/--image=/--directory= implement this. Idea:
- directories ending in ".v/" indicate a directory with versioned resources in
- them. Versioned resources inside a .v dir are always named in the pattern
- <prefix>_<version>[+<tries-left>[-<tries-done>]].<suffix>
-
-* add support for using this .v/ logic on the root fs itself: in the initrd,
- after mounting the rootfs, look for root-<arch>.v/ in the root fs, and then
- apply the logic, moving the switch root logic there.
-
* systemd-repart: add support for generating ISO9660 images
* systemd-repart: in addition to the existing "factory reset" mode (which
would just use the same public key specified with --public-key= (or the one
automatically derived from --private-key=).
-* push people to use ".sysext.raw" as suffix for sysext DDIs (DDI =
- discoverable disk images, i.e. the new name for gpt disk images following the
- discoverable disk spec). [Also: just ".sysext/" for directory-based sysext]
-
* Add "purpose" flag to partition flags in discoverable partition spec that
indicate if partition is intended for sysext, for portable service, for
booting and so on. Then, when dissecting DDI allow specifying a purpose to
should probably also one you can use to get a remote attestation quote.
* Process credentials in:
- • networkd/udevd: add a way to define additional .link, .network, .netdev files
- via the credentials logic.
• crypttab-generator: allow defining additional crypttab-like volumes via
credentials (similar: verity-generator, integrity-generator). Use
fstab-generator logic as inspiration.
passwords, not just the first. i.e. if there are multiple defined, prefer
unlocked over locked and prefer non-empty over empty.
-* maybe add a tool inspired by the GPT auto discovery spec that runs in the
- initrd and rearranges the rootfs hierarchy via bind mounts, if
- enabled. Specifically in some top-level dir /@auto/ it will look for
- dirs/symlinks/subvolumes that are named after their purpose, and optionally
- encode a version as well as assessment counters, and then mount them into the
- file system tree to boot into, similar to how we do that for the gpt auto
- logic. Maybe then bind mount the original root into /.superior or something
- like that (so that update tools can look there). Further discussion in this
- thread:
- https://lists.freedesktop.org/archives/systemd-devel/2021-November/047059.html
- The GPT dissection logic should automatically enable this tool whenever we
- detect a specially marked root fs (i.e introduce a new generic root gpt type
- for this, that is arch independent). The also implement this in the image
- dissection logic, so that nspawn/RootImage= and so on grok it. Maybe make
- generic enough so that it can also work for ostrees arrangements.
-
-* if a path ending in ".auto.d/" is set for RootDirectory=/RootImage= then do a
- strverscmp() of everything inside that dir and use that. i.e. implement very
- simple version control. Also use this in systemd-nspawn --image= and so on.
-
* homed: while a home dir is not activated generate slightly different NSS
records for it, that reports the home dir as "/" and the shell as some binary
provided by us. Then, when an SSH login happens and SSH permits it our binary
- acquire + decrypt creds from pkcs11?
- make systemd-cryptsetup acquire pw via creds logic
- make PAMName= acquire pw via creds logic
- - make macsec/wireguard code in networkd read key via creds logic
- - make gatwayd/remote read key via creds logic
+ - make macsec code in networkd read key via creds logic (copy logic from
+ wireguard)
+ - make gatewayd/remote read key via creds logic
- add sd_notify() command for flushing out creds not needed anymore
- make user manager instances create and use a user-specific key (the one in
/var/lib is root-only) and add --user switch to systemd-creds to use it
* man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted.
-* load .d/*.conf dropins for device units
-
* There's currently no way to cancel fsck (used to be possible via C-c or c on the console)
* add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/
* systemctl:
- add systemctl switch to dump transaction without executing it
- Add a verbose mode to "systemctl start" and friends that explains what is being done or not done
- - "systemctl disable" on a static unit prints no message and does
- nothing. "systemctl enable" does nothing, and gives a bad message
- about it. Should fix both to print nice actionable messages.
- print nice message from systemctl --failed if there are no entries shown, and hook that into ExecStartPre of rescue.service/emergency.service
- add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible
- systemctl enable: fail if target to alias into does not exist? maybe show how many units are enabled afterwards?
- systemctl: "Journal has been rotated since unit was started." message is misleading
- - systemctl status output should include list of triggering units and their status
* introduce an option (or replacement) for "systemctl show" that outputs all
properties as JSON, similar to busctl's new JSON output. In contrast to that
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+@@
+/* Avoid running this transformation on the mfree function itself */
+position p : script:python() { p[0].current_element != "mfree" };
+expression e;
+@@
+- free@p(e);
+- return NULL;
++ return mfree(e);
+
@@
expression p;
@@
- free(p);
- p = NULL;
+ p = mfree(p);
+
+@@
+expression p;
+@@
+- if (p)
+- free(p);
++ free(p);
+
+@@
+expression p;
+@@
+- if (p)
+- mfree(p);
++ free(p);
+
+@@
+expression p;
+@@
+- mfree(p);
++ free(p);
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-@@
-/* Avoid running this transformation on the mfree function itself */
-position p : script:python() { p[0].current_element != "mfree" };
-expression e;
-@@
-- free@p(e);
-- return NULL;
-+ return mfree(e);
devices sysfs path are actually backed by sysfs. Relaxing this verification
is useful for testing purposes.
+* `$SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC=` — Specifies an extra timespan that the
+ udev manager process waits for a worker process kills slow programs specified
+ by IMPORT{program}=, PROGRAM=, or RUN=, and finalizes the processing event.
+ If the worker process cannot finalize the event within the specified timespan,
+ the worker process is killed by the manager process. Defaults to 10 seconds,
+ maximum allowed is 5 hours.
+
`udevadm` and `systemd-hwdb`:
* `SYSTEMD_HWDB_UPDATE_BYPASS=` — If set to "1", execution of hwdb updates is skipped
latter two via the environment variable unless `systemd-storagetm` is invoked
to expose a single device only, since those identifiers better should be kept
unique.
+
+Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as
+`busctl`):
+
+* `$SYSTEMD_SSH` – the ssh binary to invoke when the `ssh:` transport is
+ used. May be a filename (which is searched for in `$PATH`) or absolute path.
sensor:modalias:acpi:KIOX000A*:dmi:*:svnTECLAST:pnX98PlusII:*
ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
+# Teclast X98 Pro
+sensor:modalias:acpi:BMA250E*:dmi:*:svnTECLAST:pnX98Pro:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
#########################################
# Thundersoft
#########################################
<xi:include href="version-info.xml" xpointer="v217"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-T</option></term>
+ <term><option>--exclude-identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
+
+ <listitem><para>Exclude messages for the specified syslog identifier
+ <replaceable>SYSLOG_IDENTIFIER</replaceable>.</para>
+
+ <para>This parameter can be specified multiple times.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-p</option></term>
<term><option>--priority=</option></term>
<varlistentry>
<term><command>show-session</command> <optional><replaceable>ID</replaceable>…</optional></term>
- <listitem><para>Show properties of one or more sessions or the
- manager itself. If no argument is specified, properties of the
- manager will be shown. If a session ID is specified,
- properties of the session are shown. By default, empty
- properties are suppressed. Use <option>--all</option> to show
- those too. To select specific properties to show, use
- <option>--property=</option>. This command is intended to be
- used whenever computer-parsable output is required. Use
- <command>session-status</command> if you are looking for
- formatted human-readable output.</para>
+ <listitem><para>Show properties of one or more sessions or the manager itself. If no argument is
+ specified, properties of the manager will be shown. If a session ID is specified, properties of
+ the session are shown. Specially, if the given ID is <literal>self</literal>, the session to which
+ the <command>loginctl</command> process belongs is used. If <literal>auto</literal>, the current
+ session is used as with <literal>self</literal> if exists, and falls back to the current user's
+ graphical session. By default, empty properties are suppressed. Use <option>--all</option> to show
+ those too. To select specific properties to show, use <option>--property=</option>. This command
+ is intended to be used whenever computer-parsable output is required. Use <command>session-status</command>
+ if you are looking for formatted human-readable output.</para>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--kill-whom=</option></term>
- <listitem><para>When used with
- <command>kill-session</command>, choose which processes to
- kill. Must be one of <option>leader</option>, or
- <option>all</option> to select whether to kill only the leader
- process of the session or all processes of the session. If
- omitted, defaults to <option>all</option>.</para>
+ <listitem><para>When used with <command>kill-session</command>, choose which processes to kill.
+ Takes one of <literal>leader</literal> or <literal>all</literal>, to select whether to kill only
+ the leader process of the session or all processes of the session. If omitted, defaults to
+ <option>all</option>.</para>
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
<term><varname>StopIdleSessionSec=</varname></term>
<listitem><para>Specifies a timeout in seconds, or a time span value after which
- <filename>systemd-logind</filename> checks the idle state of all sessions. Every session that is idle for
- longer then the timeout will be stopped. Defaults to <literal>infinity</literal>
- (<filename>systemd-logind</filename> is not checking the idle state of sessions). For details about the syntax
- of time spans, see
+ <filename>systemd-logind</filename> checks the idle state of all sessions. Every session that is idle
+ for longer than the timeout will be stopped. Note that this option doesn't apply to
+ <literal>greeter</literal> or <literal>lock-screen</literal> sessions. Defaults to
+ <literal>infinity</literal> (<filename>systemd-logind</filename> is not checking the idle state
+ of sessions). For details about the syntax of time spans, see
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
readonly ay MachineID = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly ay BootID = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly u VSockCID = ...;
};
interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... };
<!--property FirmwareDate is not documented!-->
- <!--property MachineID is not documented!-->
-
- <!--property BootID is not documented!-->
-
<!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.hostname1"/>
<variablelist class="dbus-property" generated="True" extra-ref="BootID"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="VSockCID"/>
+
<!--End of Autogenerated section-->
<para>Whenever the hostname or other metadata is changed via the daemon,
purpose of those properties is to allow remote clients to access this information over D-Bus. Local
clients can access the information directly.</para>
+ <para><varname>MachineID</varname> expose the 128bit machine ID, see
+ <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para>
+
+ <para><varname>BootID</varname> expose the 128bit boot ID, as per
+ <filename>/proc/sys/kernel/random/boot_id</filename>.</para>
+
+ <para><varname>VSockCID</varname> exposes the system's local <constant>AF_VSOCK</constant> CID (Context
+ Identifier, i.e. address) for the system, if one is available in the virtual machine environment. Set to
+ <constant>UINT32_MAX</constant> otherwise. See <citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details.</para>
+
<refsect2>
<title>Methods</title>
<para><varname>OperatingSystemSupportEnd</varname>,
<varname>FirmwareVendor</varname>, and
<varname>FirmwareDate</varname> were added in version 253.</para>
- <para><varname>MachineID</varname>, and
- <varname>BootID</varname> were added in version 256.</para>
+ <para><varname>MachineID</varname>, <varname>BootID</varname> and
+ <varname>VSockCID</varname> were added in version 256.</para>
</refsect2>
</refsect1>
</refentry>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryHigh = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t TasksCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveTasksMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressBytes = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressPackets = ...;
<!--property MemoryZSwapCurrent is not documented!-->
+ <!--property EffectiveMemoryMax is not documented!-->
+
+ <!--property EffectiveMemoryHigh is not documented!-->
+
<!--property CPUUsageNSec is not documented!-->
<!--property EffectiveCPUs is not documented!-->
<!--property TasksCurrent is not documented!-->
+ <!--property EffectiveTasksMax is not documented!-->
+
<!--property IPIngressBytes is not documented!-->
<!--property IPIngressPackets is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryMax"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryHigh"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
<variablelist class="dbus-property" generated="True" extra-ref="TasksCurrent"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveTasksMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressBytes"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressPackets"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryHigh = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t TasksCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveTasksMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressBytes = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressPackets = ...;
<!--property MemoryZSwapCurrent is not documented!-->
+ <!--property EffectiveMemoryMax is not documented!-->
+
+ <!--property EffectiveMemoryHigh is not documented!-->
+
<!--property CPUUsageNSec is not documented!-->
<!--property EffectiveCPUs is not documented!-->
<!--property TasksCurrent is not documented!-->
+ <!--property EffectiveTasksMax is not documented!-->
+
<!--property IPIngressBytes is not documented!-->
<!--property IPIngressPackets is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryMax"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryHigh"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
<variablelist class="dbus-property" generated="True" extra-ref="TasksCurrent"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveTasksMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressBytes"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressPackets"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryHigh = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t TasksCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveTasksMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressBytes = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressPackets = ...;
<!--property MemoryZSwapCurrent is not documented!-->
+ <!--property EffectiveMemoryMax is not documented!-->
+
+ <!--property EffectiveMemoryHigh is not documented!-->
+
<!--property CPUUsageNSec is not documented!-->
<!--property EffectiveCPUs is not documented!-->
<!--property TasksCurrent is not documented!-->
+ <!--property EffectiveTasksMax is not documented!-->
+
<!--property IPIngressBytes is not documented!-->
<!--property IPIngressPackets is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryMax"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryHigh"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
<variablelist class="dbus-property" generated="True" extra-ref="TasksCurrent"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveTasksMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressBytes"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressPackets"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryHigh = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t TasksCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveTasksMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressBytes = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressPackets = ...;
<!--property MemoryZSwapCurrent is not documented!-->
+ <!--property EffectiveMemoryMax is not documented!-->
+
+ <!--property EffectiveMemoryHigh is not documented!-->
+
<!--property CPUUsageNSec is not documented!-->
<!--property EffectiveCPUs is not documented!-->
<!--property TasksCurrent is not documented!-->
+ <!--property EffectiveTasksMax is not documented!-->
+
<!--property IPIngressBytes is not documented!-->
<!--property IPIngressPackets is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryMax"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryHigh"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
<variablelist class="dbus-property" generated="True" extra-ref="TasksCurrent"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveTasksMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressBytes"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressPackets"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryHigh = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t TasksCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveTasksMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressBytes = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressPackets = ...;
<!--property MemoryZSwapCurrent is not documented!-->
+ <!--property EffectiveMemoryMax is not documented!-->
+
+ <!--property EffectiveMemoryHigh is not documented!-->
+
<!--property CPUUsageNSec is not documented!-->
<!--property EffectiveCPUs is not documented!-->
<!--property TasksCurrent is not documented!-->
+ <!--property EffectiveTasksMax is not documented!-->
+
<!--property IPIngressBytes is not documented!-->
<!--property IPIngressPackets is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryMax"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryHigh"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
<variablelist class="dbus-property" generated="True" extra-ref="TasksCurrent"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveTasksMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressBytes"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressPackets"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveMemoryHigh = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t TasksCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t EffectiveTasksMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressBytes = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IPIngressPackets = ...;
<!--property MemoryZSwapCurrent is not documented!-->
+ <!--property EffectiveMemoryMax is not documented!-->
+
+ <!--property EffectiveMemoryHigh is not documented!-->
+
<!--property CPUUsageNSec is not documented!-->
<!--property EffectiveCPUs is not documented!-->
<!--property TasksCurrent is not documented!-->
+ <!--property EffectiveTasksMax is not documented!-->
+
<!--property IPIngressBytes is not documented!-->
<!--property IPIngressPackets is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryMax"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveMemoryHigh"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
<variablelist class="dbus-property" generated="True" extra-ref="TasksCurrent"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="EffectiveTasksMax"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressBytes"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPIngressPackets"/>
<varname>MemorySwapCurrent</varname>,
<varname>MemorySwapPeak</varname>, and
<varname>MemoryZSwapCurrent</varname> were added in version 255.</para>
+ <para><varname>EffectiveMemoryHigh</varname>,
+ <varname>EffectiveMemoryMax</varname>,
+ <varname>EffectiveTasksMax</varname> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>
<varname>MemorySwapCurrent</varname>,
<varname>MemorySwapPeak</varname>, and
<varname>MemoryZSwapCurrent</varname> were added in version 255.</para>
+ <para><varname>EffectiveMemoryHigh</varname>,
+ <varname>EffectiveMemoryMax</varname>,
+ <varname>EffectiveTasksMax</varname> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Mount Unit Objects</title>
<varname>MemorySwapCurrent</varname>,
<varname>MemorySwapPeak</varname>, and
<varname>MemoryZSwapCurrent</varname> were added in version 255.</para>
+ <para><varname>EffectiveMemoryHigh</varname>,
+ <varname>EffectiveMemoryMax</varname>,
+ <varname>EffectiveTasksMax</varname> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Swap Unit Objects</title>
<varname>MemorySwapCurrent</varname>,
<varname>MemorySwapPeak</varname>, and
<varname>MemoryZSwapCurrent</varname> were added in version 255.</para>
+ <para><varname>EffectiveMemoryHigh</varname>,
+ <varname>EffectiveMemoryMax</varname>,
+ <varname>EffectiveTasksMax</varname> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Slice Unit Objects</title>
<varname>MemorySwapCurrent</varname>,
<varname>MemorySwapPeak</varname>, and
<varname>MemoryZSwapCurrent</varname> were added in version 255.</para>
+ <para><varname>EffectiveMemoryHigh</varname>,
+ <varname>EffectiveMemoryMax</varname>,
+ <varname>EffectiveTasksMax</varname> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Scope Unit Objects</title>
<varname>MemorySwapCurrent</varname>,
<varname>MemorySwapPeak</varname>, and
<varname>MemoryZSwapCurrent</varname> were added in version 255.</para>
+ <para><varname>EffectiveMemoryHigh</varname>,
+ <varname>EffectiveMemoryMax</varname>,
+ <varname>EffectiveTasksMax</varname> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Job Objects</title>
'HAVE_LIBCRYPTSETUP'],
['systemd-vmspawn', '1', [], 'ENABLE_VMSPAWN'],
['systemd-volatile-root.service', '8', ['systemd-volatile-root'], ''],
+ ['systemd-vpick', '1', [], ''],
['systemd-xdg-autostart-generator', '8', [], 'ENABLE_XDG_AUTOSTART'],
['systemd', '1', ['init'], ''],
['systemd.automount', '5', [], ''],
['systemd.time', '7', [], ''],
['systemd.timer', '5', [], ''],
['systemd.unit', '5', [], ''],
+ ['systemd.v', '7', [], ''],
['sysupdate.d', '5', [], 'ENABLE_SYSUPDATE'],
['sysusers.d', '5', [], 'ENABLE_SYSUSERS'],
['telinit', '8', [], 'HAVE_SYSV_COMPAT'],
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">verify</arg>
- <arg choice="opt" rep="repeat"><replaceable>FILE</replaceable></arg>
+ <arg choice="plain" rep="repeat"><replaceable>FILE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">security</arg>
- <arg choice="plain" rep="repeat"><replaceable>UNIT</replaceable></arg>
+ <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">fdstore</arg>
- <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
+ <arg choice="plain" rep="repeat"><replaceable>UNIT</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
</refsect2>
<refsect2>
- <title><command>systemd-analyze fdstore <optional><replaceable>UNIT</replaceable>...</optional></command></title>
+ <title><command>systemd-analyze fdstore <replaceable>UNIT</replaceable>...</command></title>
<para>Lists the current contents of the specified service unit's file descriptor store. This shows
names, inode types, device numbers, inode numbers, paths and open modes of the open file
</refsect2>
<refsect2>
- <title><command>systemd-analyze image-policy <optional><replaceable>POLICY</replaceable>…</optional></command></title>
+ <title><command>systemd-analyze image-policy <replaceable>POLICY</replaceable>…</command></title>
<para>This command analyzes the specified image policy string, as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
<xi:include href="user-system-options.xml" xpointer="machine" />
<varlistentry>
+ <term><option>-q</option></term>
<term><option>--quiet</option></term>
<listitem><para>Suppress hints and other non-essential output.</para>
<listitem><para>When specified with the <command>encrypt</command> command controls the
encryption/signature key to use. Takes one of <literal>host</literal>, <literal>tpm2</literal>,
- <literal>host+tpm2</literal>, <literal>tpm2-absent</literal>, <literal>auto</literal>,
+ <literal>host+tpm2</literal>, <literal>null</literal>, <literal>auto</literal>,
<literal>auto-initrd</literal>. See above for details on the three key types. If set to
<literal>auto</literal> (which is the default) the TPM2 key is used if a TPM2 device is found and not
running in a container. The host key is used if <filename>/var/lib/systemd/</filename> is on
chip and the OS installation, and both need to be available to decrypt the credential again. If
<literal>auto</literal> is selected but neither TPM2 is available (or running in container) nor
<filename>/var/lib/systemd/</filename> is on persistent media, encryption will fail. If set to
- <literal>tpm2-absent</literal> a fixed zero length key is used (thus, in this mode no confidentiality
+ <literal>null</literal> a fixed zero length key is used (thus, in this mode no confidentiality
nor authenticity are provided!). This logic is useful to cover for systems that lack a TPM2 chip but
where credentials shall be generated. Note that decryption of such credentials is refused on systems
that have a TPM2 chip and where UEFI SecureBoot is enabled (this is done so that such a locked down
system cannot be tricked into loading a credential generated this way that lacks authentication
information). If set to <literal>auto-initrd</literal> a TPM2 key is used if a TPM2 is found. If not
- a fixed zero length key is used, equivalent to <literal>tpm2-absent</literal> mode. This option is
+ a fixed zero length key is used, equivalent to <literal>null</literal> mode. This option is
particularly useful to generate credentials files that are encrypted/authenticated against TPM2 where
available but still work on systems lacking support for this.</para>
<varlistentry>
<term><option>--pkcs11-token-uri=</option><replaceable>URI</replaceable></term>
- <listitem><para>Enroll a PKCS#11 security token or smartcard (e.g. a YubiKey). Expects a PKCS#11
- smartcard URI referring to the token. Alternatively the special value <literal>auto</literal> may
- be specified, in order to automatically determine the URI of a currently plugged in security token
- (of which there must be exactly one). The special value <literal>list</literal> may be used to
- enumerate all suitable PKCS#11 tokens currently plugged in.</para>
+ <listitem><para>Enroll a PKCS#11 security token or smartcard (e.g. a YubiKey). Expects a PKCS#11 URI
+ that allows to find an X.509 certificate on the token. The URI must also be suitable to find
+ a related private key after changing the type of object in it. Alternatively the special value
+ <literal>auto</literal> may be specified, in order to automatically determine the suitable URI if
+ a single security token containing a single key pair is plugged in. The special value
+ <literal>list</literal> may be used to enumerate all suitable PKCS#11 tokens currently plugged in.
+ </para>
<para>The PKCS#11 token must contain an RSA or EC key pair which will be used to unlock a LUKS2 volume.
For RSA, a randomly generated volume key is encrypted with a public key in the token, and stored in
<row>
<entry><varname>apple</varname></entry>
- <entry><ulink url="https://developer.apple.com/documentation/virtualization">Apple Virtualization.framework</ulink></entry>
+ <entry><ulink url="https://developer.apple.com/documentation/virtualization">Apple virtualization framework</ulink></entry>
</row>
<row>
<entry><ulink url="https://www.lockheedmartin.com/en-us/products/Hardened-Security-for-Intel-Processors.html">LMHS SRE hypervisor</ulink></entry>
</row>
+ <row>
+ <entry><varname>google</varname></entry>
+ <entry><ulink url="https://cloud.google.com/compute">Google Compute Engine</ulink></entry>
+ </row>
+
<row>
<entry valign="top" morerows="9">Container</entry>
<entry><varname>openvz</varname></entry>
mounted directly by <command>mount</command> and <citerefentry
project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>. For
details see below.</para>
+
+ <para>In place of the image path a <literal>.v/</literal> versioned directory may be specified, see
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details.</para>
</refsect1>
<refsect1>
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><ulink url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable Partitions Specification</ulink></member>
<member><citerefentry project='man-pages'><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry project='man-pages'><refentrytitle>umount</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
for option syntax and details.</para>
</refsect1>
+ <refsect1>
+ <title>Credentials</title>
+
+ <para><command>systemd-network-generator</command> supports the service credentials logic as implemented
+ by
+ <varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details). The following credentials are used when passed in:</para>
+
+ <variablelist class='system-credentials'>
+ <varlistentry>
+ <term><varname>network.netdev.*</varname></term>
+ <term><varname>network.link.*</varname></term>
+ <term><varname>network.network.*</varname></term>
+
+ <listitem><para>These credentials should contain valid
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ configuration data. From each matching credential a separate file is created. Example: a passed
+ credential <filename>network.link.50-foobar</filename> will be copied into a configuration file
+ <filename>50-foobar.link</filename>.</para>
+
+ <para>Note that the resulting files are created world-readable, it's hence recommended to not include
+ secrets in these credentials, but supply them via separate credentials directly to
+ <filename>systemd-networkd.service</filename>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>Note that by default the <filename>systemd-network-generator.service</filename> unit file is set up
+ to inherit the these credentials from the service manager.</para>
+ </refsect1>
+
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<term><option>-D</option></term>
<term><option>--directory=</option></term>
- <listitem><para>Directory to use as file system root for the
- container.</para>
+ <listitem><para>Directory to use as file system root for the container.</para>
- <para>If neither <option>--directory=</option>, nor
- <option>--image=</option> is specified the directory is
- determined by searching for a directory named the same as the
- machine name specified with <option>--machine=</option>. See
+ <para>If neither <option>--directory=</option>, nor <option>--image=</option> is specified the
+ directory is determined by searching for a directory named the same as the machine name specified
+ with <option>--machine=</option>. See
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
section "Files and Directories" for the precise search path.</para>
- <para>If neither <option>--directory=</option>,
- <option>--image=</option>, nor <option>--machine=</option>
- are specified, the current directory will
- be used. May not be specified together with
- <option>--image=</option>.</para></listitem>
+ <para>In place of the directory path a <literal>.v/</literal> versioned directory may be specified, see
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details.</para>
+
+ <para>If neither <option>--directory=</option>, <option>--image=</option>, nor
+ <option>--machine=</option> are specified, the current directory will be used. May not be specified
+ together with <option>--image=</option>.</para></listitem>
</varlistentry>
<varlistentry>
<para>Any other partitions, such as foreign partitions or swap partitions are not mounted. May not be specified
together with <option>--directory=</option>, <option>--template=</option>.</para>
+ <para>In place of the image path a <literal>.v/</literal> versioned directory may be specified, see
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details.</para>
+
<xi:include href="version-info.xml" xpointer="v211"/></listitem>
</varlistentry>
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--purge</option></term>
+ <listitem><para>If this option is passed, all files and directories created by a
+ <filename>tmpfiles.d/</filename> entry will be deleted.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--user</option></term>
<listitem><para>Execute "user" configuration, i.e. <filename>tmpfiles.d</filename>
--- /dev/null
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd-vpick"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-vpick</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-vpick</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-vpick</refname>
+ <refpurpose>Resolve paths to <literal>.v/</literal> versioned directories</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-vpick <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">PATH</arg></command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-vpick</command> resolves a file system path referencing a <literal>.v/</literal>
+ versioned directory to a path to the newest (by version) file contained therein. This tool provides a
+ command line interface for the
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ logic.</para>
+
+ <para>The tool expects a path to a <literal>.v/</literal> directory as argument (either directly, or with
+ a triple underscore pattern as final component). It then determines the newest file contained in that
+ directory, and writes its path to standard output.</para>
+
+ <para>Unless the triple underscore pattern is passed as last component of the path, it is typically
+ necessary to at least specify the <option>--suffix=</option> switch to configure the file suffix to look
+ for.</para>
+
+ <para>If the specified path does not reference a <literal>.v/</literal> path (i.e. neither the final
+ component ends in <literal>.v</literal>, nor the penultimate does or the final one does contain a triple
+ underscore) it specified path is written unmodified to standard output.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--basename=</option></term>
+ <term><option>-B</option></term>
+
+ <listitem><para>Overrides the "basename" of the files to look for, i.e. the part to the left of the
+ variable part of the filenames. Normally this is derived automatically from the filename of the
+ <literal>.v</literal> component of the specified path, or from the triple underscore pattern in the
+ last component of the specified path.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-V</option></term>
+
+ <listitem><para>Explicitly configures the version to select. If specified, a filename with the
+ specified version string will be looked for, instead of the newest version
+ available.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-A</option></term>
+
+ <listitem><para>Explicitly configures the architecture to select. If specified, a filename with the
+ specified architecture identifier will be looked for. If not specified only filenames with a locally
+ supported architecture are considered, or those without any architecture identifier.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--suffix=</option></term>
+ <term><option>-S</option></term>
+
+ <listitem><para>Configures the suffix of the filenames to consider. For the <literal>.v/</literal>
+ logic it is necessary to specify the suffix to look for, and the <literal>.v/</literal> component
+ must also carry the suffix immediately before <literal>.v</literal> in its name.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--type=</option></term>
+ <term><option>-t</option></term>
+
+ <listitem><para>Configures the inode type to look for in the <literal>.v/</literal> directory. Takes
+ one of <literal>reg</literal>, <literal>dir</literal>, <literal>sock</literal>,
+ <literal>fifo</literal>, <literal>blk</literal>, <literal>chr</literal>, <literal>lnk</literal> as
+ argument, each identifying an inode type. See <citerefentry
+ project='man-pages'><refentrytitle>inode</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details about inode types. If this option is used inodes not matching the specified type are filtered
+ and not taken into consideration.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--print=</option></term>
+ <term><option>-p</option></term>
+
+ <listitem><para>Configures what precisely to write to standard output. If not specified prints the
+ full, resolved path of the newest matching file in the <literal>.v/</literal> directory. This switch can be set to one of the following:</para>
+
+ <itemizedlist>
+ <listitem><para>If set to <literal>filename</literal>, will print only the filename instead of the full path of the resolved file.</para></listitem>
+ <listitem><para>If set to <literal>version</literal>, will print only the version of the resolved file.</para></listitem>
+ <listitem><para>If set to <literal>type</literal>, will print only the inode type of the resolved
+ file (i.e. a string such as <literal>reg</literal> for regular files, or <literal>dir</literal> for
+ directories).</para></listitem>
+ <listitem><para>If set to <literal>arch</literal>, will print only the architecture of the resolved
+ file.</para></listitem>
+ <listitem><para>If set to <literal>tries</literal>, will print only the tries left/tries done of the
+ resolved file.</para></listitem>
+ <listitem><para>If set to <literal>all</literal>, will print all of the above in a simple tabular
+ output.</para></listitem>
+ </itemizedlist>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--resolve=</option></term>
+
+ <listitem><para>Takes a boolean argument. If true the path to the versioned file is fully
+ canonicalized (i.e. symlinks resolved, and redundant path components removed) before it is shown. If
+ false (the default) this is not done, and the path is shown without canonicalization.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>Use a command like the following to automatically pick the newest raw disk image from a
+ <literal>.v/</literal> directory:</para>
+
+ <programlisting>$ systemd-vpick --suffix=.raw --type=reg /var/lib/machines/quux.raw.v/</programlisting>
+
+ <para>This will enumerate all regular files matching
+ <filename>/var/lib/machines/quux.raw.v/quux*.raw</filename>, filter and sort them according to the rules
+ described in
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>, and then
+ write the path to the newest (by version) file to standard output.</para>
+
+ <para>Use a command like the following to automatically pick the newest OS directory tree from a
+ <literal>.v/</literal> directory:</para>
+
+ <programlisting>$ systemd-vpick --type=dir /var/lib/machines/waldo.v/</programlisting>
+
+ <para>This will enumerate all directory inodes matching
+ <filename>/var/lib/machines/waldo.v/waldo*</filename>, filter and sort them according to the rules
+ described in
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>, and then
+ write the path to the newest (by version) directory to standard output.</para>
+
+ <para>For further examples see
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><simplelist type="inline">
+ <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ </simplelist></para>
+ </refsect1>
+</refentry>
<programlisting>BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout</programlisting>
</example>
+ <para>In place of the directory path a <literal>.v/</literal> versioned directory may be specified,
+ see <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details.</para>
+
<xi:include href="system-or-user-ns.xml" xpointer="singular"/></listitem>
</varlistentry>
<citerefentry><refentrytitle>systemd-soft-reboot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>),
in case the service is configured to survive it.</para>
+ <para>In place of the image path a <literal>.v/</literal> versioned directory may be specified, see
+ <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details.</para>
+
<xi:include href="system-only.xml" xpointer="singular"/>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>
<listitem>
<para>A description of the device.</para>
- <xi:include href="version-info.xml" xpointer="v211"/>
+ <xi:include href="version-info.xml" xpointer="v211"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>Property=</varname></term>
+ <listitem>
+ <para>Set specified udev properties. This takes space separated list of key-value pairs
+ concatenated with equal sign (<literal>=</literal>). Example:
+ <programlisting>Property=HOGE=foo BAR=baz</programlisting>
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.</para>
+
+ <para>This setting is useful to configure the <literal>ID_NET_MANAGED_BY=</literal> property which
+ declares which network management service shall manage the interface, which is respected by
+ systemd-networkd and others. Use
+ <programlisting>Property=ID_NET_MANAGED_BY=io.systemd.Network</programlisting>
+ to declare explicitly that <command>systemd-networkd</command> shall manage the interface, or set
+ the property to something else to declare explicitly it shall not do so. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details how this property is used to match interface names.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>ImportProperty=</varname></term>
+ <listitem>
+ <para>Import specified udev properties from the saved database. This takes space separated list of
+ property names. Example: <programlisting>ImportProperty=HOGE BAR</programlisting>
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.</para>
+ <para>If the same property is also set in <varname>Property=</varname> in the above, then the
+ imported property value will be overridden by the value specified in <varname>Property=</varname>.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>UnsetProperty=</varname></term>
+ <listitem>
+ <para>Unset specified udev properties. This takes space separated list of
+ property names. Example: <programlisting>ImportProperty=HOGE BAR</programlisting>
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.</para>
+ <para>This setting is applied after <varname>ImportProperty=</varname> and
+ <varname>Property=</varname> are applied. Hence, if the same property is specified in
+ <varname>ImportProperty=</varname> or <varname>Property=</varname>, then the imported or specified
+ property value will be ignored, and the property will be unset.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<listitem>
<para>The <varname>ifalias</varname> interface property is set to this value.</para>
- <xi:include href="version-info.xml" xpointer="v211"/>
+ <xi:include href="version-info.xml" xpointer="v211"/>
</listitem>
</varlistentry>
<varlistentry>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>Specifiers</title>
+
+ <para>Some settings resolve specifiers which may be used to write generic unit files referring to runtime
+ or unit parameters that are replaced when the unit files are loaded. Specifiers must be known and
+ resolvable for the setting to be valid. The following specifiers are understood:</para>
+
+ <table class='specifiers'>
+ <title>Specifiers available in unit files</title>
+ <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <colspec colname="spec" />
+ <colspec colname="mean" />
+ <colspec colname="detail" />
+ <thead>
+ <row>
+ <entry>Specifier</entry>
+ <entry>Meaning</entry>
+ <entry>Details</entry>
+ </row>
+ </thead>
+ <tbody>
+ <xi:include href="standard-specifiers.xml" xpointer="a"/>
+ <xi:include href="standard-specifiers.xml" xpointer="A"/>
+ <xi:include href="standard-specifiers.xml" xpointer="b"/>
+ <xi:include href="standard-specifiers.xml" xpointer="B"/>
+ <xi:include href="standard-specifiers.xml" xpointer="H"/>
+ <xi:include href="standard-specifiers.xml" xpointer="l"/>
+ <xi:include href="standard-specifiers.xml" xpointer="m"/>
+ <xi:include href="standard-specifiers.xml" xpointer="M"/>
+ <xi:include href="standard-specifiers.xml" xpointer="o"/>
+ <xi:include href="standard-specifiers.xml" xpointer="q"/>
+ <xi:include href="standard-specifiers.xml" xpointer="T"/>
+ <xi:include href="standard-specifiers.xml" xpointer="v"/>
+ <xi:include href="standard-specifiers.xml" xpointer="V"/>
+ <xi:include href="standard-specifiers.xml" xpointer="w"/>
+ <xi:include href="standard-specifiers.xml" xpointer="W"/>
+ </tbody>
+ </tgroup>
+ </table>
+ </refsect1>
+
<refsect1>
<title>Examples</title>
<varlistentry>
<term><varname>What=</varname></term>
- <listitem><para>Takes an absolute path of a device node, file or other resource to mount. See
- <citerefentry
+ <listitem><para>Takes an absolute path or a fstab-style identifier of a device node, file or
+ other resource to mount. See <citerefentry
project='man-pages'><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
details. If this refers to a device node, a dependency on the respective device unit is automatically
created. (See
<varlistentry>
<term><varname>PrivateKey=</varname></term>
<listitem>
- <para>The Base64 encoded private key for the interface. It can be
- generated using the <command>wg genkey</command> command
+ <para>The Base64 encoded private key for the interface. It can be generated using
+ the <command>wg genkey</command> command
(see <citerefentry project="wireguard"><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>).
- This option or <varname>PrivateKeyFile=</varname> is mandatory to use WireGuard.
- Note that because this information is secret, you may want to set
- the permissions of the .netdev file to be owned by <literal>root:systemd-network</literal>
- with a <literal>0640</literal> file mode.</para>
+ Specially, if the specified key is prefixed with <literal>@</literal>, it is interpreted as
+ the name of the credential from which the actual key shall be read. <command>systemd-networkd.service</command>
+ automatically imports credentials matching <literal>network.wireguard.*</literal>. For more details
+ on credentials, refer to
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ A private key is mandatory to use WireGuard. If not set, the credential
+ <literal>network.wireguard.private.<replaceable>netdev</replaceable></literal> is used if exists.
+ I.e. for <filename>50-foobar.netdev</filename>, <literal>network.wireguard.private.50-foobar</literal>
+ is tried.</para>
+
+ <para>Note that because this information is secret, it's strongly recommended to use an (encrypted)
+ credential. Alternatively, you may want to set the permissions of the .netdev file to be owned
+ by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
<listitem>
<para>Sets a Base64 encoded public key calculated by <command>wg pubkey</command>
(see <citerefentry project="wireguard"><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
- from a private key, and usually transmitted out of band to the
- author of the configuration file. This option is mandatory for this
- section.</para>
+ from a private key, and usually transmitted out of band to the author of the configuration file.
+ This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
+ setting of the <option>[WireGuard]</option> section. This option is mandatory for this section.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
<varlistentry>
<term><varname>PresharedKey=</varname></term>
<listitem>
- <para>Optional preshared key for the interface. It can be generated
- by the <command>wg genpsk</command> command. This option adds an
- additional layer of symmetric-key cryptography to be mixed into the
- already existing public-key cryptography, for post-quantum
- resistance.
- Note that because this information is secret, you may want to set
- the permissions of the .netdev file to be owned by <literal>root:systemd-network</literal>
- with a <literal>0640</literal> file mode.</para>
+ <para>Optional preshared key for the interface. It can be generated by the <command>wg genpsk</command>
+ command. This option adds an additional layer of symmetric-key cryptography to be mixed into the
+ already existing public-key cryptography, for post-quantum resistance.
+ This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
+ setting of the <option>[WireGuard]</option> section.</para>
+
+ <para>Note that because this information is secret, it's strongly recommended to use an (encrypted)
+ credential. Alternatively, you may want to set the permissions of the .netdev file to be owned
+ by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
<varlistentry>
<term><varname>Endpoint=</varname></term>
<listitem>
- <para>Sets an endpoint IP address or hostname, followed by a colon, and then
- a port number. IPv6 address must be in the square brackets. For example,
- <literal>111.222.333.444:51820</literal> for IPv4 and <literal>[1111:2222::3333]:51820</literal>
- for IPv6 address. This endpoint will be updated automatically once to
- the most recent source IP address and port of correctly
+ <para>Sets an endpoint IP address or hostname, followed by a colon, and then a port number.
+ IPv6 address must be in the square brackets. For example, <literal>111.222.333.444:51820</literal>
+ for IPv4 and <literal>[1111:2222::3333]:51820</literal> for IPv6 address. This endpoint will be
+ updated automatically once to the most recent source IP address and port of correctly
authenticated packets from the peer at configuration time.</para>
+ <para>This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
+ setting of the <option>[WireGuard]</option> section.</para>
+
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
</varlistentry>
system. If assigned the
special value <literal>infinity</literal>, no memory throttling is applied. This controls the
<literal>memory.high</literal> control group attribute. For details about this control group attribute, see
- <ulink url="https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files">Memory Interface Files</ulink>.</para>
+ <ulink url="https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files">Memory Interface Files</ulink>.
+ The effective configuration is reported as <varname>EffectiveMemoryHigh=</varname>
+ (see also <varname>EffectiveMemoryMax=</varname>).</para>
<para>While <varname>StartupMemoryHigh=</varname> applies to the startup and shutdown phases of the system,
<varname>MemoryHigh=</varname> applies to normal runtime of the system, and if the former is not set also to
percentage value may be specified, which is taken relative to the installed physical memory on the system. If
assigned the special value <literal>infinity</literal>, no memory limit is applied. This controls the
<literal>memory.max</literal> control group attribute. For details about this control group attribute, see
- <ulink url="https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files">Memory Interface Files</ulink>.</para>
+ <ulink url="https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files">Memory Interface Files</ulink>.
+ The effective configuration is reported as <varname>EffectiveMemoryMax=</varname> (the value is
+ the most stringent limit of the unit and parent slices and it is capped by physical memory).</para>
<para>While <varname>StartupMemoryMax=</varname> applies to the startup and shutdown phases of the system,
<varname>MemoryMax=</varname> applies to normal runtime of the system, and if the former is not set also to
limit is applied. This controls the <literal>pids.max</literal> control group attribute. For
details about this control group attribute, the
<ulink url="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#pid">pids controller
- </ulink>.</para>
+ </ulink>.
+ The effective configuration is reported as <varname>EffectiveTasksMax=</varname>.</para>
<para>The system default for this setting may be controlled with
<varname>DefaultTasksMax=</varname> in
<varlistentry>
<term><varname>What=</varname></term>
- <listitem><para>Takes an absolute path of a device node or file to use for paging. See <citerefentry
+ <listitem><para>Takes an absolute path or a fstab-style identifier of a device node or file to use
+ for paging. See <citerefentry
project='man-pages'><refentrytitle>swapon</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
details. If this refers to a device node, a dependency on the respective device unit is automatically
created. (See
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>network.netdev.*</varname></term>
+ <term><varname>network.link.*</varname></term>
+ <term><varname>network.network.*</varname></term>
+ <listitem>
+ <para>Configures network devices. Read by
+ <citerefentry><refentrytitle>systemd-network-generator.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. These
+ credentials directly translate to a matching <filename>*.netdev</filename>,
+ <filename>*.link</filename> or <filename>*.network</filename> file. Example: the contents of a
+ credential <filename>network.link.50-foobar</filename> will be copied into a file
+ <filename>50-foobar.link</filename>. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details.</para>
+
+ <para>Note that the resulting files are created world-readable, it's hence recommended to not include
+ secrets in these credentials, but supply them via separate credentials directly to
+ <filename>systemd-networkd.service</filename>, e.g. <varname>network.wireguard.*</varname>
+ as described below.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>network.wireguard.*</varname></term>
+ <listitem>
+ <para>Configures secrets for WireGuard netdevs. Read by
+ <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ For more information, refer to the <option>[WireGuard]</option> section of
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>passwd.hashed-password.root</varname></term>
<term><varname>passwd.plaintext-password.root</varname></term>
<entry>Credentials directory</entry>
<entry>This is the value of the <literal>$CREDENTIALS_DIRECTORY</literal> environment variable if available. See section "Credentials" in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more information.</entry>
</row>
+ <row>
+ <entry><literal>%D</literal></entry>
+ <entry>Shared data directory</entry>
+ <entry>This is either <filename>/usr/share/</filename> (for the system manager) or the path <literal>$XDG_DATA_HOME</literal> resolves to (for user managers).</entry>
+ </row>
<row>
<entry><literal>%E</literal></entry>
<entry>Configuration directory root</entry>
--- /dev/null
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd.v">
+
+ <refentryinfo>
+ <title>systemd.v</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd.v</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd.v</refname>
+ <refpurpose>Directory with Versioned Resources</refpurpose>
+ </refnamediv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>In various places systemd components accept paths whose trailing components have the
+ <literal>.v/</literal> suffix, pointing to a directory. These components will then automatically look for
+ suitable files inside the directory, do a version comparison and open the newest file found (by
+ version). Specifically, two expressions are supported:</para>
+
+ <itemizedlist>
+
+ <listitem><para>When looking for files with a suffix <replaceable>.SUFFIX</replaceable>, and a path
+ <filename>…<replaceable>PATH</replaceable>/<replaceable>NAME</replaceable><replaceable>.SUFFIX</replaceable>.v/</filename>
+ is specified, then all files
+ <filename>…<replaceable>PATH</replaceable>/<replaceable>NAME</replaceable><replaceable>.SUFFIX</replaceable>.v/<replaceable>NAME</replaceable>_*<replaceable>.SUFFIX</replaceable></filename>
+ are enumerated, filtered, sorted and the newest file used. The primary sorting key is the
+ <emphasis>variable part</emphasis>, here indicated by the wildcard
+ <literal>*</literal>.</para></listitem>
+
+ <listitem><para>When a path
+ <filename>…<replaceable>PATH</replaceable>.v/<replaceable>NAME</replaceable>___<replaceable>.SUFFIX</replaceable></filename>
+ is specified (i.e. the penultimate component of the path ends in <literal>.v</literal> and the final
+ component contains a triple underscore), then all files
+ <filename>…<replaceable>PATH</replaceable>.v/<replaceable>NAME</replaceable>_*<replaceable>.SUFFIX</replaceable></filename>
+ are enumerated, filtered, sorted and the newest file used (again, by the <emphasis>variable
+ part</emphasis>, here indicated by the wildcard <literal>*</literal>).</para></listitem>
+ </itemizedlist>
+
+ <para>To illustrate this in an example, consider a directory <filename>/var/lib/machines/mymachine.raw.v/</filename>, which is populated with three files:</para>
+
+ <itemizedlist>
+ <listitem><para><filename>mymachine_7.5.13.raw</filename></para></listitem>
+ <listitem><para><filename>mymachine_7.5.14.raw</filename></para></listitem>
+ <listitem><para><filename>mymachine_7.6.0.raw</filename></para></listitem>
+ </itemizedlist>
+
+ <para>Invoke a tool such as <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> with a command line like the following:</para>
+
+ <programlisting># systemd-nspawn --image=/var/lib/machines/mymachine.raw.v --boot</programlisting>
+
+ <para>Then this would automatically be resolved to the equivalent of:</para>
+
+ <programlisting># systemd-nspawn --image=/var/lib/machines/mymachine.raw.v/mymachine_7.6.0.raw --boot</programlisting>
+
+ <para>Much of systemd's functionality that expects a path to a disk image or OS directory hierarchy
+ support the <literal>.v/</literal> versioned directory mechanism, for example
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry> or
+ the <varname>RootDirectory=</varname>/<varname>RootImage=</varname> settings of service files (see
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>).</para>
+
+ <para>Use the
+ <citerefentry><refentrytitle>systemd-vpick</refentrytitle><manvolnum>1</manvolnum></citerefentry> tool to
+ resolve <literal>.v/</literal> paths from the command line, for example for usage in shell
+ scripts.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Filtering and Sorting</title>
+
+ <para>The variable part of the filenames in the <literal>.v/</literal> directories are filtered and
+ compared primarily with a version comparison, implementing <ulink
+ url="https://uapi-group.org/specifications/specs/version_format_specification/">Version Format
+ Specification</ulink>. However, additional rules apply:</para>
+
+ <itemizedlist>
+ <listitem><para>If the variable part is suffixed by one or two integer values ("tries left" and "tries
+ done") in the formats <filename>+<replaceable>LEFT</replaceable></filename> or
+ <filename>+<replaceable>LEFT</replaceable>-<replaceable>DONE</replaceable></filename>, then these
+ indicate usage attempt counters. The idea is that each time before a file is attempted to be used, its
+ "tries left" counter is decreased, and the "tries done" counter increased (simply by renaming the
+ file). When the file is successfully used (which for example could mean for an OS image: successfully
+ booted) the counters are removed from the file name, indicating that the file has been validated to
+ work correctly. This mechanism mirrors the boot assessment counters defined by <ulink
+ url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/">Automatic Boot Assessment</ulink>. Any filenames
+ with no boot counters or with a non-zero "tries left" counter are sorted before filenames with a zero
+ "tries left" counter.</para></listitem>
+
+ <listitem><para>Preceding the use counters (if they are specified), an optional CPU architecture
+ identifier may be specified in the filename (separated from the version with an underscore), as defined
+ in the architecture vocabulary of the <varname>ConditionArchitecture=</varname> unit file setting, as
+ documented in
+ <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Files
+ whose name indicates an architecture not supported locally are filtered and not considered for the
+ version comparison.</para></listitem>
+
+ <listitem><para>The rest of the variable part is the version string.</para></listitem>
+ </itemizedlist>
+
+ <para>Or in other words, the files in the <literal>.v/</literal> directories should follow one of these
+ naming structures:</para>
+
+ <itemizedlist>
+ <listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
+ <listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>_<replaceable>ARCHITECTURE</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
+ <listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>+<replaceable>LEFT</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
+ <listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>+<replaceable>LEFT</replaceable>-<replaceable>DONE</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
+ <listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>_<replaceable>ARCHITECTURE</replaceable>+<replaceable>LEFT</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
+ <listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>_<replaceable>ARCHITECTURE</replaceable>+<replaceable>LEFT</replaceable>-<replaceable>DONE</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
+ </itemizedlist>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+
+ <para>Here's a more comprehensive example, further extending the one described above. Consider a
+ directory <filename>/var/lib/machines/mymachine.raw.v/</filename>, which is populated with the following
+ files:</para>
+
+ <itemizedlist>
+ <listitem><para><filename>mymachine_7.5.13.raw</filename></para></listitem>
+ <listitem><para><filename>mymachine_7.5.14_x86-64.raw</filename></para></listitem>
+ <listitem><para><filename>mymachine_7.6.0_arm64.raw</filename></para></listitem>
+ <listitem><para><filename>mymachine_7.7.0_x86-64+0-5.raw</filename></para></listitem>
+ </itemizedlist>
+
+ <para>Now invoke the following command on an x86-64 machine:</para>
+
+ <programlisting>$ systemd-vpick --suffix=.raw /var/lib/machines/mymachine.raw.v/</programlisting>
+
+ <para>This would resolve the specified path to
+ <filename>/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw</filename>. Explanation: even
+ though <filename>mymachine_7.7.0_x86-64+0-5.raw</filename> has the newest version, it is not preferred
+ because its tries left counter is zero. And even though <filename>mymachine_7.6.0_arm64.raw</filename>
+ has the second newest version it is also not considered, in this case because we operate on an x86_64
+ system and the image is intended for arm64 CPUs. Finally, the <filename>mymachine_7.5.13.raw</filename>
+ image is not considered because it is older than <filename>mymachine_7.5.14_x86-64.raw</filename>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><simplelist type="inline">
+ <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd-vpick</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ </simplelist></para>
+ </refsect1>
+
+</refentry>
<itemizedlist>
<listitem><para>A Varlink service reference starting with the <literal>unix:</literal> string, followed
- by an absolute <constant>AF_UNIX</constant> path, or by <literal>@</literal> and an arbitrary string
+ by an absolute <constant>AF_UNIX</constant> socket path, or by <literal>@</literal> and an arbitrary string
(the latter for referencing sockets in the abstract namespace).</para></listitem>
<listitem><para>A Varlink service reference starting with the <literal>exec:</literal> string, followed
by an absolute path of a binary to execute.</para></listitem>
+
+ <listitem><para>A Varlink service reference starting with the <literal>ssh:</literal> string, followed
+ by an SSH host specification, followed by <literal>:</literal>, followed by an absolute
+ <constant>AF_UNIX</constant> socket path. (This requires OpenSSH 9.4 or newer on the server side,
+ abstract namespace sockets are not supported.)</para></listitem>
</itemizedlist>
<para>For convenience these two simpler (redundant) service address syntaxes are also supported:</para>
subdir('src/veritysetup')
subdir('src/vmspawn')
subdir('src/volatile-root')
+subdir('src/vpick')
subdir('src/xdg-autostart-generator')
subdir('src/systemd')
linux-image-generic
linux-tools-common
linux-tools-generic
+# "orphan_file" is enabled by default in recent versions of mkfs.ext4 but not supported by the Jammy kernel
+# so we explicitly disable it.
+Environment=SYSTEMD_REPART_MKFS_OPTIONS_EXT4="-O ^orphan_file"
__get_machines() {
local a b
{ machinectl list --full --max-addresses=0 --no-legend --no-pager 2>/dev/null; echo ".host"; } | \
- { while read a b; do echo " $a"; done; } | \
+ { while read a b; do echo " $a"; done; } | \
sort -u
}
--version --list-catalog --update-catalog --list-boots
--show-cursor --dmesg -k --pager-end -e -r --reverse
--utc -x --catalog --no-full --force --dump-catalog
- --flush --rotate --sync --no-hostname -N --fields'
- [ARG]='-b --boot -D --directory --file -F --field -t --identifier --facility
- -M --machine -o --output -u --unit --user-unit -p --priority
- --root --case-sensitive'
+ --flush --rotate --sync --no-hostname -N --fields
+ --list-namespaces'
+ [ARG]='-b --boot -D --directory --file -F --field -t --identifier
+ -T --exclude-identifier --facility -M --machine -o --output
+ -u --unit --user-unit -p --priority --root --case-sensitive
+ --namespace'
[ARGUNKNOWN]='-c --cursor --interval -n --lines -S --since -U --until
--after-cursor --cursor-file --verify-key -g --grep
--vacuum-size --vacuum-time --vacuum-files --output-fields'
--user-unit)
comps=$(journalctl -F '_SYSTEMD_USER_UNIT' 2>/dev/null)
;;
- --identifier|-t)
+ --identifier|-t|--exclude-identifier|-T)
comps=$(journalctl -F 'SYSLOG_IDENTIFIER' 2>/dev/null)
;;
--case-sensitive)
comps='yes no'
;;
+ --namespace)
+ comps=$(journalctl --list-namespaces --output=cat 2>/dev/null)
+ ;;
*)
return 0
;;
local -A OPTS=(
[STANDALONE]='-h --help --version --system --user --global --order --require --no-pager
- --man=no --generators=yes --quiet'
+ --man=no --generators=yes -q --quiet'
[ARG]='-H --host -M --machine --fuzz --from-pattern --to-pattern --root'
)
_describe 'possible values' _journalctl_facilities
}
+(( $+functions[_journalctl_namespaces] )) ||
+_journalctl_namespaces() {
+ local -a _journalctl_namespaces
+ _journalctl_namespaces=( ${(f)"$(_call_program namespaces "$service --list-namespaces --output=cat" 2>/dev/null)"} )
+ _describe 'possible values' _journalctl_namespaces
+}
+
# Build arguments for "journalctl" to be used in completion.
# Use both --user and --system modes, they are not exclusive.
local -a _modes; _modes=(--user --system)
'--header[Show journal header information]' \
'--interval=[Time interval for changing the FSS sealing key]:time interval' \
'--list-catalog[List messages in catalog]' \
+ '--list-namespaces[List available journal namespaces]' \
+ '--namespace[Show journal data from specified namespace]:namespace:_journalctl_namespaces' \
'--new-id128[Generate a new 128 Bit ID]' \
'--rotate[Request immediate rotation of the journal files]' \
'--setup-keys[Generate a new FSS key pair]' \
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", p);
- r = unhexmem(s, ss, &buf, &bufsize);
+ r = unhexmem_full(s, ss, /* secure = */ false, &buf, &bufsize);
if (r < 0)
return log_error_errno(r, "Failed to decode hex PCR data '%s': %m", s);
" dot [UNIT...] Output dependency graph in %s format\n"
" dump [PATTERN...] Output state serialization of service\n"
" manager\n"
- " cat-config Show configuration file and drop-ins\n"
+ " cat-config NAME|PATH... Show configuration file and drop-ins\n"
" unit-files List files and symlinks for units\n"
" unit-paths List load directories for units\n"
" exit-status [STATUS...] List exit status definitions\n"
" inspect-elf FILE... Parse and print ELF package metadata\n"
" malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n"
" fdstore SERVICE... Show file descriptor store contents of service\n"
+ " image-policy POLICY... Analyze image policy string\n"
" pcrs [PCR...] Show TPM2 PCRs and their names\n"
" srk > FILE Write TPM2 SRK to stdout\n"
"\nOptions:\n"
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hH:M:U:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hH:M:U:q", options, NULL)) >= 0)
switch (c) {
case 'h':
}
int setenv_systemd_exec_pid(bool update_only) {
- char str[DECIMAL_STR_MAX(pid_t)];
const char *e;
+ int r;
/* Update $SYSTEMD_EXEC_PID=pid except when '*' is set for the variable. */
if (streq_ptr(e, "*"))
return 0;
- xsprintf(str, PID_FMT, getpid_cached());
-
- if (setenv("SYSTEMD_EXEC_PID", str, 1) < 0)
- return -errno;
+ r = setenvf("SYSTEMD_EXEC_PID", /* overwrite= */ 1, PID_FMT, getpid_cached());
+ if (r < 0)
+ return r;
return 1;
}
return 0;
}
+
+int setenvf(const char *name, bool overwrite, const char *valuef, ...) {
+ _cleanup_free_ char *value = NULL;
+ va_list ap;
+ int r;
+
+ assert(name);
+
+ if (!valuef)
+ return RET_NERRNO(unsetenv(name));
+
+ va_start(ap, valuef);
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ r = vasprintf(&value, valuef, ap);
+ REENABLE_WARNING;
+ va_end(ap);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ return RET_NERRNO(setenv(name, value, overwrite));
+}
int getenv_steal_erase(const char *name, char **ret);
int set_full_environment(char **env);
+
+int setenvf(const char *name, bool overwrite, const char *valuef, ...) _printf_(3,4);
return RET_NERRNO(fcntl(fd, F_SETFL, nflags));
}
+int stdio_disable_nonblock(void) {
+ int ret = 0;
+
+ /* stdin/stdout/stderr really should have O_NONBLOCK, which would confuse apps if left on, as
+ * write()s might unexpectedly fail with EAGAIN. */
+
+ RET_GATHER(ret, fd_nonblock(STDIN_FILENO, false));
+ RET_GATHER(ret, fd_nonblock(STDOUT_FILENO, false));
+ RET_GATHER(ret, fd_nonblock(STDERR_FILENO, false));
+
+ return ret;
+}
+
int fd_cloexec(int fd, bool cloexec) {
int flags, nflags;
#define _cleanup_close_pair_ _cleanup_(close_pairp)
int fd_nonblock(int fd, bool nonblock);
+int stdio_disable_nonblock(void);
+
int fd_cloexec(int fd, bool cloexec);
int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
return 0;
}
+static mode_t write_string_file_flags_to_mode(WriteStringFileFlags flags) {
+
+ /* We support three different modes, that are the ones that really make sense for text files like this:
+ *
+ * → 0600 (i.e. root-only)
+ * → 0444 (i.e. read-only)
+ * → 0644 (i.e. writable for root, readable for everyone else)
+ */
+
+ return FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 :
+ FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0444) ? 0444 : 0644;
+}
+
static int write_string_file_atomic_at(
int dir_fd,
const char *fn,
if (r < 0)
goto fail;
- r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644);
+ r = fchmod_umask(fileno(f), write_string_file_flags_to_mode(flags));
if (r < 0)
goto fail;
(FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
(FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) |
(FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY),
- (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
+ write_string_file_flags_to_mode(flags));
if (fd < 0) {
r = -errno;
goto fail;
WRITE_STRING_FILE_NOFOLLOW = 1 << 8,
WRITE_STRING_FILE_MKDIR_0755 = 1 << 9,
WRITE_STRING_FILE_MODE_0600 = 1 << 10,
- WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 11,
+ WRITE_STRING_FILE_MODE_0444 = 1 << 11,
+ WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 12,
/* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
const char *p,
size_t l,
bool secure,
- void **ret,
+ void **ret_data,
size_t *ret_len) {
_cleanup_free_ uint8_t *buf = NULL;
if (ret_len)
*ret_len = (size_t) (z - buf);
- if (ret)
- *ret = TAKE_PTR(buf);
+ if (ret_data)
+ *ret_data = TAKE_PTR(buf);
return 0;
}
const char *p,
size_t l,
bool secure,
- void **ret,
+ void **ret_data,
size_t *ret_size) {
_cleanup_free_ uint8_t *buf = NULL;
if (ret_size)
*ret_size = (size_t) (z - buf);
- if (ret)
- *ret = TAKE_PTR(buf);
+ if (ret_data)
+ *ret_data = TAKE_PTR(buf);
return 0;
}
int unhexchar(char c) _const_;
char *hexmem(const void *p, size_t l);
-int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
-static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
- return unhexmem_full(p, l, false, mem, len);
+int unhexmem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size);
+static inline int unhexmem(const char *p, void **ret_data, size_t *ret_size) {
+ return unhexmem_full(p, SIZE_MAX, false, ret_data, ret_size);
}
char base32hexchar(int x) _const_;
size_t l,
size_t margin,
size_t width);
-int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
-static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) {
- return unbase64mem_full(p, l, false, mem, len);
+int unbase64mem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size);
+static inline int unbase64mem(const char *p, void **ret_data, size_t *ret_size) {
+ return unbase64mem_full(p, SIZE_MAX, false, ret_data, ret_size);
}
void hexdump(FILE *f, const void *p, size_t s);
bool iovec_increment(struct iovec *iovec, size_t n, size_t k);
-#define IOVEC_MAKE(base, len) (struct iovec) { .iov_base = (base), .iov_len = (len) }
-#define IOVEC_MAKE_STRING(string) \
- ({ \
- const char *_s = (string); \
- IOVEC_MAKE((char*) _s, strlen(_s)); \
- })
+/* This accepts both const and non-const pointers */
+#define IOVEC_MAKE(base, len) \
+ (struct iovec) { \
+ .iov_base = (void*) (base), \
+ .iov_len = (len), \
+ }
+
+static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) {
+ assert(iovec);
+ /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */
+ *iovec = IOVEC_MAKE(s, s ? strlen(s) : 0);
+ return iovec;
+}
+
+#define IOVEC_MAKE_STRING(s) \
+ *iovec_make_string(&(struct iovec) {}, s)
+
+#define CONST_IOVEC_MAKE_STRING(s) \
+ (const struct iovec) { \
+ .iov_base = (char*) s, \
+ .iov_len = STRLEN(s), \
+ }
static inline void iovec_done(struct iovec *iovec) {
/* A _cleanup_() helper that frees the iov_base in the iovec */
}
static inline bool iovec_is_set(const struct iovec *iovec) {
+ /* Checks if the iovec points to a non-empty chunk of memory */
return iovec && iovec->iov_len > 0 && iovec->iov_base;
}
+static inline bool iovec_is_valid(const struct iovec *iovec) {
+ /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */
+ return !iovec || (iovec->iov_base || iovec->iov_len == 0);
+}
+
char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);
void iovec_array_free(struct iovec *iovec, size_t n_iovec);
+
+static inline int iovec_memcmp(const struct iovec *a, const struct iovec *b) {
+
+ if (a == b)
+ return 0;
+
+ return memcmp_nn(a ? a->iov_base : NULL,
+ a ? a->iov_len : 0,
+ b ? b->iov_base : NULL,
+ b ? b->iov_len : 0);
+}
+
+static inline struct iovec *iovec_memdup(const struct iovec *source, struct iovec *ret) {
+ assert(ret);
+
+ if (!iovec_is_set(source))
+ *ret = (struct iovec) {};
+ else {
+ void *p = memdup(source->iov_base, source->iov_len);
+ if (!p)
+ return NULL;
+
+ *ret = IOVEC_MAKE(p, source->iov_len);
+ }
+
+ return ret;
+}
'terminal-util.c',
'time-util.c',
'tmpfile-util.c',
- 'uid-alloc-range.c',
+ 'uid-classification.c',
'uid-range.c',
'unit-def.c',
'unit-file.c',
#if HAVE_LINUX_VM_SOCKETS_H
#include <linux/vm_sockets.h>
#else
-#define VMADDR_CID_ANY -1U
struct sockaddr_vm {
unsigned short svm_family;
unsigned short svm_reserved1;
};
#endif /* !HAVE_LINUX_VM_SOCKETS_H */
+#ifndef VMADDR_CID_ANY
+#define VMADDR_CID_ANY -1U
+#endif
+
+#ifndef VMADDR_CID_HYPERVISOR
+#define VMADDR_CID_HYPERVISOR 0U
+#endif
+
+#ifndef VMADDR_CID_LOCAL
+#define VMADDR_CID_LOCAL 1U
+#endif
+
+#ifndef VMADDR_CID_HOST
+#define VMADDR_CID_HOST 2U
+#endif
+
+#ifndef VMADDR_PORT_ANY
+#define VMADDR_PORT_ANY -1U
+#endif
+
#ifndef AF_VSOCK
#define AF_VSOCK 40
#endif
#define SO_PEERGROUPS 59
#endif
+#ifndef SO_PEERPIDFD
+#define SO_PEERPIDFD 77
+#endif
+
#ifndef SO_BINDTOIFINDEX
#define SO_BINDTOIFINDEX 62
#endif
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/wait.h>
+
+#ifndef P_PIDFD
+#define P_PIDFD 3
+#endif
return 0;
}
-int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
+int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero) {
unsigned l, h;
int r;
if (r < 0)
return r;
- if (l <= 0 || l > 65535 || h <= 0 || h > 65535)
+ if (l > 65535 || h > 65535)
+ return -EINVAL;
+
+ if (!allow_zero && (l == 0 || h == 0))
return -EINVAL;
if (h < l)
int parse_nice(const char *p, int *ret);
int parse_ip_port(const char *s, uint16_t *ret);
-int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high);
+int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero);
int parse_ip_prefix_length(const char *s, int *ret);
#include "errno-util.h"
#include "fd-util.h"
#include "missing_syscall.h"
+#include "missing_wait.h"
#include "parse-util.h"
#include "pidref.h"
#include "process-util.h"
return pidref->pid == getpid_cached();
}
+int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) {
+ int r;
+
+ if (!pidref_is_set(pidref))
+ return -ESRCH;
+
+ if (pidref->pid == 1 || pidref->pid == getpid_cached())
+ return -ECHILD;
+
+ siginfo_t si = {};
+
+ if (pidref->fd >= 0) {
+ r = RET_NERRNO(waitid(P_PIDFD, pidref->fd, &si, options));
+ if (r >= 0) {
+ if (ret)
+ *ret = si;
+ return r;
+ }
+ if (r != -EINVAL) /* P_PIDFD was added in kernel 5.4 only */
+ return r;
+ }
+
+ r = RET_NERRNO(waitid(P_PID, pidref->pid, &si, options));
+ if (r >= 0 && ret)
+ *ret = si;
+ return r;
+}
+
+int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret) {
+ int r;
+
+ for (;;) {
+ r = pidref_wait(pidref, ret, WEXITED);
+ if (r != -EINTR)
+ return r;
+ }
+}
+
static void pidref_hash_func(const PidRef *pidref, struct siphash *state) {
siphash24_compress_typesafe(pidref->pid, state);
}
int pidref_kill(const PidRef *pidref, int sig);
int pidref_kill_and_sigcont(const PidRef *pidref, int sig);
-int pidref_sigqueue(const PidRef *pidfref, int sig, int value);
+int pidref_sigqueue(const PidRef *pidref, int sig, int value);
+
+int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options);
+int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret);
+
+static inline void pidref_done_sigkill_wait(PidRef *pidref) {
+ if (!pidref_is_set(pidref))
+ return;
+
+ (void) pidref_kill(pidref, SIGKILL);
+ (void) pidref_wait_for_terminate(pidref, NULL);
+ pidref_done(pidref);
+}
int pidref_verify(const PidRef *pidref);
return 0;
}
+int pid_get_start_time(pid_t pid, uint64_t *ret) {
+ _cleanup_free_ char *line = NULL;
+ const char *p;
+ int r;
+
+ assert(pid >= 0);
+
+ p = procfs_file_alloca(pid, "stat");
+ r = read_one_line_file(p, &line);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
+
+ /* Let's skip the pid and comm fields. The latter is enclosed in () but does not escape any () in its
+ * value, so let's skip over it manually */
+
+ p = strrchr(line, ')');
+ if (!p)
+ return -EIO;
+
+ p++;
+
+ unsigned long llu;
+
+ if (sscanf(p, " "
+ "%*c " /* state */
+ "%*u " /* ppid */
+ "%*u " /* pgrp */
+ "%*u " /* session */
+ "%*u " /* tty_nr */
+ "%*u " /* tpgid */
+ "%*u " /* flags */
+ "%*u " /* minflt */
+ "%*u " /* cminflt */
+ "%*u " /* majflt */
+ "%*u " /* cmajflt */
+ "%*u " /* utime */
+ "%*u " /* stime */
+ "%*u " /* cutime */
+ "%*u " /* cstime */
+ "%*i " /* priority */
+ "%*i " /* nice */
+ "%*u " /* num_threads */
+ "%*u " /* itrealvalue */
+ "%lu ", /* starttime */
+ &llu) != 1)
+ return -EIO;
+
+ if (ret)
+ *ret = llu;
+
+ return 0;
+}
+
+int pidref_get_start_time(const PidRef *pid, uint64_t *ret) {
+ uint64_t t;
+ int r;
+
+ if (!pidref_is_set(pid))
+ return -ESRCH;
+
+ r = pid_get_start_time(pid->pid, ret ? &t : NULL);
+ if (r < 0)
+ return r;
+
+ r = pidref_verify(pid);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = t;
+
+ return 0;
+}
+
int get_process_umask(pid_t pid, mode_t *ret) {
_cleanup_free_ char *m = NULL;
const char *p;
return -ESRCH;
result = pid_is_alive(pidref->pid);
- if (result < 0)
+ if (result < 0) {
+ assert(result != -ESRCH);
return result;
+ }
r = pidref_verify(pidref);
if (r == -ESRCH)
log_full_errno(prio, r, "Failed to rearrange stdio fds: %m");
_exit(EXIT_FAILURE);
}
+
+ /* Turn off O_NONBLOCK on the fdio fds, in case it was left on */
+ stdio_disable_nonblock();
} else {
r = make_null_stdio();
if (r < 0) {
return 0;
}
+int pidref_safe_fork_full(
+ const char *name,
+ const int stdio_fds[3],
+ const int except_fds[],
+ size_t n_except_fds,
+ ForkFlags flags,
+ PidRef *ret_pid) {
+
+ pid_t pid;
+ int r, q;
+
+ assert(!FLAGS_SET(flags, FORK_WAIT));
+
+ r = safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags, &pid);
+ if (r < 0)
+ return r;
+
+ q = pidref_set_pid(ret_pid, pid);
+ if (q < 0) /* Let's not fail for this, no matter what, the process exists after all, and that's key */
+ *ret_pid = PIDREF_MAKE_FROM_PID(pid);
+
+ return r;
+}
+
int namespace_fork(
const char *outer_name,
const char *inner_name,
int get_process_root(pid_t pid, char **ret);
int get_process_environ(pid_t pid, char **ret);
int get_process_ppid(pid_t pid, pid_t *ret);
+int pid_get_start_time(pid_t pid, uint64_t *ret);
+int pidref_get_start_time(const PidRef* pid, uint64_t *ret);
int get_process_umask(pid_t pid, mode_t *ret);
int container_get_leader(const char *machine, pid_t *pid);
return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid);
}
+int pidref_safe_fork_full(
+ const char *name,
+ const int stdio_fds[3],
+ const int except_fds[],
+ size_t n_except_fds,
+ ForkFlags flags,
+ PidRef *ret_pid);
+
+static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *ret_pid) {
+ return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid);
+}
+
int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid);
int set_oom_score_adjust(int value);
}
/* If we can't, make sure the queue size is out of bounds, to
- * mark it as overflow */
+ * mark it as overflowed */
for (;;) {
sig_atomic_t c;
__atomic_thread_fence(__ATOMIC_SEQ_CST);
c = n_sigbus_queue;
- if (c > SIGBUS_QUEUE_MAX) /* already overflow */
+ if (c > SIGBUS_QUEUE_MAX) /* already overflowed */
return;
/* OK if we clobber c here, since we either immediately return
if (_likely_(c == 0))
return 0;
- if (_unlikely_(c >= SIGBUS_QUEUE_MAX))
+ if (_unlikely_(c > SIGBUS_QUEUE_MAX))
return -EOVERFLOW;
for (u = 0; u < SIGBUS_QUEUE_MAX; u++) {
return sockaddr_pretty(&sa.sa, salen, false, true, ret);
}
-int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) {
+int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) {
+ char host[NI_MAXHOST];
int r;
- char host[NI_MAXHOST], *ret;
- assert(_ret);
+ assert(sa);
+ assert(salen > sizeof(sa_family_t));
- r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS);
+ r = getnameinfo(sa, salen, host, sizeof(host), /* service= */ NULL, /* service_len= */ 0, IDN_FLAGS);
if (r != 0) {
- int saved_errno = errno;
+ if (r == EAI_MEMORY)
+ return log_oom_debug();
+ if (r == EAI_SYSTEM)
+ log_debug_errno(errno, "getnameinfo() failed, ignoring: %m");
+ else
+ log_debug("getnameinfo() failed, ignoring: %s", gai_strerror(r));
- r = sockaddr_pretty(&sa->sa, salen, true, true, &ret);
- if (r < 0)
- return r;
+ return sockaddr_pretty(sa, salen, /* translate_ipv6= */ true, /* include_port= */ true, ret);
+ }
- log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret);
- } else {
- ret = strdup(host);
- if (!ret)
+ if (ret) {
+ char *copy = strdup(host);
+ if (!copy)
return -ENOMEM;
+
+ *ret = copy;
}
- *_ret = ret;
return 0;
}
return (int) n;
}
+int getpeerpidfd(int fd) {
+ socklen_t n = sizeof(int);
+ int pidfd = -EBADF;
+
+ assert(fd >= 0);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD, &pidfd, &n) < 0)
+ return -errno;
+
+ if (n != sizeof(int))
+ return -EIO;
+
+ return pidfd;
+}
+
ssize_t send_many_fds_iov_sa(
int transport_fd,
int *fds_array, size_t n_fds_array,
return 0;
}
+int vsock_parse_port(const char *s, unsigned *ret) {
+ int r;
+
+ assert(ret);
+
+ if (!s)
+ return -EINVAL;
+
+ unsigned u;
+ r = safe_atou(s, &u);
+ if (r < 0)
+ return r;
+
+ /* Port 0 is apparently valid and not special in AF_VSOCK (unlike on IP). But VMADDR_PORT_ANY
+ * (UINT32_MAX) is. Hence refuse that. */
+
+ if (u == VMADDR_PORT_ANY)
+ return -EINVAL;
+
+ *ret = u;
+ return 0;
+}
+
+int vsock_parse_cid(const char *s, unsigned *ret) {
+ assert(ret);
+
+ if (!s)
+ return -EINVAL;
+
+ /* Parsed an AF_VSOCK "CID". This is a 32bit entity, and the usual type is "unsigned". We recognize
+ * the three special CIDs as strings, and otherwise parse the numeric CIDs. */
+
+ if (streq(s, "hypervisor"))
+ *ret = VMADDR_CID_HYPERVISOR;
+ else if (streq(s, "local"))
+ *ret = VMADDR_CID_LOCAL;
+ else if (streq(s, "host"))
+ *ret = VMADDR_CID_HOST;
+ else
+ return safe_atou(s, ret);
+
+ return 0;
+}
+
int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
/* AF_VSOCK socket in vsock:cid:port notation */
_cleanup_free_ char *n = NULL;
if (!e)
return -EINVAL;
- r = safe_atou(e+1, &port);
+ r = vsock_parse_port(e+1, &port);
if (r < 0)
return r;
if (isempty(n))
cid = VMADDR_CID_ANY;
else {
- r = safe_atou(n, &cid);
+ r = vsock_parse_cid(n, &cid);
if (r < 0)
return r;
}
*ret_address = (SocketAddress) {
.sockaddr.vm = {
- .svm_cid = cid,
.svm_family = AF_VSOCK,
+ .svm_cid = cid,
.svm_port = port,
},
.type = type,
return 0;
}
+
+int vsock_get_local_cid(unsigned *ret) {
+ _cleanup_close_ int vsock_fd = -EBADF;
+
+ assert(ret);
+
+ vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC);
+ if (vsock_fd < 0)
+ return log_debug_errno(errno, "Failed to open /dev/vsock: %m");
+
+ if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret) < 0)
+ return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m");
+
+ return 0;
+}
int getpeername_pretty(int fd, bool include_port, char **ret);
int getsockname_pretty(int fd, char **ret);
-int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret);
+int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **_ret);
const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_;
SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_;
int getpeercred(int fd, struct ucred *ucred);
int getpeersec(int fd, char **ret);
int getpeergroups(int fd, gid_t **ret);
+int getpeerpidfd(int fd);
ssize_t send_many_fds_iov_sa(
int transport_fd,
int connect_unix_path(int fd, int dir_fd, const char *path);
+int vsock_parse_port(const char *s, unsigned *ret);
+int vsock_parse_cid(const char *s, unsigned *ret);
+
/* Parses AF_UNIX and AF_VSOCK addresses. AF_INET[6] require some netlink calls, so it cannot be in
* src/basic/ and is done from 'socket_local_address from src/shared/. Return -EPROTO in case of
* protocol mismatch. */
* /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file
* authoritative. */
#define SOMAXCONN_DELUXE INT_MAX
+
+int vsock_get_local_cid(unsigned *ret);
return NULL;
}
+
+mode_t inode_type_from_string(const char *s) {
+ if (!s)
+ return MODE_INVALID;
+
+ if (streq(s, "reg"))
+ return S_IFREG;
+ if (streq(s, "dir"))
+ return S_IFDIR;
+ if (streq(s, "lnk"))
+ return S_IFLNK;
+ if (streq(s, "chr"))
+ return S_IFCHR;
+ if (streq(s, "blk"))
+ return S_IFBLK;
+ if (streq(s, "fifo"))
+ return S_IFIFO;
+ if (streq(s, "sock"))
+ return S_IFSOCK;
+
+ return MODE_INVALID;
+}
#include "macro.h"
#include "missing_stat.h"
#include "siphash24.h"
+#include "time-util.h"
int is_symlink(const char *path);
int is_dir_full(int atfd, const char *fname, bool follow);
} var
#endif
+static inline usec_t statx_timestamp_load(const struct statx_timestamp *ts) {
+ return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
+}
+static inline nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) {
+ return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
+}
+
void inode_hash_func(const struct stat *q, struct siphash *state);
int inode_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_hash_ops;
const char* inode_type_to_string(mode_t m);
+mode_t inode_type_from_string(const char *s);
return p + strlen(needle);
}
-char *startswith_strv(const char *string, char **strv) {
- char *found = NULL;
-
- STRV_FOREACH(i, strv) {
- found = startswith(string, *i);
- if (found)
- break;
- }
-
- return found;
-}
-
bool version_is_valid(const char *s) {
if (isempty(s))
return false;
return t1[yl];
}
+
+char *strrstr(const char *haystack, const char *needle) {
+ const char *f = NULL;
+ size_t l;
+
+ /* Like strstr() but returns the last rather than the first occurrence of "needle" in "haystack". */
+
+ if (!haystack || !needle)
+ return NULL;
+
+ l = strlen(needle);
+
+ /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the
+ * last char, not before. */
+ if (l == 0)
+ return strchr(haystack, 0);
+
+ for (const char *p = haystack; *p; p++)
+ if (strneq(p, needle, l))
+ f = p;
+
+ return (char*) f;
+}
char *find_line_startswith(const char *haystack, const char *needle);
-char *startswith_strv(const char *string, char **strv);
-
-#define STARTSWITH_SET(p, ...) \
- startswith_strv(p, STRV_MAKE(__VA_ARGS__))
-
bool version_is_valid(const char *s);
bool version_is_valid_versionspec(const char *s);
*ret = c;
return 1;
}
+
+char *strrstr(const char *haystack, const char *needle);
return strv_consume(l, x);
}
+char* startswith_strv(const char *s, char * const *l) {
+ STRV_FOREACH(i, l) {
+ char *found = startswith(s, *i);
+ if (found)
+ return found;
+ }
+
+ return NULL;
+}
+
+char* endswith_strv(const char *s, char * const *l) {
+ STRV_FOREACH(i, l) {
+ char *found = endswith(s, *i);
+ if (found)
+ return found;
+ }
+
+ return NULL;
+}
+
char** strv_reverse(char **l) {
size_t n;
strv_print_full(l, NULL);
}
+char* startswith_strv(const char *s, char * const *l);
+
+#define STARTSWITH_SET(p, ...) \
+ startswith_strv(p, STRV_MAKE(__VA_ARGS__))
+
+char* endswith_strv(const char *s, char * const *l);
+
+#define ENDSWITH_SET(p, ...) \
+ endswith_strv(p, STRV_MAKE(__VA_ARGS__))
+
#define strv_from_stdarg_alloca(first) \
({ \
char **_l; \
_x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
})
-#define ENDSWITH_SET(p, ...) \
- ({ \
- const char *_p = (p); \
- char *_found = NULL; \
- STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \
- _found = endswith(_p, *_i); \
- if (_found) \
- break; \
- } \
- _found; \
- })
-
#define _FOREACH_STRING(uniq, x, y, ...) \
for (const char *x, * const*UNIQ_T(l, uniq) = STRV_MAKE_CONST(({ x = y; }), ##__VA_ARGS__); \
x; \
#include "fileio.h"
#include "missing_threads.h"
#include "string-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
static const UGIDAllocationRange default_ugid_allocation_range = {
#include "uid-range.h"
#include "user-util.h"
-UidRange *uid_range_free(UidRange *range) {
+UIDRange *uid_range_free(UIDRange *range) {
if (!range)
return NULL;
return mfree(range);
}
-static bool uid_range_entry_intersect(const UidRangeEntry *a, const UidRangeEntry *b) {
+static bool uid_range_entry_intersect(const UIDRangeEntry *a, const UIDRangeEntry *b) {
assert(a);
assert(b);
return a->start <= b->start + b->nr && a->start + a->nr >= b->start;
}
-static int uid_range_entry_compare(const UidRangeEntry *a, const UidRangeEntry *b) {
+static int uid_range_entry_compare(const UIDRangeEntry *a, const UIDRangeEntry *b) {
int r;
assert(a);
return CMP(a->nr, b->nr);
}
-static void uid_range_coalesce(UidRange *range) {
+static void uid_range_coalesce(UIDRange *range) {
assert(range);
if (range->n_entries <= 0)
typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare);
for (size_t i = 0; i < range->n_entries; i++) {
- UidRangeEntry *x = range->entries + i;
+ UIDRangeEntry *x = range->entries + i;
for (size_t j = i + 1; j < range->n_entries; j++) {
- UidRangeEntry *y = range->entries + j;
+ UIDRangeEntry *y = range->entries + j;
uid_t begin, end;
if (!uid_range_entry_intersect(x, y))
x->nr = end - begin;
if (range->n_entries > j + 1)
- memmove(y, y + 1, sizeof(UidRangeEntry) * (range->n_entries - j - 1));
+ memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1));
range->n_entries--;
j--;
}
}
-int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce) {
- _cleanup_(uid_range_freep) UidRange *range_new = NULL;
- UidRange *p;
+int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce) {
+ _cleanup_(uid_range_freep) UIDRange *range_new = NULL;
+ UIDRange *p;
assert(range);
if (*range)
p = *range;
else {
- range_new = new0(UidRange, 1);
+ range_new = new0(UIDRange, 1);
if (!range_new)
return -ENOMEM;
if (!GREEDY_REALLOC(p->entries, p->n_entries + 1))
return -ENOMEM;
- p->entries[p->n_entries++] = (UidRangeEntry) {
+ p->entries[p->n_entries++] = (UIDRangeEntry) {
.start = start,
.nr = nr,
};
return 0;
}
-int uid_range_add_str(UidRange **range, const char *s) {
+int uid_range_add_str(UIDRange **range, const char *s) {
uid_t start, end;
int r;
return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true);
}
-int uid_range_next_lower(const UidRange *range, uid_t *uid) {
+int uid_range_next_lower(const UIDRange *range, uid_t *uid) {
uid_t closest = UID_INVALID, candidate;
assert(range);
return 1;
}
-bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr) {
+bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr) {
if (nr == 0) /* empty range? always covered... */
return true;
return 0;
}
-int uid_range_load_userns(UidRange **ret, const char *path) {
- _cleanup_(uid_range_freep) UidRange *range = NULL;
+int uid_range_load_userns(UIDRange **ret, const char *path) {
+ _cleanup_(uid_range_freep) UIDRange *range = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
return r;
}
- range = new0(UidRange, 1);
+ range = new0(UIDRange, 1);
if (!range)
return -ENOMEM;
#include "macro.h"
-typedef struct UidRangeEntry {
+typedef struct UIDRangeEntry {
uid_t start, nr;
-} UidRangeEntry;
+} UIDRangeEntry;
-typedef struct UidRange {
- UidRangeEntry *entries;
+typedef struct UIDRange {
+ UIDRangeEntry *entries;
size_t n_entries;
-} UidRange;
+} UIDRange;
-UidRange *uid_range_free(UidRange *range);
-DEFINE_TRIVIAL_CLEANUP_FUNC(UidRange*, uid_range_free);
+UIDRange *uid_range_free(UIDRange *range);
+DEFINE_TRIVIAL_CLEANUP_FUNC(UIDRange*, uid_range_free);
-int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce);
-static inline int uid_range_add(UidRange **range, uid_t start, uid_t nr) {
+int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce);
+static inline int uid_range_add(UIDRange **range, uid_t start, uid_t nr) {
return uid_range_add_internal(range, start, nr, true);
}
-int uid_range_add_str(UidRange **range, const char *s);
+int uid_range_add_str(UIDRange **range, const char *s);
-int uid_range_next_lower(const UidRange *range, uid_t *uid);
+int uid_range_next_lower(const UIDRange *range, uid_t *uid);
-bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr);
-static inline bool uid_range_contains(const UidRange *range, uid_t uid) {
+bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr);
+static inline bool uid_range_contains(const UIDRange *range, uid_t uid) {
return uid_range_covers(range, uid, 1);
}
int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range);
-int uid_range_load_userns(UidRange **ret, const char *path);
+int uid_range_load_userns(UIDRange **ret, const char *path);
const char *vendor;
Virtualization id;
} dmi_vendor_table[] = {
- { "KVM", VIRTUALIZATION_KVM },
- { "OpenStack", VIRTUALIZATION_KVM }, /* Detect OpenStack instance as KVM in non x86 architecture */
- { "KubeVirt", VIRTUALIZATION_KVM }, /* Detect KubeVirt instance as KVM in non x86 architecture */
- { "Amazon EC2", VIRTUALIZATION_AMAZON },
- { "QEMU", VIRTUALIZATION_QEMU },
- { "VMware", VIRTUALIZATION_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */
- { "VMW", VIRTUALIZATION_VMWARE },
- { "innotek GmbH", VIRTUALIZATION_ORACLE },
- { "VirtualBox", VIRTUALIZATION_ORACLE },
- { "Xen", VIRTUALIZATION_XEN },
- { "Bochs", VIRTUALIZATION_BOCHS },
- { "Parallels", VIRTUALIZATION_PARALLELS },
+ { "KVM", VIRTUALIZATION_KVM },
+ { "OpenStack", VIRTUALIZATION_KVM }, /* Detect OpenStack instance as KVM in non x86 architecture */
+ { "KubeVirt", VIRTUALIZATION_KVM }, /* Detect KubeVirt instance as KVM in non x86 architecture */
+ { "Amazon EC2", VIRTUALIZATION_AMAZON },
+ { "QEMU", VIRTUALIZATION_QEMU },
+ { "VMware", VIRTUALIZATION_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */
+ { "VMW", VIRTUALIZATION_VMWARE },
+ { "innotek GmbH", VIRTUALIZATION_ORACLE },
+ { "VirtualBox", VIRTUALIZATION_ORACLE },
+ { "Xen", VIRTUALIZATION_XEN },
+ { "Bochs", VIRTUALIZATION_BOCHS },
+ { "Parallels", VIRTUALIZATION_PARALLELS },
/* https://wiki.freebsd.org/bhyve */
- { "BHYVE", VIRTUALIZATION_BHYVE },
- { "Hyper-V", VIRTUALIZATION_MICROSOFT },
- { "Apple Virtualization", VIRTUALIZATION_APPLE },
+ { "BHYVE", VIRTUALIZATION_BHYVE },
+ { "Hyper-V", VIRTUALIZATION_MICROSOFT },
+ { "Apple Virtualization", VIRTUALIZATION_APPLE },
+ { "Google Compute Engine", VIRTUALIZATION_GOOGLE }, /* https://cloud.google.com/run/docs/container-contract#sandbox */
};
int r;
return true;
}
- if (__get_cpuid(7, &eax, &ebx, &ecx, &edx)) {
+ if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) {
if (given_flag_in_set(flag, leaf7_ebx, ELEMENTSOF(leaf7_ebx), ebx))
return true;
}
[VIRTUALIZATION_POWERVM] = "powervm",
[VIRTUALIZATION_APPLE] = "apple",
[VIRTUALIZATION_SRE] = "sre",
+ [VIRTUALIZATION_GOOGLE] = "google",
[VIRTUALIZATION_VM_OTHER] = "vm-other",
[VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
VIRTUALIZATION_POWERVM,
VIRTUALIZATION_APPLE,
VIRTUALIZATION_SRE,
+ VIRTUALIZATION_GOOGLE,
VIRTUALIZATION_VM_OTHER,
VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER,
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", p);
- r = unhexmem(strstrip(s), SIZE_MAX, &v, &sz);
+ r = unhexmem(strstrip(s), &v, &sz);
if (r < 0)
return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", p);
- r = unhexmem(strstrip(s), SIZE_MAX, &h, &l);
+ r = unhexmem(strstrip(s), &h, &l);
if (r < 0)
return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
};
}
-int cgroup_context_add_io_device_weight_dup(CGroupContext *c, CGroupIODeviceWeight *w) {
+int cgroup_context_add_io_device_weight_dup(CGroupContext *c, const CGroupIODeviceWeight *w) {
_cleanup_free_ CGroupIODeviceWeight *n = NULL;
assert(c);
assert(w);
- n = new0(CGroupIODeviceWeight, 1);
+ n = new(CGroupIODeviceWeight, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(w->path);
+ *n = (CGroupIODeviceWeight) {
+ .path = strdup(w->path),
+ .weight = w->weight,
+ };
if (!n->path)
return -ENOMEM;
- n->weight = w->weight;
LIST_PREPEND(device_weights, c->io_device_weights, TAKE_PTR(n));
return 0;
}
-int cgroup_context_add_io_device_limit_dup(CGroupContext *c, CGroupIODeviceLimit *l) {
+int cgroup_context_add_io_device_limit_dup(CGroupContext *c, const CGroupIODeviceLimit *l) {
_cleanup_free_ CGroupIODeviceLimit *n = NULL;
assert(c);
assert(l);
n = new0(CGroupIODeviceLimit, 1);
- if (!l)
+ if (!n)
return -ENOMEM;
n->path = strdup(l->path);
return 0;
}
-int cgroup_context_add_io_device_latency_dup(CGroupContext *c, CGroupIODeviceLatency *l) {
+int cgroup_context_add_io_device_latency_dup(CGroupContext *c, const CGroupIODeviceLatency *l) {
_cleanup_free_ CGroupIODeviceLatency *n = NULL;
assert(c);
assert(l);
- n = new0(CGroupIODeviceLatency, 1);
+ n = new(CGroupIODeviceLatency, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(l->path);
+ *n = (CGroupIODeviceLatency) {
+ .path = strdup(l->path),
+ .target_usec = l->target_usec,
+ };
if (!n->path)
return -ENOMEM;
- n->target_usec = l->target_usec;
-
LIST_PREPEND(device_latencies, c->io_device_latencies, TAKE_PTR(n));
return 0;
}
-int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, CGroupBlockIODeviceWeight *w) {
+int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, const CGroupBlockIODeviceWeight *w) {
_cleanup_free_ CGroupBlockIODeviceWeight *n = NULL;
assert(c);
assert(w);
- n = new0(CGroupBlockIODeviceWeight, 1);
+ n = new(CGroupBlockIODeviceWeight, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(w->path);
+ *n = (CGroupBlockIODeviceWeight) {
+ .path = strdup(w->path),
+ .weight = w->weight,
+ };
if (!n->path)
return -ENOMEM;
- n->weight = w->weight;
-
LIST_PREPEND(device_weights, c->blockio_device_weights, TAKE_PTR(n));
return 0;
}
-int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) {
+int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, const CGroupBlockIODeviceBandwidth *b) {
_cleanup_free_ CGroupBlockIODeviceBandwidth *n = NULL;
assert(c);
assert(b);
- n = new0(CGroupBlockIODeviceBandwidth, 1);
+ n = new(CGroupBlockIODeviceBandwidth, 1);
if (!n)
return -ENOMEM;
return 0;
}
-int cgroup_context_add_device_allow_dup(CGroupContext *c, CGroupDeviceAllow *a) {
+int cgroup_context_add_device_allow_dup(CGroupContext *c, const CGroupDeviceAllow *a) {
_cleanup_free_ CGroupDeviceAllow *n = NULL;
assert(c);
assert(a);
- n = new0(CGroupDeviceAllow, 1);
+ n = new(CGroupDeviceAllow, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(a->path);
+ *n = (CGroupDeviceAllow) {
+ .path = strdup(a->path),
+ .permissions = a->permissions,
+ };
if (!n->path)
return -ENOMEM;
- n->permissions = a->permissions;
-
LIST_PREPEND(device_allow, c->device_allow, TAKE_PTR(n));
return 0;
}
-static int cgroup_context_add_socket_bind_item_dup(CGroupContext *c, CGroupSocketBindItem *i, CGroupSocketBindItem *h) {
+static int cgroup_context_add_socket_bind_item_dup(CGroupContext *c, const CGroupSocketBindItem *i, CGroupSocketBindItem *h) {
_cleanup_free_ CGroupSocketBindItem *n = NULL;
assert(c);
assert(i);
- n = new0(CGroupSocketBindItem, 1);
+ n = new(CGroupSocketBindItem, 1);
if (!n)
return -ENOMEM;
return 0;
}
-int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, CGroupSocketBindItem *i) {
+int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, const CGroupSocketBindItem *i) {
return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_allow);
}
-int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, CGroupSocketBindItem *i) {
+int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, const CGroupSocketBindItem *i) {
return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_deny);
}
dst->tasks_accounting = src->tasks_accounting;
dst->ip_accounting = src->ip_accounting;
- dst->memory_oom_group = dst->memory_oom_group;
+ dst->memory_oom_group = src->memory_oom_group;
dst->cpu_weight = src->cpu_weight;
dst->startup_cpu_weight = src->startup_cpu_weight;
return r;
}
+static uint64_t unit_get_effective_limit_one(Unit *u, CGroupLimitType type) {
+ CGroupContext *cc;
+
+ assert(u);
+ assert(UNIT_HAS_CGROUP_CONTEXT(u));
+
+ if (unit_has_name(u, SPECIAL_ROOT_SLICE))
+ switch (type) {
+ case CGROUP_LIMIT_MEMORY_MAX:
+ case CGROUP_LIMIT_MEMORY_HIGH:
+ return physical_memory();
+ case CGROUP_LIMIT_TASKS_MAX:
+ return system_tasks_max();
+ default:
+ assert_not_reached();
+ }
+
+ cc = ASSERT_PTR(unit_get_cgroup_context(u));
+ switch (type) {
+ /* Note: on legacy/hybrid hierarchies memory_max stays CGROUP_LIMIT_MAX unless configured
+ * explicitly. Effective value of MemoryLimit= (cgroup v1) is not implemented. */
+ case CGROUP_LIMIT_MEMORY_MAX:
+ return cc->memory_max;
+ case CGROUP_LIMIT_MEMORY_HIGH:
+ return cc->memory_high;
+ case CGROUP_LIMIT_TASKS_MAX:
+ return cgroup_tasks_max_resolve(&cc->tasks_max);
+ default:
+ assert_not_reached();
+ }
+}
+
+int unit_get_effective_limit(Unit *u, CGroupLimitType type, uint64_t *ret) {
+ uint64_t infimum;
+
+ assert(u);
+ assert(ret);
+ assert(type >= 0);
+ assert(type < _CGROUP_LIMIT_TYPE_MAX);
+
+ if (!UNIT_HAS_CGROUP_CONTEXT(u))
+ return -EINVAL;
+
+ infimum = unit_get_effective_limit_one(u, type);
+ for (Unit *slice = UNIT_GET_SLICE(u); slice; slice = UNIT_GET_SLICE(slice))
+ infimum = MIN(infimum, unit_get_effective_limit_one(slice, type));
+
+ *ret = infimum;
+ return 0;
+}
+
static int unit_get_io_accounting_raw(Unit *u, uint64_t ret[static _CGROUP_IO_ACCOUNTING_METRIC_MAX]) {
static const char *const field_names[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {
[CGROUP_IO_READ_BYTES] = "rbytes=",
};
DEFINE_STRING_TABLE_LOOKUP(cgroup_memory_accounting_metric, CGroupMemoryAccountingMetric);
+
+static const char *const cgroup_limit_type_table[_CGROUP_LIMIT_TYPE_MAX] = {
+ [CGROUP_LIMIT_MEMORY_MAX] = "EffectiveMemoryMax",
+ [CGROUP_LIMIT_MEMORY_HIGH] = "EffectiveMemoryHigh",
+ [CGROUP_LIMIT_TASKS_MAX] = "EffectiveTasksMax",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(cgroup_limit_type, CGroupLimitType);
_CGROUP_MEMORY_ACCOUNTING_METRIC_INVALID = -EINVAL,
} CGroupMemoryAccountingMetric;
+/* Used for limits whose value sets have infimum */
+typedef enum CGroupLimitType {
+ CGROUP_LIMIT_MEMORY_MAX,
+ CGROUP_LIMIT_MEMORY_HIGH,
+ CGROUP_LIMIT_TASKS_MAX,
+ _CGROUP_LIMIT_TYPE_MAX,
+ _CGROUP_LIMIT_INVALID = -EINVAL,
+} CGroupLimitType;
+
typedef struct Unit Unit;
typedef struct Manager Manager;
typedef enum ManagerState ManagerState;
int cgroup_context_add_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p);
int cgroup_context_add_or_update_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p);
int cgroup_context_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path);
-int cgroup_context_add_io_device_limit_dup(CGroupContext *c, CGroupIODeviceLimit *l);
-int cgroup_context_add_io_device_weight_dup(CGroupContext *c, CGroupIODeviceWeight *w);
-int cgroup_context_add_io_device_latency_dup(CGroupContext *c, CGroupIODeviceLatency *l);
-int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, CGroupBlockIODeviceWeight *w);
-int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, CGroupBlockIODeviceBandwidth *b);
-int cgroup_context_add_device_allow_dup(CGroupContext *c, CGroupDeviceAllow *a);
-int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, CGroupSocketBindItem *i);
-int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, CGroupSocketBindItem *i);
-
-static inline int cgroup_context_add_bpf_foreign_program_dup(CGroupContext *c, CGroupBPFForeignProgram *p) {
+static inline int cgroup_context_add_bpf_foreign_program_dup(CGroupContext *c, const CGroupBPFForeignProgram *p) {
return cgroup_context_add_bpf_foreign_program(c, p->attach_type, p->bpffs_path);
}
+int cgroup_context_add_io_device_limit_dup(CGroupContext *c, const CGroupIODeviceLimit *l);
+int cgroup_context_add_io_device_weight_dup(CGroupContext *c, const CGroupIODeviceWeight *w);
+int cgroup_context_add_io_device_latency_dup(CGroupContext *c, const CGroupIODeviceLatency *l);
+int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, const CGroupBlockIODeviceWeight *w);
+int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, const CGroupBlockIODeviceBandwidth *b);
+int cgroup_context_add_device_allow_dup(CGroupContext *c, const CGroupDeviceAllow *a);
+int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, const CGroupSocketBindItem *i);
+int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, const CGroupSocketBindItem *i);
void unit_modify_nft_set(Unit *u, bool add);
int unit_get_cpu_usage(Unit *u, nsec_t *ret);
int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret);
int unit_get_ip_accounting(Unit *u, CGroupIPAccountingMetric metric, uint64_t *ret);
+int unit_get_effective_limit(Unit *u, CGroupLimitType type, uint64_t *ret);
int unit_reset_cpu_accounting(Unit *u);
void unit_reset_memory_accounting_last(Unit *u);
const char* cgroup_io_accounting_metric_to_string(CGroupIOAccountingMetric m) _const_;
CGroupIOAccountingMetric cgroup_io_accounting_metric_from_string(const char *s) _pure_;
+const char* cgroup_limit_type_to_string(CGroupLimitType m) _const_;
+CGroupLimitType cgroup_limit_type_from_string(const char *s) _pure_;
+
const char* cgroup_memory_accounting_metric_to_string(CGroupMemoryAccountingMetric m) _const_;
CGroupMemoryAccountingMetric cgroup_memory_accounting_metric_from_string(const char *s) _pure_;
unit = manager_get_unit_by_pidref(m, &p);
if (!unit) {
- log_unit_warning_errno(from, SYNTHETIC_ERRNO(ENOENT), "Failed to get unit from PIDFD, ingoring: %m");
+ log_unit_warning_errno(from, SYNTHETIC_ERRNO(ENOENT), "Failed to get unit from PIDFD, ignoring: %m");
continue;
}
#include "dbus-kill.h"
#include "dbus-mount.h"
#include "dbus-util.h"
+#include "fstab-util.h"
#include "mount.h"
#include "string-util.h"
#include "unit.h"
sd_bus_error *error) {
Unit *u = UNIT(m);
+ int r;
assert(m);
assert(name);
if (streq(name, "Where"))
return bus_set_transient_path(u, name, &m->where, message, flags, error);
- if (streq(name, "What"))
- return bus_set_transient_string(u, name, &m->parameters_fragment.what, message, flags, error);
+ if (streq(name, "What")) {
+ _cleanup_free_ char *path = NULL;
+ const char *v;
+
+ r = sd_bus_message_read(message, "s", &v);
+ if (r < 0)
+ return r;
+
+ if (!isempty(v)) {
+ path = fstab_node_to_udev_node(v);
+ if (!path)
+ return -ENOMEM;
+
+ /* path_is_valid is not used - see the comment for config_parse_mount_node */
+ if (strlen(path) >= PATH_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Resolved What=%s too long", path);
+ }
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ free_and_replace(m->parameters_fragment.what, path);
+ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "What=%s", strempty(m->parameters_fragment.what));
+ }
+
+ return 1;
+ }
if (streq(name, "Options"))
return bus_set_transient_string(u, name, &m->parameters_fragment.options, message, flags, error);
return sd_bus_message_append(reply, "t", value);
}
+static int property_get_effective_limit(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ uint64_t value = CGROUP_LIMIT_MAX;
+ Unit *u = ASSERT_PTR(userdata);
+ ssize_t type;
+
+ assert(bus);
+ assert(reply);
+ assert(property);
+
+ assert_se((type = cgroup_limit_type_from_string(property)) >= 0);
+ (void) unit_get_effective_limit(u, type, &value);
+ return sd_bus_message_append(reply, "t", value);
+}
+
int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
_cleanup_set_free_ Set *pids = NULL;
SD_BUS_PROPERTY("MemorySwapPeak", "t", property_get_memory_accounting, 0, 0),
SD_BUS_PROPERTY("MemoryZSwapCurrent", "t", property_get_memory_accounting, 0, 0),
SD_BUS_PROPERTY("MemoryAvailable", "t", property_get_available_memory, 0, 0),
+ SD_BUS_PROPERTY("EffectiveMemoryMax", "t", property_get_effective_limit, 0, 0),
+ SD_BUS_PROPERTY("EffectiveMemoryHigh", "t", property_get_effective_limit, 0, 0),
SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0),
SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0),
SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0),
SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0),
+ SD_BUS_PROPERTY("EffectiveTasksMax", "t", property_get_effective_limit, 0, 0),
SD_BUS_PROPERTY("IPIngressBytes", "t", property_get_ip_counter, 0, 0),
SD_BUS_PROPERTY("IPIngressPackets", "t", property_get_ip_counter, 0, 0),
SD_BUS_PROPERTY("IPEgressBytes", "t", property_get_ip_counter, 0, 0),
assert(!m->subscribed);
m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ m->polkit_registry = hashmap_free(m->polkit_registry);
}
int bus_fdset_add_all(Manager *m, FDSet *fds) {
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */
#include "fileio.h"
#include "glob-util.h"
#include "io-util.h"
+#include "iovec-util.h"
#include "label-util.h"
#include "mkdir-label.h"
#include "mount-util.h"
size_t size,
uint64_t *left) {
- _cleanup_free_ void *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {};
size_t add;
int r;
if (encrypted) {
- size_t plaintext_size = 0;
-
- r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size,
- &plaintext, &plaintext_size);
+ r = decrypt_credential_and_warn(
+ id,
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &IOVEC_MAKE(data, size),
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
- data = plaintext;
- size = plaintext_size;
+ data = plaintext.iov_base;
+ size = plaintext.iov_len;
}
add = strlen(id) + size;
/* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
* add them, so that they can act as a "default" if the same credential is specified multiple times. */
HASHMAP_FOREACH(sc, context->set_credentials) {
- _cleanup_(erase_and_freep) void *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {};
const char *data;
size_t size, add;
return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
if (sc->encrypted) {
- r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, NULL, sc->data, sc->size, &plaintext, &size);
+ r = decrypt_credential_and_warn(
+ sc->id,
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &IOVEC_MAKE(sc->data, sc->size),
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
- data = plaintext;
+ data = plaintext.iov_base;
+ size = plaintext.iov_len;
} else {
data = sc->data;
size = sc->size;
#include "strv.h"
#include "terminal-util.h"
#include "utmp-wtmp.h"
+#include "vpick.h"
#define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC)
#define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC)
gid_t gid,
const char *tty,
char ***env, /* updated on success */
- const int fds[], size_t n_fds) {
+ const int fds[], size_t n_fds,
+ int exec_fd) {
#if HAVE_PAM
* those fds are open here that have been opened by PAM. */
(void) close_many(fds, n_fds);
+ /* Also close the 'exec_fd' in the child, since the service manager waits for the EOF induced
+ * by the execve() to wait for completion, and if we'd keep the fd open here in the child
+ * we'd never signal completion. */
+ exec_fd = safe_close(exec_fd);
+
/* Drop privileges - we don't need any to pam_close_session and this will make
* PR_SET_PDEATHSIG work in most cases. If this fails, ignore the error - but expect sd-pam
* threads to fail to exit normally */
return false;
}
-static int setup_ephemeral(const ExecContext *context, ExecRuntime *runtime) {
+static int setup_ephemeral(
+ const ExecContext *context,
+ ExecRuntime *runtime,
+ char **root_image, /* both input and output! modified if ephemeral logic enabled */
+ char **root_directory) { /* ditto */
+
_cleanup_close_ int fd = -EBADF;
+ _cleanup_free_ char *new_root = NULL;
int r;
+ assert(context);
+ assert(root_image);
+ assert(root_directory);
+
+ if (!*root_image && !*root_directory)
+ return 0;
+
if (!runtime || !runtime->ephemeral_copy)
return 0;
+ assert(runtime->ephemeral_storage_socket[0] >= 0);
+ assert(runtime->ephemeral_storage_socket[1] >= 0);
+
+ new_root = strdup(runtime->ephemeral_copy);
+ if (!new_root)
+ return log_oom_debug();
+
r = posix_lock(runtime->ephemeral_storage_socket[0], LOCK_EX);
if (r < 0)
return log_debug_errno(r, "Failed to lock ephemeral storage socket: %m");
if (fd >= 0)
/* We got an fd! That means ephemeral has already been set up, so nothing to do here. */
return 0;
-
if (fd != -EAGAIN)
return log_debug_errno(fd, "Failed to receive file descriptor queued on ephemeral storage socket: %m");
- log_debug("Making ephemeral snapshot of %s to %s",
- context->root_image ?: context->root_directory, runtime->ephemeral_copy);
+ if (*root_image) {
+ log_debug("Making ephemeral copy of %s to %s", *root_image, new_root);
- if (context->root_image)
- fd = copy_file(context->root_image, runtime->ephemeral_copy, O_EXCL, 0600,
- COPY_LOCK_BSD|COPY_REFLINK|COPY_CRTIME);
- else
- fd = btrfs_subvol_snapshot_at(AT_FDCWD, context->root_directory,
- AT_FDCWD, runtime->ephemeral_copy,
- BTRFS_SNAPSHOT_FALLBACK_COPY |
- BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
- BTRFS_SNAPSHOT_RECURSIVE |
- BTRFS_SNAPSHOT_LOCK_BSD);
- if (fd < 0)
- return log_debug_errno(fd, "Failed to snapshot %s to %s: %m",
- context->root_image ?: context->root_directory, runtime->ephemeral_copy);
+ fd = copy_file(*root_image,
+ new_root,
+ O_EXCL,
+ 0600,
+ COPY_LOCK_BSD|
+ COPY_REFLINK|
+ COPY_CRTIME);
+ if (fd < 0)
+ return log_debug_errno(fd, "Failed to copy image %s to %s: %m",
+ *root_image, new_root);
- if (context->root_image) {
/* A root image might be subject to lots of random writes so let's try to disable COW on it
* which tends to not perform well in combination with lots of random writes.
*
*/
r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
if (r < 0)
- log_debug_errno(fd, "Failed to disable copy-on-write for %s, ignoring: %m", runtime->ephemeral_copy);
+ log_debug_errno(fd, "Failed to disable copy-on-write for %s, ignoring: %m", new_root);
+ } else {
+ assert(*root_directory);
+
+ log_debug("Making ephemeral snapshot of %s to %s", *root_directory, new_root);
+
+ fd = btrfs_subvol_snapshot_at(
+ AT_FDCWD, *root_directory,
+ AT_FDCWD, new_root,
+ BTRFS_SNAPSHOT_FALLBACK_COPY |
+ BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
+ BTRFS_SNAPSHOT_RECURSIVE |
+ BTRFS_SNAPSHOT_LOCK_BSD);
+ if (fd < 0)
+ return log_debug_errno(fd, "Failed to snapshot directory %s to %s: %m",
+ *root_directory, new_root);
}
r = send_one_fd(runtime->ephemeral_storage_socket[1], fd, MSG_DONTWAIT);
if (r < 0)
return log_debug_errno(r, "Failed to queue file descriptor on ephemeral storage socket: %m");
+ if (*root_image)
+ free_and_replace(*root_image, new_root);
+ else {
+ assert(*root_directory);
+ free_and_replace(*root_directory, new_root);
+ }
+
return 1;
}
return 0;
}
+static int pick_versions(
+ const ExecContext *context,
+ const ExecParameters *params,
+ char **ret_root_image,
+ char **ret_root_directory) {
+
+ int r;
+
+ assert(context);
+ assert(params);
+ assert(ret_root_image);
+ assert(ret_root_directory);
+
+ if (context->root_image) {
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ context->root_image,
+ &pick_filter_image_raw,
+ PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
+ &result);
+ if (r < 0)
+ return r;
+
+ if (!result.path)
+ return log_exec_debug_errno(context, params, SYNTHETIC_ERRNO(ENOENT), "No matching entry in .v/ directory %s found.", context->root_image);
+
+ *ret_root_image = TAKE_PTR(result.path);
+ *ret_root_directory = NULL;
+ return r;
+ }
+
+ if (context->root_directory) {
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ context->root_directory,
+ &pick_filter_image_dir,
+ PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
+ &result);
+ if (r < 0)
+ return r;
+
+ if (!result.path)
+ return log_exec_debug_errno(context, params, SYNTHETIC_ERRNO(ENOENT), "No matching entry in .v/ directory %s found.", context->root_directory);
+
+ *ret_root_image = NULL;
+ *ret_root_directory = TAKE_PTR(result.path);
+ return r;
+ }
+
+ *ret_root_image = *ret_root_directory = NULL;
+ return 0;
+}
+
static int apply_mount_namespace(
ExecCommandFlags command_flags,
const ExecContext *context,
_cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL,
**read_write_paths_cleanup = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL,
- *extension_dir = NULL, *host_os_release_stage = NULL;
- const char *root_dir = NULL, *root_image = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL;
+ *extension_dir = NULL, *host_os_release_stage = NULL, *root_image = NULL, *root_dir = NULL;
+ const char *tmp_dir = NULL, *var_tmp_dir = NULL;
char **read_write_paths;
bool needs_sandboxing, setup_os_release_symlink;
BindMount *bind_mounts = NULL;
CLEANUP_ARRAY(bind_mounts, n_bind_mounts, bind_mount_free_many);
if (params->flags & EXEC_APPLY_CHROOT) {
- r = setup_ephemeral(context, runtime);
+ r = pick_versions(
+ context,
+ params,
+ &root_image,
+ &root_dir);
if (r < 0)
return r;
- if (context->root_image)
- root_image = (runtime ? runtime->ephemeral_copy : NULL) ?: context->root_image;
- else
- root_dir = (runtime ? runtime->ephemeral_copy : NULL) ?: context->root_directory;
+ r = setup_ephemeral(
+ context,
+ runtime,
+ &root_image,
+ &root_dir);
+ if (r < 0)
+ return r;
}
r = compile_bind_mounts(context, params, &bind_mounts, &n_bind_mounts, &empty_directories);
* wins here. (See above.) */
/* All fds passed in the fds array will be closed in the pam child process. */
- r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds);
+ r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds, params->exec_fd);
if (r < 0) {
*exit_status = EXIT_PAM;
return log_exec_error_errno(context, params, r, "Failed to set up PAM session: %m");
FOREACH_ARRAY(i, c->directories[dt].items, c->directories[dt].n_items) {
_cleanup_free_ char *path_escaped = NULL;
- path_escaped = shell_escape(i->path, ":");
+ path_escaped = shell_escape(i->path, ":" WHITESPACE);
if (!path_escaped)
return log_oom_debug();
STRV_FOREACH(d, i->symlinks) {
_cleanup_free_ char *link_escaped = NULL;
- link_escaped = shell_escape(*d, ":");
+ link_escaped = shell_escape(*d, ":" WHITESPACE);
if (!link_escaped)
return log_oom_debug();
FOREACH_ARRAY(mount, c->bind_mounts, c->n_bind_mounts) {
_cleanup_free_ char *src_escaped = NULL, *dst_escaped = NULL;
- src_escaped = shell_escape(mount->source, ":");
+ src_escaped = shell_escape(mount->source, ":" WHITESPACE);
if (!src_escaped)
return log_oom_debug();
- dst_escaped = shell_escape(mount->destination, ":");
+ dst_escaped = shell_escape(mount->destination, ":" WHITESPACE);
if (!dst_escaped)
return log_oom_debug();
FOREACH_ARRAY(mount, c->mount_images, c->n_mount_images) {
_cleanup_free_ char *s = NULL, *source_escaped = NULL, *dest_escaped = NULL;
- source_escaped = shell_escape(mount->source, " ");
+ source_escaped = shell_escape(mount->source, WHITESPACE);
if (!source_escaped)
return log_oom_debug();
- dest_escaped = shell_escape(mount->destination, " ");
+ dest_escaped = shell_escape(mount->destination, WHITESPACE);
if (!dest_escaped)
return log_oom_debug();
FOREACH_ARRAY(mount, c->extension_images, c->n_extension_images) {
_cleanup_free_ char *s = NULL, *source_escaped = NULL;
- source_escaped = shell_escape(mount->source, ":");
+ source_escaped = shell_escape(mount->source, ":" WHITESPACE);
if (!source_escaped)
return log_oom_debug();
return r;
} else if ((val = startswith(l, "exec-context-root-hash="))) {
c->root_hash = mfree(c->root_hash);
- r = unhexmem(val, strlen(val), &c->root_hash, &c->root_hash_size);
+ r = unhexmem(val, &c->root_hash, &c->root_hash_size);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-root-hash-sig="))) {
c->root_hash_sig = mfree(c->root_hash_sig);
- r= unbase64mem(val, strlen(val), &c->root_hash_sig, &c->root_hash_sig_size);
+ r= unbase64mem(val, &c->root_hash_sig, &c->root_hash_sig_size);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-root-ephemeral="))) {
_cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL;
const char *p;
- r = extract_first_word(&val, &tuple, WHITESPACE, EXTRACT_RETAIN_ESCAPE);
+ /* Use EXTRACT_UNESCAPE_RELAX here, as we unescape the colons in subsequent calls */
+ r = extract_first_word(&val, &tuple, WHITESPACE, EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX);
if (r < 0)
return r;
if (r == 0)
if (c->stdin_data)
return -EINVAL; /* duplicated */
- r = unbase64mem(val, strlen(val), &c->stdin_data, &c->stdin_data_size);
+ r = unbase64mem(val, &c->stdin_data, &c->stdin_data_size);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-tty-path="))) {
.encrypted = r,
};
- r = unbase64mem(data, strlen(data), &sc->data, &sc->size);
+ r = unbase64mem(data, &sc->data, &sc->size);
if (r < 0)
return r;
colon++;
if (base64) {
- r = unbase64mem(colon, SIZE_MAX, &binary, &l);
+ r = unbase64mem(colon, &binary, &l);
if (r < 0) {
log_warning_errno(r, "Failed to decode binary credential '%s' data, ignoring: %m", n);
return 0;
/* Optionally base64 decode the data, if requested, to allow binary credentials */
if (unbase64) {
- r = unbase64mem(eq + 1, nul - (eq + 1), &buf, &buflen);
+ r = unbase64mem_full(eq + 1, nul - (eq + 1), /* secure = */ false, &buf, &buflen);
if (r < 0) {
log_warning_errno(r, "Failed to base64 decode credential '%s', ignoring: %m", cn);
continue;
{{ EXEC_CONTEXT_CONFIG_ITEMS('Socket') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Socket') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Socket') }}
-Mount.What, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.what)
+Mount.What, config_parse_mount_node, 0, offsetof(Mount, parameters_fragment.what)
Mount.Where, config_parse_unit_path_printf, 0, offsetof(Mount, where)
Mount.Options, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.options)
Mount.Type, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.fstype)
Automount.ExtraOptions, config_parse_unit_string_printf, 0, offsetof(Automount, extra_options)
Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode)
Automount.TimeoutIdleSec, config_parse_sec_fix_0, 0, offsetof(Automount, timeout_idle_usec)
-Swap.What, config_parse_unit_path_printf, 0, offsetof(Swap, parameters_fragment.what)
+Swap.What, config_parse_mount_node, 0, offsetof(Swap, parameters_fragment.what)
Swap.Priority, config_parse_swap_priority, 0, 0
Swap.Options, config_parse_unit_string_printf, 0, offsetof(Swap, parameters_fragment.options)
Swap.TimeoutSec, config_parse_sec_fix_0, 0, offsetof(Swap, timeout_usec)
#include "fileio.h"
#include "firewall-util.h"
#include "fs-util.h"
+#include "fstab-util.h"
#include "hexdecoct.h"
#include "iovec-util.h"
#include "ioprio-util.h"
return 0;
}
- r = unbase64mem(rvalue, SIZE_MAX, &p, &sz);
+ r = unbase64mem(rvalue, &p, &sz);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to decode base64 data, ignoring: %s", rvalue);
}
/* We have a roothash to decode, eg: RootHash=012345789abcdef */
- r = unhexmem(rvalue, strlen(rvalue), &roothash_decoded, &roothash_decoded_size);
+ r = unhexmem(rvalue, &roothash_decoded, &roothash_decoded_size);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to decode RootHash=, ignoring: %s", rvalue);
return 0;
}
/* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */
- r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size);
+ r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to decode RootHashSignature=, ignoring: %s", rvalue);
return 0;
return 0;
}
- r = unit_full_printf_full(u, rvalue, PATH_MAX, &n);
+ r = unit_path_printf(u, rvalue, &n);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
return 0;
if (r == 0)
break;
- r = unit_full_printf_full(u, source, PATH_MAX, &sresolved);
+ r = unit_path_printf(u, source, &sresolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to resolve unit specifiers in \"%s\", ignoring: %m", source);
return 0;
}
+int config_parse_mount_node(
+ 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) {
+
+ const Unit *u = ASSERT_PTR(userdata);
+ _cleanup_free_ char *resolved = NULL, *path = NULL;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = unit_full_printf(u, rvalue, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+
+ path = fstab_node_to_udev_node(resolved);
+ if (!path)
+ return log_oom();
+
+ /* The source passed is not necessarily something we understand, and we pass it as-is to mount/swapon,
+ * so path_is_valid is not used. But let's check for basic sanety, i.e. if the source is longer than
+ * PATH_MAX, you're likely doing something wrong. */
+ if (strlen(path) >= PATH_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Resolved mount path '%s' too long, ignoring.", path);
+ return 0;
+ }
+
+ return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata);
+}
+
static int merge_by_names(Unit *u, Set *names, const char *id) {
char *k;
int r;
{ config_parse_job_mode_isolate, "BOOLEAN" },
{ config_parse_personality, "PERSONALITY" },
{ config_parse_log_filter_patterns, "REGEX" },
+ { config_parse_mount_node, "NODE" },
};
const char *prev = NULL;
CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch);
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
+CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
if (proc_cmdline_value_missing(key, value))
return 0;
- r = unbase64mem(value, SIZE_MAX, &p, &sz);
+ r = unbase64mem(value, &p, &sz);
if (r < 0)
log_warning_errno(r, "Failed to parse systemd.random_seed= argument, ignoring: %s", value);
}
static int short_uid_range(const char *path) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
int r;
assert(path);
return log_debug_errno(r, "Failed to parse mount option '%s': %m", str);
ro = flags & MS_RDONLY;
- if (ro)
- flags ^= MS_RDONLY;
+ flags &= ~MS_RDONLY;
MountEntry *me = mount_list_extend(ml);
if (!me)
#include "cgroup-util.h"
#include "format-util.h"
#include "macro.h"
+#include "sd-path.h"
#include "specifier.h"
#include "string-util.h"
#include "strv.h"
return 0;
}
+static int specifier_shared_data_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
+ const Unit *u = ASSERT_PTR(userdata);
+
+ assert(ret);
+
+ return sd_path_lookup(MANAGER_IS_SYSTEM(u->manager) ? SD_PATH_SYSTEM_SHARED : SD_PATH_USER_SHARED, NULL, ret);
+}
+
int unit_name_printf(const Unit *u, const char* format, char **ret) {
/*
* This will use the passed string as format string and replace the following specifiers (which should all be
*
* %C: the cache directory root (e.g. /var/cache or $XDG_CACHE_HOME)
* %d: the credentials directory ($CREDENTIALS_DIRECTORY)
+ * %D: the shared data root (e.g. /usr/share or $XDG_DATA_HOME)
* %E: the configuration directory root (e.g. /etc or $XDG_CONFIG_HOME)
* %L: the log directory root (e.g. /var/log or $XDG_STATE_HOME/log)
* %S: the state directory root (e.g. /var/lib or $XDG_STATE_HOME)
{ 'C', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_CACHE) },
{ 'd', specifier_credentials_dir, NULL },
+ { 'D', specifier_shared_data_dir, NULL },
{ 'E', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_CONFIGURATION) },
{ 'L', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_LOGS) },
{ 'S', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_STATE) },
#include "strv.h"
#include "sync-util.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
/* The maximum size up to which we process coredumps. We use 1G on 32-bit systems, and 32G on 64-bit systems */
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
#include <unistd.h>
#include "build.h"
+#include "bus-polkit.h"
#include "creds-util.h"
#include "dirent-util.h"
#include "escape.h"
}
if (encrypted) {
- _cleanup_(erase_and_freep) void *plaintext = NULL;
- size_t plaintext_size;
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {};
r = decrypt_credential_and_warn(
*cn,
timestamp,
arg_tpm2_device,
arg_tpm2_signature,
- data, size,
- &plaintext, &plaintext_size);
+ &IOVEC_MAKE(data, size),
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
erase_and_free(data);
- data = TAKE_PTR(plaintext);
- size = plaintext_size;
+ data = TAKE_PTR(plaintext.iov_base);
+ size = plaintext.iov_len;
}
r = write_blob(stdout, data, size);
}
static int verb_encrypt(int argc, char **argv, void *userdata) {
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {};
_cleanup_free_ char *base64_buf = NULL, *fname = NULL;
- _cleanup_(erase_and_freep) char *plaintext = NULL;
const char *input_path, *output_path, *name;
- _cleanup_free_ void *output = NULL;
- size_t plaintext_size, output_size;
ssize_t base64_size;
usec_t timestamp;
int r;
input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
if (input_path)
- r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &plaintext, &plaintext_size);
+ r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &plaintext.iov_base, &plaintext.iov_len);
else
- r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, &plaintext, &plaintext_size);
+ r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &plaintext.iov_base, &plaintext.iov_len);
if (r == -E2BIG)
return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX);
if (r < 0)
arg_tpm2_pcr_mask,
arg_tpm2_public_key,
arg_tpm2_public_key_pcr_mask,
- plaintext, plaintext_size,
- &output, &output_size);
+ &plaintext,
+ /* flags= */ 0,
+ &output);
if (r < 0)
return r;
- base64_size = base64mem_full(output, output_size, arg_pretty ? 69 : 79, &base64_buf);
+ base64_size = base64mem_full(output.iov_base, output.iov_len, arg_pretty ? 69 : 79, &base64_buf);
if (base64_size < 0)
return base64_size;
}
static int verb_decrypt(int argc, char **argv, void *userdata) {
- _cleanup_(erase_and_freep) void *plaintext = NULL;
- _cleanup_free_ char *input = NULL, *fname = NULL;
+ _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {};
+ _cleanup_free_ char *fname = NULL;
_cleanup_fclose_ FILE *output_file = NULL;
const char *input_path, *output_path, *name;
- size_t input_size, plaintext_size;
usec_t timestamp;
FILE *f;
int r;
input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
if (input_path)
- r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &input, &input_size);
+ r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &input, &input.iov_len);
else
- r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, &input, &input_size);
+ r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &input, &input.iov_len);
if (r == -E2BIG)
return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX);
if (r < 0)
timestamp,
arg_tpm2_device,
arg_tpm2_signature,
- input, input_size,
- &plaintext, &plaintext_size);
+ &input,
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
} else
f = stdout;
- r = write_blob(f, plaintext, plaintext_size);
+ r = write_blob(f, plaintext.iov_base, plaintext.iov_len);
if (r < 0)
return r;
}
static int verb_setup(int argc, char **argv, void *userdata) {
- size_t size;
+ _cleanup_(iovec_done_erase) struct iovec host_key = {};
int r;
- r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, NULL, &size);
+ r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, &host_key);
if (r < 0)
return log_error_errno(r, "Failed to setup credentials host key: %m");
- log_info("%zu byte credentials host key set up.", size);
+ log_info("%zu byte credentials host key set up.", host_key.iov_len);
return EXIT_SUCCESS;
}
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK;
- else if (streq(optarg, "tpm2-absent"))
- arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT;
+ else if (STR_IN_SET(optarg, "null", "tpm2-absent"))
+ arg_with_key = CRED_AES256_GCM_BY_NULL;
else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
{ "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodEncryptParameters, data), 0 },
{ "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, timestamp), 0 },
{ "notAfter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, not_after), 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
_cleanup_(method_encrypt_parameters_done) MethodEncryptParameters p = {
.not_after = UINT64_MAX,
};
_cleanup_(iovec_done) struct iovec output = {};
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
int r;
assert(link);
if (p.not_after != UINT64_MAX && p.not_after < p.timestamp)
return varlink_error_invalid_parameter_name(link, "notAfter");
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ "io.systemd.credentials.encrypt",
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
r = encrypt_credential_and_warn(
arg_with_key,
p.name,
arg_tpm2_pcr_mask,
arg_tpm2_public_key,
arg_tpm2_public_key_pcr_mask,
- p.text ?: p.data.iov_base, p.text ? strlen(p.text) : p.data.iov_len,
- &output.iov_base, &output.iov_len);
+ p.text ? &IOVEC_MAKE_STRING(p.text) : &p.data,
+ /* flags= */ 0,
+ &output);
if (r < 0)
return r;
static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
static const JsonDispatch dispatch_table[] = {
- { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
- { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), 0 },
- { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
+ { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
+ { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), JSON_MANDATORY },
+ { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
_cleanup_(method_decrypt_parameters_done) MethodDecryptParameters p = {
.timestamp = UINT64_MAX,
};
_cleanup_(iovec_done_erase) struct iovec output = {};
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
int r;
assert(link);
if (p.name && !credential_name_valid(p.name))
return varlink_error_invalid_parameter_name(link, "name");
- if (!p.blob.iov_base)
- return varlink_error_invalid_parameter_name(link, "blob");
if (p.timestamp == UINT64_MAX)
p.timestamp = now(CLOCK_REALTIME);
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ "io.systemd.credentials.decrypt",
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
r = decrypt_credential_and_warn(
p.name,
p.timestamp,
arg_tpm2_device,
arg_tpm2_signature,
- p.blob.iov_base, p.blob.iov_len,
- &output.iov_base, &output.iov_len);
+ &p.blob,
+ /* flags= */ 0,
+ &output);
if (r == -EBADMSG)
return varlink_error(link, "io.systemd.Credentials.BadFormat", NULL);
if (r == -EREMOTE)
if (arg_varlink) {
_cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
+ _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL;
/* Invocation as Varlink service */
- r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA);
+ r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
+ varlink_server_set_userdata(varlink_server, &polkit_registry);
+
r = varlink_server_loop_auto(varlink_server);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+-->
+
+<policyconfig>
+
+ <vendor>The systemd Project</vendor>
+ <vendor_url>https://systemd.io</vendor_url>
+
+ <action id="io.systemd.credentials.encrypt">
+ <description gettext-domain="systemd">Allow encryption and signing of system credentials.</description>
+ <message gettext-domain="systemd">Authentication is required for an application to encrypt and sign a system credential.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="io.systemd.credentials.decrypt">
+ <description gettext-domain="systemd">Allow decryption of system credentials.</description>
+ <message gettext-domain="systemd">Authentication is required for an application to decrypto a system credential.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+</policyconfig>
install_emptydir(sysconfdir / 'credstore.encrypted',
install_mode : 'rwx------')
endif
+
+install_data('io.systemd.credentials.policy',
+ install_dir : polkitpolicydir)
#include "openssl-util.h"
#include "pkcs11-util.h"
+static int uri_set_private_class(const char *uri, char **ret_uri) {
+ _cleanup_(sym_p11_kit_uri_freep) P11KitUri *p11kit_uri = NULL;
+ _cleanup_free_ char *private_uri = NULL;
+ int r;
+
+ r = uri_from_string(uri, &p11kit_uri);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", uri);
+
+ if (sym_p11_kit_uri_get_attribute(p11kit_uri, CKA_CLASS)) {
+ CK_OBJECT_CLASS class = CKO_PRIVATE_KEY;
+ CK_ATTRIBUTE attribute = { CKA_CLASS, &class, sizeof(class) };
+
+ if (sym_p11_kit_uri_set_attribute(p11kit_uri, &attribute) != P11_KIT_URI_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set class for URI '%s': %m", uri);
+
+ if (sym_p11_kit_uri_format(p11kit_uri, P11_KIT_URI_FOR_ANY, &private_uri) != P11_KIT_URI_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to format PKCS#11 URI: %m");
+ }
+
+ *ret_uri = TAKE_PTR(private_uri);
+ return 0;
+}
+
int enroll_pkcs11(
struct crypt_device *cd,
const void *volume_key,
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- _cleanup_free_ char *keyslot_as_string = NULL;
+ _cleanup_free_ char *keyslot_as_string = NULL, *private_uri = NULL;
size_t decrypted_key_size, saved_key_size;
_cleanup_free_ void *saved_key = NULL;
_cleanup_(X509_freep) X509 *cert = NULL;
ssize_t base64_encoded_size;
const char *node;
- int keyslot, r;
+ int r;
assert_se(cd);
assert_se(volume_key);
if (r < 0)
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
- keyslot = crypt_keyslot_add_by_volume_key(
+ int keyslot = crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return log_oom();
+ /* Change 'type=cert' in the provided URI to 'type=private' before storing in a LUKS2 header.
+ This allows users to use output of some PKCS#11 tools directly without modifications. */
+ r = uri_set_private_class(uri, &private_uri);
+ if (r < 0)
+ return r;
+
r = json_build(&v,
- JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
- JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
- JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)),
- JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(saved_key, saved_key_size))));
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
+ JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
+ JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(private_uri ?: uri)),
+ JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(saved_key, saved_key_size))));
if (r < 0)
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-policy-hash' field.");
- r = unhexmem(json_variant_string(w), SIZE_MAX, &thash, &thash_size);
+ r = unhexmem(json_variant_string(w), &thash, &thash_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-policy-hash' field.");
bool use_pin,
const char *pcrlock_path) {
- _cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
- _cleanup_free_ void *srk_buf = NULL;
- size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL;
+ _cleanup_(iovec_done) struct iovec srk = {}, blob = {}, pubkey = {};
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL;
ssize_t base64_encoded_size;
}
TPM2B_PUBLIC public = {};
- r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (pubkey_path || signature_path || r != -ENOENT)
return log_error_errno(r, "Failed to read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
pubkey_pcr_mask = 0;
} else {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
r = tpm2_calculate_sealing_policy(
hash_pcr_values,
n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
use_pin,
pcrlock_path ? &pcrlock_policy : NULL,
&policy);
seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
- /* secret= */ NULL, /* secret_size= */ 0,
+ /* secret= */ NULL,
&policy,
pin_str,
- &secret, &secret_size,
- &blob, &blob_size,
- &srk_buf, &srk_buf_size);
+ &secret,
+ &blob,
+ &srk);
else
r = tpm2_seal(tpm2_context,
seal_key_handle,
&policy,
pin_str,
- &secret, &secret_size,
- &blob, &blob_size,
+ &secret,
+ &blob,
/* ret_primary_alg= */ NULL,
- &srk_buf, &srk_buf_size);
+ &srk);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
}
/* If possible, verify the sealed data object. */
- if ((!pubkey || signature_json) && !any_pcr_value_specified && !device_key) {
- _cleanup_(erase_and_freep) void *secret2 = NULL;
- size_t secret2_size;
+ if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) {
+ _cleanup_(iovec_done_erase) struct iovec secret2 = {};
log_debug("Unsealing for verification...");
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
hash_pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
signature_json,
pin_str,
pcrlock_path ? &pcrlock_policy : NULL,
/* primary_alg= */ 0,
- blob, blob_size,
- policy.buffer, policy.size,
- srk_buf, srk_buf_size,
- &secret2, &secret2_size);
+ &blob,
+ &IOVEC_MAKE(policy.buffer, policy.size),
+ &srk,
+ &secret2);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
- if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
+ if (iovec_memcmp(&secret, &secret2) != 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
}
/* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
- base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
keyslot,
hash_pcr_mask,
hash_pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
/* primary_alg= */ 0,
- blob, blob_size,
- policy.buffer, policy.size,
- use_pin ? binary_salt : NULL,
- use_pin ? sizeof(binary_salt) : 0,
- srk_buf, srk_buf_size,
+ &blob,
+ &IOVEC_MAKE(policy.buffer, policy.size),
+ use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
+ &srk,
flags,
&v);
if (r < 0)
assert(!key);
assert(key_size == 0);
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size);
+ r = unbase64mem(json_variant_string(w), &key, &key_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded key.");
}
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m");
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded salt: %m.");
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
- size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
- _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {};
+ _cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
systemd_tpm2_plugin_params params = {
r = tpm2_parse_luks2_json(
v,
- NULL,
+ /* ret_keyslot= */ NULL,
&hash_pcr_mask,
&pcr_bank,
&pubkey,
- &pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob,
- &blob_size,
&policy_hash,
- &policy_hash_size,
&salt,
- &salt_size,
- &srk_buf,
- &srk_buf_size,
+ &srk,
&flags);
if (r < 0)
return log_debug_open_error(cd, r);
params.device,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
params.signature_path,
pin_string,
params.pcrlock_path,
primary_alg,
- blob,
- blob_size,
- policy_hash,
- policy_hash_size,
- salt,
- salt_size,
- srk_buf,
- srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
flags,
- &decrypted_key,
- &decrypted_key_size);
+ &decrypted_key);
if (r < 0)
return log_debug_open_error(cd, r);
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_debug_open_error(cd, base64_encoded_size);
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
+ _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags = 0;
&hash_pcr_mask,
&pcr_bank,
&pubkey,
- &pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob,
- &blob_size,
&policy_hash,
- &policy_hash_size,
&salt,
- &salt_size,
- &srk_buf,
- &srk_buf_size,
+ &srk,
&flags);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
if (!pubkey_pcrs_str)
return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m");
- r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str);
+ r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
- r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str);
+ r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
- r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str);
+ r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK));
- crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt));
- crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf));
+ crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt)));
+ crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk)));
}
/*
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
return 1;
}
- r = unhexmem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unhexmem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
if (!w)
return -EINVAL;
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
+ r = unbase64mem(json_variant_string(w), &cid, &cid_size);
if (r < 0)
return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m");
if (!w)
return -EINVAL;
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
+ r = unbase64mem(json_variant_string(w), &salt, &salt_size);
if (r < 0)
return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m");
if (!w)
return -EINVAL;
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size);
+ r = unbase64mem(json_variant_string(w), &key, &key_size);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pin,
const char *pcrlock_path,
uint16_t primary_alg,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *blob,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size) {
+ struct iovec *ret_decrypted_key) {
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ char *auto_device = NULL;
_cleanup_(erase_and_freep) char *b64_salted_pin = NULL;
int r;
- assert(salt || salt_size == 0);
+ assert(iovec_is_valid(salt));
assert(ret_decrypted_key);
- assert(ret_decrypted_key_size);
if (!device) {
r = tpm2_find_device_auto(&auto_device);
if ((flags & TPM2_FLAGS_USE_PIN) && !pin)
return -ENOANO;
- if (pin && salt_size > 0) {
+ if (pin && iovec_is_set(salt)) {
uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
CLEANUP_ERASE(salted_pin);
- r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt, salt_size, salted_pin);
+ r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt->iov_base, salt->iov_len, salted_pin);
if (r < 0)
return log_error_errno(r, "Failed to perform PBKDF2: %m");
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ pubkey,
pubkey_pcr_mask,
signature_json,
pin,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg,
- key_data, key_data_size,
- policy_hash, policy_hash_size,
- srk_buf, srk_buf_size,
- ret_decrypted_key, ret_decrypted_key_size);
+ blob,
+ policy_hash,
+ srk,
+ ret_decrypted_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
const char *device,
uint32_t pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *pin,
uint16_t primary_alg,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size);
+ struct iovec *decrypted_key);
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size) {
+ struct iovec *ret_decrypted_key) {
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ void *loaded_blob = NULL;
_cleanup_free_ char *auto_device = NULL;
- size_t blob_size;
- const void *blob;
+ struct iovec blob;
int r;
- assert(salt || salt_size == 0);
+ assert(iovec_is_valid(salt));
if (!device) {
r = tpm2_find_device_auto(&auto_device);
device = auto_device;
}
- if (key_data) {
- blob = key_data;
- blob_size = key_data_size;
- } else {
+ if (iovec_is_set(key_data))
+ blob = *key_data;
+ else {
_cleanup_free_ char *bindname = NULL;
/* If we read the salt via AF_UNIX, make this client recognizable */
key_file_size == 0 ? SIZE_MAX : key_file_size,
READ_FULL_FILE_CONNECT_SOCKET,
bindname,
- (char**) &loaded_blob, &blob_size);
+ (char**) &loaded_blob, &blob.iov_len);
if (r < 0)
return r;
- blob = loaded_blob;
+ blob.iov_base = loaded_blob;
}
if (pubkey_pcr_mask != 0) {
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ pubkey,
pubkey_pcr_mask,
signature_json,
/* pin= */ NULL,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg,
- blob,
- blob_size,
+ &blob,
policy_hash,
- policy_hash_size,
- srk_buf,
- srk_buf_size,
- ret_decrypted_key,
- ret_decrypted_key_size);
+ srk,
+ ret_decrypted_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
if (r < 0)
return r;
- if (salt_size > 0) {
+ if (iovec_is_set(salt)) {
uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
CLEANUP_ERASE(salted_pin);
- r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt, salt_size, salted_pin);
+ r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt->iov_base, salt->iov_len, salted_pin);
if (r < 0)
return log_error_errno(r, "Failed to perform PBKDF2: %m");
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ pubkey,
pubkey_pcr_mask,
signature_json,
b64_salted_pin,
pcrlock_path ? &pcrlock_policy : NULL,
primary_alg,
- blob,
- blob_size,
+ &blob,
policy_hash,
- policy_hash_size,
- srk_buf,
- srk_buf_size,
- ret_decrypted_key,
- ret_decrypted_key_size);
+ srk,
+ ret_decrypted_key);
if (r < 0) {
log_error_errno(r, "Failed to unseal secret using TPM2: %m");
int start_token,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
assert(cd);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags;
&keyslot,
&hash_pcr_mask,
&pcr_bank,
- &pubkey, &pubkey_size,
+ &pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob, &blob_size,
- &policy_hash, &policy_hash_size,
- &salt, &salt_size,
- &srk_buf, &srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
&flags);
if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
continue;
*ret_hash_pcr_mask = hash_pcr_mask;
*ret_pcr_bank = pcr_bank;
- *ret_pubkey = TAKE_PTR(pubkey);
- *ret_pubkey_size = pubkey_size;
+ *ret_pubkey = TAKE_STRUCT(pubkey);
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
*ret_primary_alg = primary_alg;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
- *ret_policy_hash = TAKE_PTR(policy_hash);
- *ret_policy_hash_size = policy_hash_size;
- *ret_salt = TAKE_PTR(salt);
- *ret_salt_size = salt_size;
+ *ret_blob = TAKE_STRUCT(blob);
+ *ret_policy_hash = TAKE_STRUCT(policy_hash);
+ *ret_salt = TAKE_STRUCT(salt);
*ret_keyslot = keyslot;
*ret_token = token;
- *ret_srk_buf = TAKE_PTR(srk_buf);
- *ret_srk_buf_size = srk_buf_size;
+ *ret_srk = TAKE_STRUCT(srk);
*ret_flags = flags;
return 0;
}
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t salt_srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size);
+ struct iovec *ret_decrypted_key);
int find_tpm2_auto_data(
struct crypt_device *cd,
int start_token,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token);
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t salt_srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size) {
+ struct iovec *ret_decrypted_key) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support not available.");
int start_token,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
_cleanup_free_ void *cid = NULL;
size_t cid_size;
- r = unbase64mem(val, SIZE_MAX, &cid, &cid_size);
+ r = unbase64mem(val, &cid, &cid_size);
if (r < 0)
return log_error_errno(r, "Failed to decode FIDO2 CID data: %m");
struct crypt_device *cd,
const char *name,
const char *key_file,
- const void *key_data,
- size_t key_data_size,
+ const struct iovec *key_data,
usec_t until,
uint32_t flags,
bool pass_volume_key) {
_cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
- _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_free_ char *friendly = NULL;
int keyslot = arg_key_slot, r;
- size_t decrypted_key_size;
assert(cd);
assert(name);
return log_oom();
for (;;) {
- if (key_file || key_data) {
+ if (key_file || iovec_is_set(key_data)) {
/* If key data is specified, use that */
r = acquire_tpm2_key(
arg_tpm2_device,
arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask,
UINT16_MAX,
- /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature_path= */ NULL,
/* pcrlock_path= */ NULL,
/* primary_alg= */ 0,
key_file, arg_keyfile_size, arg_keyfile_offset,
- key_data, key_data_size,
- /* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
- /* salt= */ NULL, /* salt_size= */ 0,
- /* srk_buf= */ NULL, /* srk_buf_size= */ 0,
+ key_data,
+ /* policy_hash= */ NULL, /* we don't know the policy hash */
+ /* salt= */ NULL,
+ /* srk= */ NULL,
arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
until,
arg_headless,
arg_ask_password_flags,
- &decrypted_key, &decrypted_key_size);
+ &decrypted_key);
if (r >= 0)
break;
if (IN_SET(r, -EACCES, -ENOLCK))
}
if (r == -EOPNOTSUPP) { /* Plugin not available, let's process TPM2 stuff right here instead */
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL;
- size_t blob_size, policy_hash_size;
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
bool found_some = false;
int token = 0; /* first token to look at */
* works. */
for (;;) {
- _cleanup_free_ void *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
- size_t pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
+ _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {};
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
token, /* search for the token with this index, or any later index than this */
&hash_pcr_mask,
&pcr_bank,
- &pubkey, &pubkey_size,
+ &pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob, &blob_size,
- &policy_hash, &policy_hash_size,
- &salt, &salt_size,
- &srk_buf, &srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
&tpm2_flags,
&keyslot,
&token);
arg_tpm2_device,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
arg_tpm2_signature,
arg_tpm2_pcrlock,
primary_alg,
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
- blob, blob_size,
- policy_hash, policy_hash_size,
- salt, salt_size,
- srk_buf, srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
tpm2_flags,
until,
arg_headless,
arg_ask_password_flags,
- &decrypted_key, &decrypted_key_size);
+ &decrypted_key);
if (IN_SET(r, -EACCES, -ENOLCK))
return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking.");
if (r != -EPERM)
log_debug("Got one or more potentially relevant udev events, rescanning for TPM2...");
}
- assert(decrypted_key);
if (pass_volume_key)
- r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key.iov_base, decrypted_key.iov_len, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_oom();
crypt_get_device_name(cd));
if (arg_tpm2_device || arg_tpm2_device_auto)
- return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key);
+ return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, &IOVEC_MAKE(key_data, key_data_size), until, flags, pass_volume_key);
if (arg_fido2_device || arg_fido2_device_auto)
return attach_luks_or_plain_or_bitlk_by_fido2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key);
if (arg_pkcs11_uri || arg_pkcs11_uri_auto)
#include "strv.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
+#include "vpick.h"
static enum {
ACTION_DISSECT,
_cleanup_free_ void *p = NULL;
size_t l;
- r = unhexmem(optarg, strlen(optarg), &p, &l);
+ r = unhexmem(optarg, &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg);
if (l < sizeof(sd_id128_t))
void *p;
if ((value = startswith(optarg, "base64:"))) {
- r = unbase64mem(value, strlen(value), &p, &l);
+ r = unbase64mem(value, &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
} else {
struct stat st;
int r;
+ assert(path);
+
fd = open(path, O_PATH|O_CLOEXEC);
if (fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", path);
if (r <= 0)
return r;
+ if (arg_image) {
+ r = path_pick_update_warn(
+ &arg_image,
+ &pick_filter_image_raw,
+ PICK_ARCHITECTURE|PICK_TRIES,
+ /* ret_result= */ NULL);
+ if (r < 0)
+ return r;
+ }
+
switch (arg_action) {
case ACTION_UMOUNT:
return action_umount(arg_path);
STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static bool press_any_key(void) {
" --timezone=TIMEZONE Set timezone\n"
" --hostname=NAME Set hostname\n"
" --setup-machine-id Set a random machine ID\n"
- " --machine-ID=ID Set specified machine ID\n"
+ " --machine-id=ID Set specified machine ID\n"
" --root-password=PASSWORD Set root password from plaintext password\n"
" --root-password-file=FILE Set root password from file\n"
" --root-password-hashed=HASH Set root password from hashed password\n"
" --root-shell=SHELL Set root shell\n"
+ " --kernel-command-line=CMDLINE\n"
+ " Set kernel command line\n"
" --prompt-locale Prompt the user for locale settings\n"
" --prompt-keymap Prompt the user for keymap settings\n"
" --prompt-timezone Prompt the user for timezone\n"
log_info("Requesting %s/start/%s", target, mode);
/* Start this unit only if we can replace basic.target with it */
- r = bus_call_method(bus, bus_systemd_mgr, "StartUnitReplace", &error, NULL, "sss", "basic.target", target, mode);
+ r = bus_call_method(bus, bus_systemd_mgr, "StartUnitReplace", &error, NULL, "sss", SPECIAL_BASIC_TARGET, target, mode);
/* Don't print a warning if we aren't called during startup */
if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
return (sd_char*) s + l;
}
-sd_char* endswith(const sd_char *s, const sd_char *postfix) {
+sd_char* endswith(const sd_char *s, const sd_char *suffix) {
size_t sl, pl;
assert(s);
- assert(postfix);
+ assert(suffix);
sl = strlen(s);
- pl = strlen(postfix);
+ pl = strlen(suffix);
if (pl == 0)
return (sd_char*) s + sl;
if (sl < pl)
return NULL;
- if (strcmp(s + sl - pl, postfix) != 0)
+ if (!streq(s + sl - pl, suffix))
return NULL;
return (sd_char*) s + sl - pl;
}
-sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
+sd_char* endswith_no_case(const sd_char *s, const sd_char *suffix) {
size_t sl, pl;
assert(s);
- assert(postfix);
+ assert(suffix);
sl = strlen(s);
- pl = strlen(postfix);
+ pl = strlen(suffix);
if (pl == 0)
return (sd_char*) s + sl;
if (sl < pl)
return NULL;
- if (strcasecmp(s + sl - pl, postfix) != 0)
+ if (!strcaseeq(s + sl - pl, suffix))
return NULL;
return (sd_char*) s + sl - pl;
sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_;
sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_;
-sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_;
-sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_;
+sd_char *endswith(const sd_char *s, const sd_char *suffix) _pure_;
+sd_char *endswith_no_case(const sd_char *s, const sd_char *suffix) _pure_;
static inline bool isempty(const sd_char *a) {
return !a || a[0] == '\0';
if (!e)
return log_oom();
- r = json_dispatch(v, dispatch_table, JSON_LOG, e);
+ r = json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, e);
if (r < 0)
return r;
#include "rlimit-util.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-record.h"
#include "user-record-password-quality.h"
#include "user-record-show.h"
#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-record-password-quality.h"
#include "user-record-sign.h"
#include "user-record-util.h"
return 0;
}
-static void home_count_bad_authentication(Home *h, bool save) {
+static void home_count_bad_authentication(Home *h, int error, bool save) {
int r;
assert(h);
+ if (!IN_SET(error,
+ -ENOKEY, /* Password incorrect */
+ -EBADSLT, /* Password incorrect and no token */
+ -EREMOTEIO)) /* Recovery key incorrect */
+ return;
+
r = user_record_bad_authentication(h->record);
if (r < 0) {
log_warning_errno(r, "Failed to increase bad authentication counter, ignoring: %m");
secret = TAKE_PTR(h->secret); /* Take possession */
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, false);
+ (void) home_count_bad_authentication(h, ret, /* save= */ false);
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Fixation failed: %m");
home_set_state(h, HOME_UNFIXATED);
}
+static bool error_is_bad_password(int ret) {
+ /* Tests for the various cases of bad passwords. We generally don't want to log so loudly about
+ * these, since everyone types in a bad password now and then. Moreover we usually try to start out
+ * with an empty set of passwords, so the first authentication will frequently fail, if not token is
+ * inserted. */
+
+ return IN_SET(ret,
+ -ENOKEY, /* Bad password, or insufficient */
+ -EBADSLT, /* Bad password, and no token */
+ -EREMOTEIO, /* Bad recovery key */
+ -ENOANO, /* PIN for security token needed */
+ -ERFKILL, /* "Protected Authentication Path" for token needed */
+ -EMEDIUMTYPE, /* Presence confirmation on token needed */
+ -ENOCSI, /* User verification on token needed */
+ -ENOSTR, /* Token action timeout */
+ -EOWNERDEAD, /* PIN locked of security token */
+ -ENOLCK, /* Bad PIN of security token */
+ -ETOOMANYREFS, /* Bad PIN and few tries left */
+ -EUCLEAN); /* Bad PIN and one try left */
+}
+
static void home_activate_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(IN_SET(h->state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
if (ret < 0) {
- if (ret == -ENOKEY)
- home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Activation failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Activation failed: %s", bus_error_message(&error, ret));
goto finish;
}
assert(h);
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Change operation failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Change operation failed: %s", bus_error_message(&error, ret));
goto finish;
}
assert(IN_SET(h->state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Unlocking operation failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Unlocking operation failed: %s", bus_error_message(&error, ret));
/* Revert to locked state */
home_set_state(h, HOME_LOCKED);
assert(IN_SET(h->state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Authentication failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Authentication failed: %s", bus_error_message(&error, ret));
goto finish;
}
return sd_bus_message_close_container(reply);
}
+static int lookup_user_name(
+ Manager *m,
+ sd_bus_message *message,
+ const char *user_name,
+ sd_bus_error *error,
+ Home **ret) {
+
+ Home *h;
+ int r;
+
+ assert(m);
+ assert(message);
+ assert(user_name);
+ assert(ret);
+
+ if (isempty(user_name)) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ uid_t uid;
+
+ /* If an empty user name is specified, then identify caller's EUID and find home by that. */
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_euid(creds, &uid);
+ if (r < 0)
+ return r;
+
+ h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
+ if (!h)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "Client's UID " UID_FMT " not managed.", uid);
+
+ } else {
+
+ if (!valid_user_group_name(user_name, 0))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
+
+ h = hashmap_get(m->homes_by_name, user_name);
+ if (!h)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ }
+
+ *ret = h;
+ return 0;
+}
+
static int method_get_home_by_name(
sd_bus_message *message,
void *userdata,
r = sd_bus_message_read(message, "s", &user_name);
if (r < 0)
return r;
- if (!valid_user_group_name(user_name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
- h = hashmap_get(m->homes_by_name, user_name);
- if (!h)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ r = lookup_user_name(m, message, user_name, error, &h);
+ if (r < 0)
+ return r;
r = bus_home_path(h, &path);
if (r < 0)
r = sd_bus_message_read(message, "s", &user_name);
if (r < 0)
return r;
- if (!valid_user_group_name(user_name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
- h = hashmap_get(m->homes_by_name, user_name);
- if (!h)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ r = lookup_user_name(m, message, user_name, error, &h);
+ if (r < 0)
+ return r;
r = bus_home_get_record_json(h, message, &json, &incomplete);
if (r < 0)
Home *h;
int r;
+ assert(m);
+ assert(message);
+ assert(handler);
+
r = sd_bus_message_read(message, "s", &user_name);
if (r < 0)
return r;
- if (!valid_user_group_name(user_name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
-
- h = hashmap_get(m->homes_by_name, user_name);
- if (!h)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ r = lookup_user_name(m, message, user_name, error, &h);
+ if (r < 0)
+ return r;
return handler(message, h, error);
}
(void) home_wait_for_worker(h);
m->bus = sd_bus_flush_close_unref(m->bus);
- m->polkit_registry = bus_verify_polkit_async_registry_free(m->polkit_registry);
+ m->polkit_registry = hashmap_free(m->polkit_registry);
m->device_monitor = sd_device_monitor_unref(m->device_monitor);
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator: %m", xa);
- r = unbase64mem(value, e - value, &salt, &salt_size);
+ r = unbase64mem_full(value, e - value, /* secure = */ false, &salt, &salt_size);
if (r < 0)
return log_error_errno(r, "Failed to decode salt of %s: %m", xa);
- r = unbase64mem(e+1, n - (e - value) - 1, &encrypted, &encrypted_size);
+
+ r = unbase64mem_full(e + 1, n - (e - value) - 1, /* secure = */ false, &encrypted, &encrypted_size);
if (r < 0)
return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
send_interface="org.freedesktop.home1.Manager"
send_member="LockAllHomes"/>
+ <allow send_destination="org.freedesktop.home1"
+ send_interface="org.freedesktop.home1.Manager"
+ send_member="DeactivateAllHomes"/>
+
<allow send_destination="org.freedesktop.home1"
send_interface="org.freedesktop.home1.Manager"
send_member="Rebalance"/>
#include "user-record.h"
#include "user-util.h"
+typedef enum AcquireHomeFlags {
+ ACQUIRE_MUST_AUTHENTICATE = 1 << 0,
+ ACQUIRE_PLEASE_SUSPEND = 1 << 1,
+} AcquireHomeFlags;
+
static int parse_argv(
pam_handle_t *handle,
int argc, const char **argv,
- bool *please_suspend,
+ AcquireHomeFlags *flags,
bool *debug) {
assert(argc >= 0);
k = parse_boolean(v);
if (k < 0)
pam_syslog(handle, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v);
- else if (please_suspend)
- *please_suspend = k;
+ else if (flags)
+ SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k);
} else if (streq(argv[i], "debug")) {
if (debug)
static int parse_env(
pam_handle_t *handle,
- bool *please_suspend) {
+ AcquireHomeFlags *flags) {
const char *v;
int r;
r = parse_boolean(v);
if (r < 0)
pam_syslog(handle, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v);
- else if (please_suspend)
- *please_suspend = r;
+ else if (flags)
+ SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r);
return 0;
}
const sd_bus_error *error,
bool debug) {
+ int r;
+
assert(user_name);
assert(error);
- int r;
-
/* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */
if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL,
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL,
_("Home of user %s is currently absent, please plug in the necessary storage device or backing file system."), user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_PERM_DENIED,
"Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
} else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
"Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
/* This didn't work? Ask for an (additional?) password */
if (strv_isempty(secret->password))
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: "));
else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
/* Hmm, homed asks for recovery key (because no regular password is defined maybe)? Provide it. */
if (strv_isempty(secret->password))
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: "));
else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
if (strv_isempty(secret->password)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
} else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
return PAM_AUTHTOK_ERR;
}
-
r = user_record_set_password(secret, STRV_MAKE(newp), true);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m");
assert(secret);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name);
r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true);
if (r < 0)
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name);
r = user_record_set_fido2_user_presence_permitted(secret, true);
if (r < 0)
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name);
r = user_record_set_fido2_user_verification_permitted(secret, true);
if (r < 0)
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"));
return PAM_SERVICE_ERR;
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
static int acquire_home(
pam_handle_t *handle,
- bool please_authenticate,
- bool please_suspend,
+ AcquireHomeFlags flags,
bool debug,
PamBusData **bus_data) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL;
- bool do_auth = please_authenticate, home_not_active = false, home_locked = false;
+ bool do_auth = FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE), home_not_active = false, home_locked = false;
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
_cleanup_close_ int acquired_fd = -EBADF;
_cleanup_free_ char *fd_field = NULL;
return pam_bus_log_create_error(handle, r);
}
- r = sd_bus_message_append(m, "b", please_suspend);
+ r = sd_bus_message_append(m, "b", FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND));
if (r < 0)
return pam_bus_log_create_error(handle, r);
* failure. */
if (home_not_active)
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name);
if (home_locked)
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name);
- if (please_authenticate || debug)
- pam_syslog(handle, please_authenticate ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
+ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) || debug)
+ pam_syslog(handle, FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR;
}
if (r != PAM_SUCCESS)
return r;
}
-
} else {
int fd;
}
if (++n_attempts >= 5) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL,
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL,
_("Too many unsuccessful login attempts for user %s, refusing."), ur->user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
"Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r));
}
/* Later PAM modules may need the auth token, but only during pam_authenticate. */
- if (please_authenticate && !strv_isempty(secret->password)) {
+ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) {
r = pam_set_item(handle, PAM_AUTHTOK, *secret->password);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@");
_public_ PAM_EXTERN int pam_sm_authenticate(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
- bool debug = false, suspend_please = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
- if (parse_env(handle, &suspend_please) < 0)
+ if (parse_env(handle, &flags) < 0)
return PAM_AUTH_ERR;
if (parse_argv(handle,
argc, argv,
- &suspend_please,
+ &flags,
&debug) < 0)
return PAM_AUTH_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed authenticating");
- return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug, NULL);
+ return acquire_home(handle, ACQUIRE_MUST_AUTHENTICATE|flags, debug, /* bus_data= */ NULL);
}
-_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int sm_flags, int argc, const char **argv) {
return PAM_SUCCESS;
}
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
/* Let's release the D-Bus connection once this function exits, after all the session might live
* quite a long time, and we are not going to process the bus connection in that time, so let's
* better close before the daemon kicks us off because we are not processing anything. */
_cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
- bool debug = false, suspend_please = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
int r;
- if (parse_env(handle, &suspend_please) < 0)
+ if (parse_env(handle, &flags) < 0)
return PAM_SESSION_ERR;
if (parse_argv(handle,
argc, argv,
- &suspend_please,
+ &flags,
&debug) < 0)
return PAM_SESSION_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed session start");
- r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug, &d);
+ r = acquire_home(handle, flags, debug, &d);
if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
return PAM_SUCCESS;
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@");
- r = pam_putenv(handle, suspend_please ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
+ r = pam_putenv(handle, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
_public_ PAM_EXTERN int pam_sm_close_session(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_public_ PAM_EXTERN int pam_sm_acct_mgmt(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc,
const char **argv) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
- bool debug = false, please_suspend = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
usec_t t;
int r;
- if (parse_env(handle, &please_suspend) < 0)
+ if (parse_env(handle, &flags) < 0)
return PAM_AUTH_ERR;
if (parse_argv(handle,
argc, argv,
- &please_suspend,
+ &flags,
&debug) < 0)
return PAM_AUTH_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed account management");
- r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug, NULL);
+ r = acquire_home(handle, flags, debug, NULL);
if (r != PAM_SUCCESS)
return r;
break;
case -ENOLCK:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access."));
return PAM_ACCT_EXPIRED;
case -EL2HLT:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access."));
return PAM_ACCT_EXPIRED;
case -EL3HLT:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access."));
return PAM_ACCT_EXPIRED;
default:
if (r < 0) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
return PAM_ACCT_EXPIRED;
}
usec_t n = now(CLOCK_REALTIME);
if (t > n) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."),
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."),
FORMAT_TIMESPAN(t - n, USEC_PER_SEC));
return PAM_MAXTRIES;
switch (r) {
case -EKEYREVOKED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password change required."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password change required."));
return PAM_NEW_AUTHTOK_REQD;
case -EOWNERDEAD:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required."));
return PAM_NEW_AUTHTOK_REQD;
/* Strictly speaking this is only about password expiration, and we might want to allow
* authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */
case -EKEYREJECTED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login."));
return PAM_AUTHTOK_EXPIRED;
case -EKEYEXPIRED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change."));
break;
case -ESTALE:
default:
if (r < 0) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
return PAM_AUTHTOK_EXPIRED;
}
_public_ PAM_EXTERN int pam_sm_chauthtok(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc,
const char **argv) {
return r;
/* Start with cached credentials */
- r = pam_get_item(handle, PAM_OLDAUTHTOK, (const void**) &old_password);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get old password: @PAMERR@");
-
- r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &new_password);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached password: @PAMERR@");
+ r = pam_get_item_many(
+ handle,
+ PAM_OLDAUTHTOK, &old_password,
+ PAM_AUTHTOK, &new_password);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached passwords: @PAMERR@");
if (isempty(new_password)) {
/* No, it's not cached, then let's ask for the password and its verification, and cache
}
/* Now everything is cached and checked, let's exit from the preliminary check */
- if (FLAGS_SET(flags, PAM_PRELIM_CHECK))
+ if (FLAGS_SET(sm_flags, PAM_PRELIM_CHECK))
return PAM_SUCCESS;
old_secret = user_record_new();
#include "main-func.h"
#include "parse-argument.h"
#include "pretty-print.h"
+#include "socket-util.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
#include "verbs.h"
usec_t firmware_date;
sd_id128_t machine_id;
sd_id128_t boot_id;
+ uint32_t vsock_cid;
} StatusInfo;
static const char* chassis_string_to_glyph(const char *chassis) {
return table_log_add_error(r);
}
+ if (i->vsock_cid != VMADDR_CID_ANY) {
+ r = table_add_many(table,
+ TABLE_FIELD, "AF_VSOCK CID",
+ TABLE_UINT32, i->vsock_cid);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
if (!isempty(i->virtualization)) {
r = table_add_many(table,
TABLE_FIELD, "Virtualization",
}
static int show_all_names(sd_bus *bus) {
- StatusInfo info = {};
+ StatusInfo info = {
+ .vsock_cid = VMADDR_CID_ANY,
+ };
static const struct bus_properties_map hostname_map[] = {
{ "Hostname", "s", NULL, offsetof(StatusInfo, hostname) },
{ "FirmwareDate", "t", NULL, offsetof(StatusInfo, firmware_date) },
{ "MachineID", "ay", bus_map_id128, offsetof(StatusInfo, machine_id) },
{ "BootID", "ay", bus_map_id128, offsetof(StatusInfo, boot_id) },
+ { "VSockCID", "u", NULL, offsetof(StatusInfo, vsock_cid) },
{}
}, manager_map[] = {
{ "Virtualization", "s", NULL, offsetof(StatusInfo, virtualization) },
#include <sys/types.h>
#include <unistd.h>
+#include "sd-device.h"
+
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-get-properties.h"
#include "bus-log-control-api.h"
#include "bus-polkit.h"
#include "constants.h"
+#include "daemon-util.h"
#include "env-file-label.h"
#include "env-file.h"
#include "env-util.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
-#include "sd-device.h"
#include "selinux-util.h"
#include "service-util.h"
#include "signal-util.h"
+#include "socket-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
#include "user-util.h"
+#include "varlink-io.systemd.Hostname.h"
#include "virt.h"
#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
struct stat etc_os_release_stat;
struct stat etc_machine_info_stat;
+ sd_event *event;
+ sd_bus *bus;
+ VarlinkServer *varlink_server;
Hashmap *polkit_registry;
} Context;
assert(c);
context_reset(c, UINT64_MAX);
- bus_verify_polkit_async_registry_free(c->polkit_registry);
+ hashmap_free(c->polkit_registry);
+ sd_event_unref(c->event);
+ sd_bus_flush_close_unref(c->bus);
+ varlink_server_unref(c->varlink_server);
}
static void context_read_etc_hostname(Context *c) {
return bus_property_get_id128(bus, path, interface, property, reply, &id, error);
}
+static int property_get_vsock_cid(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ unsigned local_cid = VMADDR_CID_ANY;
+
+ (void) vsock_get_local_cid(&local_cid);
+
+ return sd_bus_message_append(reply, "u", (uint32_t) local_cid);
+}
+
static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) {
Context *c = ASSERT_PTR(userdata);
const char *name;
return sd_bus_send(NULL, reply, NULL);
}
-static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) {
- _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *text = NULL,
+static int build_describe_response(Context *c, bool privileged, JsonVariant **ret) {
+ _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL,
*chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL,
*firmware_vendor = NULL;
usec_t firmware_date = USEC_INFINITY, eol = USEC_INFINITY;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
sd_id128_t machine_id, boot_id, product_uuid = SD_ID128_NULL;
- Context *c = ASSERT_PTR(userdata);
- bool privileged;
+ unsigned local_cid = VMADDR_CID_ANY;
struct utsname u;
int r;
- assert(m);
-
- r = bus_verify_polkit_async(
- m,
- "org.freedesktop.hostname1.get-description",
- /* details= */ NULL,
- &c->polkit_registry,
- error);
- if (r == 0)
- return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
-
- /* We ignore all authentication errors here, since most data is unprivileged, the one exception being
- * the product ID which we'll check explicitly. */
- privileged = r > 0;
+ assert(c);
+ assert(ret);
context_read_etc_hostname(c);
context_read_machine_info(c);
if (r < 0)
return log_error_errno(r, "Failed to get boot ID: %m");
+ (void) vsock_get_local_cid(&local_cid);
+
r = json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("Hostname", JSON_BUILD_STRING(hn)),
JSON_BUILD_PAIR("StaticHostname", JSON_BUILD_STRING(c->data[PROP_STATIC_HOSTNAME])),
JSON_BUILD_PAIR_ID128("MachineID", machine_id),
JSON_BUILD_PAIR_ID128("BootID", boot_id),
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_ID128(product_uuid)),
- JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL)));
-
+ JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL),
+ JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid)),
+ JSON_BUILD_PAIR_CONDITION(local_cid == VMADDR_CID_ANY, "VSockCID", JSON_BUILD_NULL)));
if (r < 0)
return log_error_errno(r, "Failed to build JSON data: %m");
+ *ret = TAKE_PTR(v);
+ return 0;
+}
+
+static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ Context *c = ASSERT_PTR(userdata);
+ _cleanup_free_ char *text = NULL;
+ bool privileged;
+ int r;
+
+ assert(m);
+
+ r = bus_verify_polkit_async(
+ m,
+ "org.freedesktop.hostname1.get-description",
+ /* details= */ NULL,
+ &c->polkit_registry,
+ error);
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ /* We ignore all authentication errors here, since most data is unprivileged, the one exception being
+ * the product ID which we'll check explicitly. */
+ privileged = r > 0;
+
+ r = build_describe_response(c, privileged, &v);
+ if (r < 0)
+ return r;
+
r = json_variant_format(v, 0, &text);
if (r < 0)
return log_error_errno(r, "Failed to format JSON data: %m");
SD_BUS_PROPERTY("FirmwareDate", "t", property_get_firmware_date, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MachineID", "ay", property_get_machine_id, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BootID", "ay", property_get_boot_id, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("VSockCID", "u", property_get_vsock_cid, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD_WITH_ARGS("SetHostname",
SD_BUS_ARGS("s", hostname, "b", interactive),
.vtables = BUS_VTABLES(hostname_vtable),
};
-static int connect_bus(Context *c, sd_event *event, sd_bus **ret) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+static int connect_bus(Context *c) {
int r;
assert(c);
- assert(event);
- assert(ret);
+ assert(c->event);
+ assert(!c->bus);
- r = sd_bus_default_system(&bus);
+ r = sd_bus_default_system(&c->bus);
if (r < 0)
return log_error_errno(r, "Failed to get system bus connection: %m");
- r = bus_add_implementation(bus, &manager_object, c);
+ r = bus_add_implementation(c->bus, &manager_object, c);
if (r < 0)
return r;
- r = bus_log_control_api_register(bus);
+ r = bus_log_control_api_register(c->bus);
if (r < 0)
return r;
- r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL);
+ r = sd_bus_request_name_async(c->bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
- r = sd_bus_attach_event(bus, event, 0);
+ r = sd_bus_attach_event(c->bus, c->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
- *ret = TAKE_PTR(bus);
+ return 0;
+}
+
+static int vl_method_describe(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ static const JsonDispatch dispatch_table[] = {
+ VARLINK_DISPATCH_POLKIT_FIELD,
+ {}
+ };
+
+ Context *c = ASSERT_PTR(userdata);
+ bool privileged;
+ int r;
+
+ assert(link);
+ assert(parameters);
+
+ r = varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL);
+ if (r != 0)
+ return r;
+
+ r = varlink_verify_polkit_async(
+ link,
+ c->bus,
+ "org.freedesktop.hostname1.get-hardware-serial",
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ &c->polkit_registry);
+ if (r == 0)
+ return 0; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ /* We ignore all authentication errors here, since most data is unprivileged, the one exception being
+ * the product ID which we'll check explicitly. */
+ privileged = r > 0;
+
+ if (json_variant_elements(parameters) > 0)
+ return varlink_error_invalid_parameter(link, parameters);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ r = build_describe_response(c, privileged, &v);
+ if (r < 0)
+ return r;
+
+ return varlink_reply(link, v);
+}
+
+static int connect_varlink(Context *c) {
+ int r;
+
+ assert(c);
+ assert(c->event);
+ assert(!c->varlink_server);
+
+ r = varlink_server_new(&c->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+ varlink_server_set_userdata(c->varlink_server, c);
+
+ r = varlink_server_add_interface(c->varlink_server, &vl_interface_io_systemd_Hostname);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Hostname interface to varlink server: %m");
+
+ r = varlink_server_bind_method_many(
+ c->varlink_server,
+ "io.systemd.Hostname.Describe", vl_method_describe);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind Varlink method calls: %m");
+
+ r = varlink_server_attach_event(c->varlink_server, c->event, SD_EVENT_PRIORITY_NORMAL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach Varlink server to event loop: %m");
+
+ r = varlink_server_listen_auto(c->varlink_server);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m");
+ if (r == 0) {
+ r = varlink_server_listen_address(c->varlink_server, "/run/systemd/io.systemd.Hostname", 0666);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind to Varlink socket: %m");
+ }
+
return 0;
}
_cleanup_(context_destroy) Context context = {
.hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */
};
- _cleanup_(sd_event_unrefp) sd_event *event = NULL;
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
log_setup();
if (r < 0)
return r;
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
-
- r = sd_event_default(&event);
+ r = sd_event_default(&context.event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
- (void) sd_event_set_watchdog(event, true);
+ (void) sd_event_set_watchdog(context.event, true);
- r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
+ r = sd_event_set_signal_exit(context.event, true);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGINT handler: %m");
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
- r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+ r = connect_bus(&context);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGTERM handler: %m");
+ return r;
- r = connect_bus(&context, event, &bus);
+ r = connect_varlink(&context);
if (r < 0)
return r;
- r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
+ r = bus_event_loop_with_idle(
+ context.event,
+ context.bus,
+ "org.freedesktop.hostname1",
+ DEFAULT_EXIT_USEC,
+ /* check_idle= */ NULL,
+ /* userdata= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
#include "bus-polkit.h"
#include "common-signal.h"
#include "constants.h"
+#include "daemon-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "float.h"
hashmap_free(m->transfers);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
m->bus = sd_bus_flush_close_unref(m->bus);
sd_event_unref(m->event);
return hashmap_isempty(m->transfers);
}
-static int manager_run(Manager *m) {
- assert(m);
-
- return bus_event_loop_with_idle(
- m->event,
- m->bus,
- "org.freedesktop.import1",
- DEFAULT_EXIT_USEC,
- manager_check_idle,
- m);
-}
-
static void manager_parse_env(Manager *m) {
int r;
if (r < 0)
return r;
- r = manager_run(m);
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
+ r = bus_event_loop_with_idle(
+ m->event,
+ m->bus,
+ "org.freedesktop.import1",
+ DEFAULT_EXIT_USEC,
+ manager_check_idle,
+ m);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
/* If this is not a valid verification mode, maybe it's a literally specified
* SHA256 hash? We can handle that too... */
- r = unhexmem(optarg, (size_t) -1, &h, &n);
+ r = unhexmem(optarg, &h, &n);
if (r < 0 || n == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid verification setting: %s", optarg);
if (r < 0)
return log_error_errno(r, "socket_address_print(): %m");
- r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b);
+ r = socknameinfo_pretty(&addr->sockaddr.sa, addr->size, &b);
if (r < 0)
return log_error_errno(r, "Resolving hostname failed: %m");
#include "alloc-util.h"
#include "build.h"
+#include "env-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "main-func.h"
if (argc <= optind)
(void) execl("/bin/cat", "/bin/cat", NULL);
else {
- _cleanup_free_ char *s = NULL;
struct stat st;
if (fstat(STDERR_FILENO, &st) < 0)
"Failed to fstat(%s): %m",
FORMAT_PROC_FD_PATH(STDERR_FILENO));
- if (asprintf(&s, DEV_FMT ":" INO_FMT, (dev_t)st.st_dev, st.st_ino) < 0)
- return log_oom();
-
- if (setenv("JOURNAL_STREAM", s, /* overwrite = */ true) < 0)
- return log_error_errno(errno, "Failed to set environment variable JOURNAL_STREAM: %m");
+ r = setenvf("JOURNAL_STREAM", /* overwrite = */ true, DEV_FMT ":" INO_FMT, (dev_t) st.st_dev, st.st_ino);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set environment variable JOURNAL_STREAM: %m");
(void) execvp(argv[optind], argv + optind);
}
#include "chattr-util.h"
#include "constants.h"
#include "devnum-util.h"
+#include "dirent-util.h"
#include "dissect-image.h"
#include "fd-util.h"
#include "fileio.h"
static usec_t arg_since = 0, arg_until = 0;
static bool arg_since_set = false, arg_until_set = false;
static char **arg_syslog_identifier = NULL;
+static char **arg_exclude_identifier = NULL;
static char **arg_system_units = NULL;
static char **arg_user_units = NULL;
static const char *arg_field = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep);
STATIC_DESTRUCTOR_REGISTER(arg_verify_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_syslog_identifier, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_exclude_identifier, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_system_units, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_user_units, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
ACTION_ROTATE_AND_VACUUM,
ACTION_LIST_FIELDS,
ACTION_LIST_FIELD_NAMES,
+ ACTION_LIST_NAMESPACES,
} arg_action = ACTION_SHOW;
static int add_matches_for_device(sd_journal *j, const char *devpath) {
" -u --unit=UNIT Show logs from the specified unit\n"
" --user-unit=UNIT Show logs from the specified user unit\n"
" -t --identifier=STRING Show entries with the specified syslog identifier\n"
+ " -T --exclude-identifier=STRING\n"
+ " Hide entries with the specified syslog identifier\n"
" -p --priority=RANGE Show entries with the specified priority\n"
" --facility=FACILITY... Show entries with the specified facilities\n"
" -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n"
ARG_NO_HOSTNAME,
ARG_OUTPUT_FIELDS,
ARG_NAMESPACE,
+ ARG_LIST_NAMESPACES,
};
static const struct option options[] = {
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "header", no_argument, NULL, ARG_HEADER },
{ "identifier", required_argument, NULL, 't' },
+ { "exclude-identifier", required_argument, NULL, 'T' },
{ "priority", required_argument, NULL, 'p' },
{ "facility", required_argument, NULL, ARG_FACILITY },
{ "grep", required_argument, NULL, 'g' },
{ "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
{ "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS },
{ "namespace", required_argument, NULL, ARG_NAMESPACE },
+ { "list-namespaces", no_argument, NULL, ARG_LIST_NAMESPACES },
{}
};
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:u:NF:xrM:i:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:NF:xrM:i:", options, NULL)) >= 0)
switch (c) {
break;
+ case ARG_LIST_NAMESPACES:
+ arg_action = ACTION_LIST_NAMESPACES;
+ break;
+
case 'D':
arg_directory = optarg;
break;
return log_oom();
break;
+ case 'T':
+ r = strv_extend(&arg_exclude_identifier, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
case 'u':
r = strv_extend(&arg_system_units, optarg);
if (r < 0)
return 0;
}
+static int list_namespaces(const char *root) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ sd_id128_t machine;
+ char machine_id[SD_ID128_STRING_MAX];
+ int r;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get machine ID: %m");
+
+ sd_id128_to_string(machine, machine_id);
+
+ table = table_new("namespace");
+ if (!table)
+ return log_oom();
+
+ (void) table_set_sort(table, (size_t) 0);
+
+ FOREACH_STRING(dir, "/var/log/journal", "/run/log/journal") {
+ _cleanup_free_ char *path = NULL;
+ _cleanup_closedir_ DIR *dirp = NULL;
+
+ path = path_join(root, dir);
+ if (!path)
+ return log_oom();
+
+ dirp = opendir(path);
+ if (!dirp) {
+ log_debug_errno(errno, "Failed to open directory %s, ignoring: %m", path);
+ continue;
+ }
+
+ FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", path)) {
+ char *dot;
+
+ if (!startswith(de->d_name, machine_id))
+ continue;
+
+ dot = strchr(de->d_name, '.');
+ if (!dot)
+ continue;
+
+ if (!log_namespace_name_valid(dot + 1))
+ continue;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, dot + 1);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+ }
+
+ r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
+ if (r < 0)
+ return table_log_print_error(r);
+
+ return 0;
+}
+
static int list_boots(sd_journal *j) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ BootId *boots = NULL;
return 0;
}
+static int add_exclude_identifier(sd_journal *j) {
+ _cleanup_set_free_ Set *excludes = NULL;
+ int r;
+
+ assert(j);
+
+ r = set_put_strdupv(&excludes, arg_exclude_identifier);
+ if (r < 0)
+ return r;
+
+ return set_free_and_replace(j->exclude_syslog_identifiers, excludes);
+}
+
#if HAVE_GCRYPT
static int format_journal_url(
const void *seed,
case ACTION_ROTATE:
return rotate();
+ case ACTION_LIST_NAMESPACES:
+ return list_namespaces(arg_root);
+
case ACTION_SHOW:
case ACTION_PRINT_HEADER:
case ACTION_VERIFY:
if (r < 0)
return log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
+ r = add_exclude_identifier(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add exclude filter for syslog identifiers: %m");
+
r = add_priorities(j);
if (r < 0)
return r;
#include "string-table.h"
#include "string-util.h"
#include "syslog-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "varlink-io.systemd.Journal.h"
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
typedef enum Action {
assert(argv);
if (arg_root)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root=.");
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root= or --image=.");
if (bypass())
return 0;
assert(argv);
+ if (arg_root)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add-all' does not support --root= or --image=.");
+
if (bypass())
return 0;
assert(argv);
if (arg_root)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root=.");
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root= or --image=.");
if (argc > 2)
log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
_cleanup_close_ int fd = -EBADF;
int r;
+ if (arg_root)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'list' does not support --root= or --image=.");
+
fd = open("/usr/lib/modules", O_DIRECTORY|O_RDONLY|O_CLOEXEC);
if (fd < 0)
return log_error_errno(fd, "Failed to open /usr/lib/modules/: %m");
r = asprintf(&t, "IAID:0x%x/DUID", iaid);
}
break;
+ default:
+ assert_not_reached();
}
if (r < 0)
return -ENOMEM;
_cleanup_free_ void *data = NULL;
size_t data_size;
- r = unhexmem(client_id_hex, SIZE_MAX, &data, &data_size);
+ r = unhexmem(client_id_hex, &data, &data_size);
if (r < 0)
log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex);
}
if (vendor_specific_hex) {
- r = unhexmem(vendor_specific_hex, SIZE_MAX, &lease->vendor_specific, &lease->vendor_specific_len);
+ r = unhexmem(vendor_specific_hex, &lease->vendor_specific, &lease->vendor_specific_len);
if (r < 0)
log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex);
}
if (!options[i])
continue;
- r = unhexmem(options[i], SIZE_MAX, &data, &len);
+ r = unhexmem(options[i], &data, &len);
if (r < 0) {
log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]);
continue;
if (l % 2 != 0)
return 0;
- r = unhexmem(p, l, (void **) &token, &len);
+ r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len);
if (r < 0)
return 0;
if (l % 2 != 0)
return 0;
- r = unhexmem(p, l, (void**) &token, &len);
+ r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len);
if (r < 0)
return 0;
return -ENOMEM;
}
- a = strjoin("unixexec:path=ssh,argv1=-xT", p ? ",argv2=-p,argv3=" : "", strempty(p),
- ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e,
- ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c);
+ const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh";
+ _cleanup_free_ char *ssh_escaped = bus_address_escape(ssh);
+ if (!ssh_escaped)
+ return -ENOMEM;
+
+ a = strjoin("unixexec:path=", ssh_escaped, ",argv1=-xT",
+ p ? ",argv2=-p,argv3=" : "", strempty(p),
+ ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e,
+ ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c);
if (!a)
return -ENOMEM;
union sockaddr_union snl_trusted_sender;
bool bound;
- UidRange *mapped_userns_uid_range;
+ UIDRange *mapped_userns_uid_range;
Hashmap *subsystem_filter;
Set *tag_filter;
#include "alloc-util.h"
#include "log.h"
#include "macro.h"
+#include "strv.h"
#define device_unref_and_replace(a, b) \
unref_and_replace_full(a, b, sd_device_ref, sd_device_unref)
bool device_in_subsystem(sd_device *device, const char *subsystem);
bool device_is_devtype(sd_device *device, const char *devtype);
+
+static inline bool device_property_can_set(const char *property) {
+ return property &&
+ !STR_IN_SET(property,
+ "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER",
+ "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS");
+}
env = secure_getenv("MEMORY_PRESSURE_WRITE");
if (env) {
- r = unbase64mem(env, SIZE_MAX, &write_buffer, &write_buffer_size);
+ r = unbase64mem(env, &write_buffer, &write_buffer_size);
if (r < 0)
return r;
}
#include "stdio-util.h"
#include "string-util.h"
#include "sync-util.h"
+#include "virt.h"
int id128_from_string_nonzero(const char *s, sd_id128_t *ret) {
sd_id128_t t;
/* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is
* particularly relevant in VM environments, where VM managers typically place a VM uuid there. */
+ r = detect_container();
+ if (r < 0)
+ return r;
+ if (r > 0) /* Refuse returning this in containers, as this is not a property of our system then, but
+ * of the host */
+ return -ENOENT;
+
r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
if (r == -ENOENT)
r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
Object **ret_object, /* The found object. */
uint64_t *ret_offset) { /* The offset of the found object. */
- uint64_t a, t = 0, k;
+ uint64_t a, t = 0, k = 0; /* Explicit initialization of k to appease gcc */
ChainCacheItem *ci;
Object *o = NULL;
int r;
uint64_t current_field;
Match *level0, *level1, *level2;
+ Set *exclude_syslog_identifiers;
uint64_t origin_id;
#include "string-util.h"
#include "strv.h"
#include "syslog-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC)
hashmap_free_free(j->errors);
+ set_free(j->exclude_syslog_identifiers);
+
free(j->path);
free(j->prefix);
free(j->namespace);
c->x11_cache = sd_bus_message_unref(c->x11_cache);
c->vc_cache = sd_bus_message_unref(c->vc_cache);
- c->polkit_registry = bus_verify_polkit_async_registry_free(c->polkit_registry);
+ c->polkit_registry = hashmap_free(c->polkit_registry);
};
X11Context *context_get_x11_context(Context *c) {
#include "bus-polkit.h"
#include "bus-unit-util.h"
#include "constants.h"
+#include "daemon-util.h"
#include "kbd-util.h"
#include "localed-util.h"
#include "macro.h"
if (r < 0)
return r;
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
-
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
(void) sd_event_set_watchdog(event, true);
- r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to install SIGINT handler: %m");
-
- r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+ r = sd_event_set_signal_exit(event, true);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGTERM handler: %m");
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
r = connect_bus(&context, event, &bus);
if (r < 0)
return r;
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
return &handle_action_data_table[action];
}
+static bool handle_action_sleep_supported(HandleAction action) {
+ assert(HANDLE_ACTION_IS_SLEEP(action) && action != HANDLE_SLEEP);
+ return sleep_supported(ASSERT_PTR(handle_action_lookup(action))->sleep_operation) > 0;
+}
+
/* The order in which we try each sleep operation. We should typically prefer operations without a delay,
* i.e. s2h and suspend, and use hibernation at last since it requires minimum hardware support.
* hybrid-sleep is disabled by default, and thus should be ordered before suspend if manually chosen by user,
if (!FLAGS_SET(mask, a))
continue;
- if (sleep_supported(ASSERT_PTR(handle_action_lookup(*i))->sleep_operation) > 0)
+ if (handle_action_sleep_supported(*i))
return *i;
}
return handle_action_sleep_execute(m, a, ignore_inhibited, is_edge);
}
- bool supported;
-
- if (handle == HANDLE_SUSPEND)
- supported = sleep_supported(SLEEP_SUSPEND) > 0;
- else if (handle == HANDLE_HIBERNATE)
- supported = sleep_supported(SLEEP_HIBERNATE) > 0;
- else if (handle == HANDLE_HYBRID_SLEEP)
- supported = sleep_supported(SLEEP_HYBRID_SLEEP) > 0;
- else if (handle == HANDLE_SUSPEND_THEN_HIBERNATE)
- supported = sleep_supported(SLEEP_SUSPEND_THEN_HIBERNATE) > 0;
- else
- assert_not_reached();
+ bool supported = handle_action_sleep_supported(handle);
if (!supported && handle != HANDLE_SUSPEND) {
supported = sleep_supported(SLEEP_SUSPEND) > 0;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
_cleanup_close_ int fifo_fd = -EBADF;
_cleanup_free_ char *p = NULL;
- int r;
assert(s);
if (fifo_fd < 0)
return fifo_fd;
- r = session_watch_pidfd(s);
- if (r < 0)
- return r;
-
/* Update the session state file before we notify the client about the result. */
session_save(s);
return 0;
}
-int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **ret) {
SessionDevice *sd;
int r;
assert(s);
- assert(out);
if (!s->seat)
return -EPERM;
- sd = new0(SessionDevice, 1);
+ sd = new(SessionDevice, 1);
if (!sd)
return -ENOMEM;
- sd->session = s;
- sd->dev = dev;
- sd->fd = -EBADF;
- sd->type = DEVICE_TYPE_UNKNOWN;
+ *sd = (SessionDevice) {
+ .session = s,
+ .dev = dev,
+ .fd = -EBADF,
+ .type = DEVICE_TYPE_UNKNOWN,
+ };
r = session_device_verify(sd);
if (r < 0)
LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
- *out = sd;
+ if (ret)
+ *ret = sd;
+
return 0;
error:
LIST_FIELDS(struct SessionDevice, sd_by_device);
};
-int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out);
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **ret);
SessionDevice *session_device_free(SessionDevice *sd);
DEFINE_TRIVIAL_CLEANUP_FUNC(SessionDevice*, session_device_free);
#include "audit-util.h"
#include "bus-error.h"
#include "bus-util.h"
+#include "daemon-util.h"
#include "devnum-util.h"
#include "env-file.h"
#include "escape.h"
#include "strv.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#define RELEASE_USEC (20*USEC_PER_SEC)
return 0;
}
-static void session_reset_leader(Session *s) {
+static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(s->leader.fd == fd);
+ session_stop(s, /* force= */ false);
+
+ return 1;
+}
+
+static int session_watch_pidfd(Session *s) {
+ int r;
+
+ assert(s);
+ assert(s->manager);
+ assert(pidref_is_set(&s->leader));
+
+ if (s->leader.fd < 0)
+ return 0;
+
+ r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd");
+
+ return 0;
+}
+
+static void session_reset_leader(Session *s, bool keep_fdstore) {
assert(s);
+ if (!keep_fdstore) {
+ /* Clear fdstore if we're asked to, no matter if s->leader is set or not, so that when
+ * initially deserializing leader fd we clear the old fd too. */
+ (void) notify_remove_fd_warnf("session-%s-leader-fd", s->id);
+ s->leader_fd_saved = false;
+ }
+
if (!pidref_is_set(&s->leader))
return;
- assert_se(hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s));
+ s->leader_pidfd_event_source = sd_event_source_disable_unref(s->leader_pidfd_event_source);
+
+ (void) hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s);
return pidref_done(&s->leader);
}
free(s->scope_job);
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ true);
sd_bus_message_unref(s->create_message);
if (pidref_equal(&s->leader, &pidref))
return 0;
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ false);
s->leader = TAKE_PIDREF(pidref);
+ r = session_watch_pidfd(s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch leader pidfd for session '%s': %m", s->id);
+
r = hashmap_ensure_put(&s->manager->sessions_by_leader, &pidref_hash_ops, &s->leader, s);
if (r < 0)
return r;
assert(r > 0);
+ if (s->leader.fd >= 0) {
+ r = notify_push_fdf(s->leader.fd, "session-%s-leader-fd", s->id);
+ if (r < 0)
+ log_warning_errno(r, "Failed to push leader pidfd for session '%s', ignoring: %m", s->id);
+ else
+ s->leader_fd_saved = true;
+ }
+
(void) audit_session_from_pid(s->leader.pid, &s->audit_id);
return 1;
"# This is private data. Do not parse.\n"
"UID="UID_FMT"\n"
"USER=%s\n"
- "ACTIVE=%i\n"
- "IS_DISPLAY=%i\n"
+ "ACTIVE=%s\n"
+ "IS_DISPLAY=%s\n"
"STATE=%s\n"
- "REMOTE=%i\n",
+ "REMOTE=%s\n"
+ "LEADER_FD_SAVED=%s\n",
s->user->user_record->uid,
s->user->user_record->user_name,
- session_is_active(s),
- s->user->display == s,
+ one_zero(session_is_active(s)),
+ one_zero(s->user->display == s),
session_state_to_string(session_get_state(s)),
- s->remote);
+ one_zero(s->remote),
+ one_zero(s->leader_fd_saved));
if (s->type >= 0)
fprintf(f, "TYPE=%s\n", session_type_to_string(s->type));
for (const char *p = devices;;) {
_cleanup_free_ char *word = NULL;
- SessionDevice *sd;
dev_t dev;
int k;
k = extract_first_word(&p, &word, NULL, 0);
- if (k == 0)
- break;
- if (k < 0) {
- r = k;
+ if (k <= 0) {
+ RET_GATHER(r, k);
break;
}
k = parse_devnum(word, &dev);
if (k < 0) {
- r = k;
+ RET_GATHER(r, k);
continue;
}
/* The file descriptors for loaded devices will be reattached later. */
- k = session_device_new(s, dev, false, &sd);
- if (k < 0)
- r = k;
+ RET_GATHER(r, session_device_new(s, dev, /* open_device = */ false, /* ret = */ NULL));
}
if (r < 0)
- log_error_errno(r, "Loading session devices for session %s failed: %m", s->id);
-
+ log_error_errno(r, "Failed to load some session devices for session '%s': %m", s->id);
return r;
}
*vtnr = NULL,
*state = NULL,
*position = NULL,
- *leader = NULL,
+ *leader_pid = NULL,
+ *leader_fd_saved = NULL,
*type = NULL,
*original_type = NULL,
*class = NULL,
assert(s);
r = parse_env_file(NULL, s->state_file,
- "REMOTE", &remote,
- "SCOPE", &s->scope,
- "SCOPE_JOB", &s->scope_job,
- "FIFO", &s->fifo_path,
- "SEAT", &seat,
- "TTY", &s->tty,
- "TTY_VALIDITY", &tty_validity,
- "DISPLAY", &s->display,
- "REMOTE_HOST", &s->remote_host,
- "REMOTE_USER", &s->remote_user,
- "SERVICE", &s->service,
- "DESKTOP", &s->desktop,
- "VTNR", &vtnr,
- "STATE", &state,
- "POSITION", &position,
- "LEADER", &leader,
- "TYPE", &type,
- "ORIGINAL_TYPE", &original_type,
- "CLASS", &class,
- "UID", &uid,
- "REALTIME", &realtime,
- "MONOTONIC", &monotonic,
- "CONTROLLER", &controller,
- "ACTIVE", &active,
- "DEVICES", &devices,
- "IS_DISPLAY", &is_display);
+ "REMOTE", &remote,
+ "SCOPE", &s->scope,
+ "SCOPE_JOB", &s->scope_job,
+ "FIFO", &s->fifo_path,
+ "SEAT", &seat,
+ "TTY", &s->tty,
+ "TTY_VALIDITY", &tty_validity,
+ "DISPLAY", &s->display,
+ "REMOTE_HOST", &s->remote_host,
+ "REMOTE_USER", &s->remote_user,
+ "SERVICE", &s->service,
+ "DESKTOP", &s->desktop,
+ "VTNR", &vtnr,
+ "STATE", &state,
+ "POSITION", &position,
+ "LEADER", &leader_pid,
+ "LEADER_FD_SAVED", &leader_fd_saved,
+ "TYPE", &type,
+ "ORIGINAL_TYPE", &original_type,
+ "CLASS", &class,
+ "UID", &uid,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "CONTROLLER", &controller,
+ "ACTIVE", &active,
+ "DEVICES", &devices,
+ "IS_DISPLAY", &is_display);
if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", s->state_file);
s->tty_validity = v;
}
- if (leader) {
- _cleanup_(pidref_done) PidRef p = PIDREF_NULL;
-
- r = pidref_set_pidstr(&p, leader);
- if (r < 0)
- log_debug_errno(r, "Failed to parse leader PID of session: %s", leader);
- else {
- r = session_set_leader_consume(s, TAKE_PIDREF(p));
- if (r < 0)
- log_warning_errno(r, "Failed to set session leader PID, ignoring: %m");
- }
- }
-
if (type) {
SessionType t;
session_restore_vt(s);
}
+ if (leader_pid) {
+ assert(!pidref_is_set(&s->leader));
+
+ r = parse_pid(leader_pid, &s->deserialized_pid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse LEADER=%s: %m", leader_pid);
+
+ if (leader_fd_saved) {
+ r = parse_boolean(leader_fd_saved);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse LEADER_FD_SAVED=%s: %m", leader_fd_saved);
+ s->leader_fd_saved = r > 0;
+
+ if (s->leader_fd_saved)
+ /* The leader fd will be acquired from fdstore later */
+ return 0;
+ }
+
+ _cleanup_(pidref_done) PidRef p = PIDREF_NULL;
+
+ r = pidref_set_pid(&p, s->deserialized_pid);
+ if (r >= 0)
+ r = session_set_leader_consume(s, TAKE_PIDREF(p));
+ if (r < 0)
+ log_warning_errno(r, "Failed to set leader PID for session '%s': %m", s->id);
+ }
+
return r;
}
* of STRV_IGNORE with strv_new() to skip these order constraints when needed. */
after = strv_new("systemd-logind.service",
s->user->runtime_dir_service,
- !uid_is_system(s->user->user_record->uid) ? "systemd-user-sessions.service" : STRV_IGNORE,
+ SESSION_CLASS_IS_EARLY(s->class) ? STRV_IGNORE : "systemd-user-sessions.service",
s->user->service);
if (!after)
return log_oom();
assert(s);
- if (s->manager->stop_idle_session_usec == USEC_INFINITY)
+ if (s->manager->stop_idle_session_usec == USEC_INFINITY || IN_SET(s->class, SESSION_GREETER, SESSION_LOCK_SCREEN))
return 0;
r = sd_event_add_time_relative(
user_elect_display(s->user);
/* Save data */
- session_save(s);
- user_save(s->user);
+ (void) session_save(s);
+ (void) user_save(s->user);
if (s->seat)
- seat_save(s->seat);
+ (void) seat_save(s->seat);
/* Send signals */
session_send_signal(s, true);
user_send_changed(s->user, "Display", NULL);
if (s->seat && s->seat->active == s)
- seat_send_changed(s->seat, "ActiveSession", NULL);
+ (void) seat_send_changed(s->seat, "ActiveSession", NULL);
return 0;
}
user_elect_display(s->user);
- session_save(s);
- user_save(s->user);
+ (void) session_save(s);
+ (void) user_save(s->user);
return r;
}
LOG_MESSAGE("Removed session %s.", s->id));
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
- s->leader_pidfd_event_source = sd_event_source_unref(s->leader_pidfd_event_source);
if (s->seat)
seat_evict_position(s->seat, s);
seat_save(s->seat);
}
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ false);
- user_save(s->user);
- user_send_changed(s->user, "Display", NULL);
+ (void) user_save(s->user);
+ (void) user_send_changed(s->user, "Display", NULL);
return 0;
}
return s->idle_hint;
}
- /* For sessions with an explicitly configured tty, let's check its atime */
- if (s->tty) {
- r = get_tty_atime(s->tty, &atime);
- if (r >= 0)
- goto found_atime;
- }
+ if (s->type == SESSION_TTY) {
+ /* For sessions with an explicitly configured tty, let's check its atime */
+ if (s->tty) {
+ r = get_tty_atime(s->tty, &atime);
+ if (r >= 0)
+ goto found_atime;
+ }
- /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of
- * the leader */
- if (pidref_is_set(&s->leader)) {
- r = get_process_ctty_atime(s->leader.pid, &atime);
- if (r >= 0)
- goto found_atime;
+ /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of
+ * the leader */
+ if (pidref_is_set(&s->leader)) {
+ r = get_process_ctty_atime(s->leader.pid, &atime);
+ if (r >= 0)
+ goto found_atime;
+ }
}
if (t)
return;
s->type = t;
- session_save(s);
-
- session_send_changed(s, "Type", NULL);
+ (void) session_save(s);
+ (void) session_send_changed(s, "Type", NULL);
}
int session_set_display(Session *s, const char *display) {
if (r <= 0) /* 0 means the strings were equal */
return r;
- session_save(s);
-
- session_send_changed(s, "Display", NULL);
+ (void) session_save(s);
+ (void) session_send_changed(s, "Display", NULL);
return 1;
}
if (r <= 0) /* 0 means the strings were equal */
return r;
- session_save(s);
-
- session_send_changed(s, "TTY", NULL);
+ (void) session_save(s);
+ (void) session_send_changed(s, "TTY", NULL);
return 1;
}
s->fifo_event_source = sd_event_source_unref(s->fifo_event_source);
s->fifo_fd = safe_close(s->fifo_fd);
-
- if (s->fifo_path) {
- (void) unlink(s->fifo_path);
- s->fifo_path = mfree(s->fifo_path);
- }
-}
-
-static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
- Session *s = ASSERT_PTR(userdata);
-
- assert(s->leader.fd == fd);
- session_stop(s, /* force= */ false);
-
- return 1;
-}
-
-int session_watch_pidfd(Session *s) {
- int r;
-
- assert(s);
-
- if (s->leader.fd < 0)
- return 0;
-
- r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s);
- if (r < 0)
- return r;
-
- r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT);
- if (r < 0)
- return r;
-
- (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd");
-
- return 0;
+ s->fifo_path = unlink_and_free(s->fifo_path);
}
bool session_may_gc(Session *s, bool drop_not_started) {
return true;
r = pidref_is_alive(&s->leader);
+ if (r == -ESRCH)
+ /* Session has no leader. This is probably because the leader vanished before deserializing
+ * pidfd from FD store. */
+ return true;
if (r < 0)
- log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not.", s->leader.pid);
+ log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not: %m", s->leader.pid);
if (r > 0)
return false;
- if (s->fifo_fd >= 0) {
- if (pipe_eof(s->fifo_fd) <= 0)
- return false;
- }
+ if (s->fifo_fd >= 0 && pipe_eof(s->fifo_fd) <= 0)
+ return false;
if (s->scope_job) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
session_release_controller(s, true);
s->controller = TAKE_PTR(name);
- session_save(s);
+ (void) session_save(s);
return 0;
}
s->track = sd_bus_track_unref(s->track);
session_set_type(s, s->original_type);
session_release_controller(s, false);
- session_save(s);
+ (void) session_save(s);
session_restore_vt(s);
}
static const char* const session_class_table[_SESSION_CLASS_MAX] = {
[SESSION_USER] = "user",
+ [SESSION_USER_EARLY] = "user-early",
[SESSION_GREETER] = "greeter",
[SESSION_LOCK_SCREEN] = "lock-screen",
[SESSION_BACKGROUND] = "background",
} SessionState;
typedef enum SessionClass {
- SESSION_USER,
- SESSION_GREETER,
- SESSION_LOCK_SCREEN,
- SESSION_BACKGROUND,
+ SESSION_USER, /* A regular user session */
+ SESSION_USER_EARLY, /* A user session, that is not ordered after systemd-user-sessions.service (i.e. for root) */
+ SESSION_GREETER, /* A login greeter pseudo-session */
+ SESSION_LOCK_SCREEN, /* A lock screen */
+ SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */
_SESSION_CLASS_MAX,
_SESSION_CLASS_INVALID = -EINVAL,
} SessionClass;
+/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. For now,
+ * there's only one class we allow this for. It's generally set for root sessions, but no one else. */
+#define SESSION_CLASS_IS_EARLY(class) ((class) == SESSION_USER_EARLY)
+
typedef enum SessionType {
SESSION_UNSPECIFIED,
SESSION_TTY,
int vtfd;
PidRef leader;
+ bool leader_fd_saved; /* pidfd of leader uploaded to fdstore */
+ pid_t deserialized_pid; /* PID deserialized from state file (for verification when pidfd is used) */
uint32_t audit_id;
int fifo_fd;
int session_set_display(Session *s, const char *display);
int session_set_tty(Session *s, const char *tty);
int session_create_fifo(Session *s);
-int session_watch_pidfd(Session *s);
int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
int session_stop(Session *s, bool force);
int session_finalize(Session *s);
#include "string-table.h"
#include "strv.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "unit-name.h"
#include "user-util.h"
{ "IOWeight", u->user_record->io_weight },
};
- for (size_t i = 0; i < ELEMENTSOF(settings); i++)
- if (settings[i].value != UINT64_MAX) {
- r = sd_bus_message_append(m, "(sv)", settings[i].name, "t", settings[i].value);
- if (r < 0)
- return bus_log_create_error(r);
- }
+ FOREACH_ARRAY(st, settings, ELEMENTSOF(settings)) {
+ if (st->value == UINT64_MAX)
+ continue;
+
+ r = sd_bus_message_append(m, "(sv)", st->name, "t", st->value);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
r = sd_bus_message_close_container(m);
if (r < 0)
if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER))
return (s1->class != SESSION_USER) - (s2->class != SESSION_USER);
+ if ((s1->class != SESSION_USER_EARLY) != (s2->class != SESSION_USER_EARLY))
+ return (s1->class != SESSION_USER_EARLY) - (s2->class != SESSION_USER_EARLY);
+
if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID))
return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID);
#include "constants.h"
#include "daemon-util.h"
#include "device-util.h"
+#include "devnum-util.h"
#include "dirent-util.h"
#include "escape.h"
#include "fd-util.h"
if (m->unlink_nologin)
(void) unlink_or_warn("/run/nologin");
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
sd_event_unref(m->event);
if (r < 0)
return r;
- FOREACH_DEVICE(e, d) {
- int k;
+ r = 0;
- k = manager_process_seat_device(m, d);
- if (k < 0)
- r = k;
- }
+ FOREACH_DEVICE(e, d)
+ RET_GATHER(r, manager_process_seat_device(m, d));
return r;
}
if (r < 0)
return r;
- FOREACH_DEVICE(e, d) {
- int k;
+ r = 0;
- k = manager_process_button_device(m, d);
- if (k < 0)
- r = k;
- }
+ FOREACH_DEVICE(e, d)
+ RET_GATHER(r, manager_process_button_device(m, d));
return r;
}
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/seats: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/seats/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
Seat *s;
- int k;
if (!dirent_is_file(de))
continue;
continue;
}
- k = seat_load(s);
- if (k < 0)
- r = k;
+ RET_GATHER(r, seat_load(s));
}
return r;
}
FOREACH_DIRENT(de, d, return -errno) {
- int k;
_cleanup_free_ char *n = NULL;
+ int k;
if (!dirent_is_file(de))
continue;
+
k = cunescape(de->d_name, 0, &n);
if (k < 0) {
- r = log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name));
continue;
}
+
k = manager_add_user_by_name(m, n, NULL);
if (k < 0)
- r = log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name));
}
return r;
static int manager_enumerate_users(Manager *m) {
_cleanup_closedir_ DIR *d = NULL;
- int r, k;
+ int r;
assert(m);
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/users: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/users/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
User *u;
uid_t uid;
+ int k;
if (!dirent_is_file(de))
continue;
k = parse_uid(de->d_name, &uid);
if (k < 0) {
- r = log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID.", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID, ignoring: %m", de->d_name));
continue;
}
k = manager_add_user_by_uid(m, uid, &u);
if (k < 0) {
- r = log_warning_errno(k, "Failed to add user by file name %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to add user by filename %s, ignoring: %m", de->d_name));
continue;
}
user_add_to_gc_queue(u);
- k = user_load(u);
- if (k < 0)
- r = k;
+ RET_GATHER(r, user_load(u));
}
return r;
}
-static int parse_fdname(const char *fdname, char **session_id, dev_t *dev) {
+static int parse_fdname(const char *fdname, char **ret_session_id, dev_t *ret_devno) {
_cleanup_strv_free_ char **parts = NULL;
_cleanup_free_ char *id = NULL;
- unsigned major, minor;
int r;
+ assert(ret_session_id);
+ assert(ret_devno);
+
parts = strv_split(fdname, "-");
if (!parts)
return -ENOMEM;
- if (strv_length(parts) != 5)
- return -EINVAL;
- if (!streq(parts[0], "session"))
+ if (_unlikely_(!streq(parts[0], "session")))
return -EINVAL;
id = strdup(parts[1]);
if (!id)
return -ENOMEM;
- if (!streq(parts[2], "device"))
+ if (streq(parts[2], "leader")) {
+ *ret_session_id = TAKE_PTR(id);
+ *ret_devno = 0;
+
+ return 0;
+ }
+
+ if (_unlikely_(!streq(parts[2], "device")))
return -EINVAL;
+ unsigned major, minor;
+
r = safe_atou(parts[3], &major);
if (r < 0)
return r;
+
r = safe_atou(parts[4], &minor);
if (r < 0)
return r;
- *dev = makedev(major, minor);
- *session_id = TAKE_PTR(id);
+ *ret_session_id = TAKE_PTR(id);
+ *ret_devno = makedev(major, minor);
return 0;
}
-static int deliver_fd(Manager *m, const char *fdname, int fd) {
- _cleanup_free_ char *id = NULL;
+static int deliver_session_device_fd(Session *s, const char *fdname, int fd, dev_t devno) {
SessionDevice *sd;
struct stat st;
- Session *s;
- dev_t dev;
- int r;
- assert(m);
+ assert(s);
+ assert(fdname);
assert(fd >= 0);
-
- r = parse_fdname(fdname, &id, &dev);
- if (r < 0)
- return log_debug_errno(r, "Failed to parse fd name %s: %m", fdname);
-
- s = hashmap_get(m->sessions, id);
- if (!s)
- /* If the session doesn't exist anymore, the associated session device attached to this fd
- * doesn't either. Let's simply close this fd. */
- return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Failed to attach fd for unknown session: %s", id);
+ assert(devno > 0);
if (fstat(fd, &st) < 0)
/* The device is allowed to go away at a random point, in which case fstat() failing is
* expected. */
- return log_debug_errno(errno, "Failed to stat device fd for session %s: %m", id);
+ return log_debug_errno(errno, "Failed to stat device fd '%s' for session '%s': %m",
+ fdname, s->id);
- if (!S_ISCHR(st.st_mode) || st.st_rdev != dev)
- return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "Device fd doesn't point to the expected character device node");
+ if (!S_ISCHR(st.st_mode) || st.st_rdev != devno)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+ "Device fd '%s' doesn't point to the expected character device node.",
+ fdname);
- sd = hashmap_get(s->devices, &dev);
+ sd = hashmap_get(s->devices, &devno);
if (!sd)
/* Weird, we got an fd for a session device which wasn't recorded in the session state
* file... */
- return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), "Got fd for missing session device [%u:%u] in session %s",
- major(dev), minor(dev), s->id);
+ return log_warning_errno(SYNTHETIC_ERRNO(ENODEV),
+ "Got session device fd '%s' [" DEVNUM_FORMAT_STR "], but not present in session state.",
+ fdname, DEVNUM_FORMAT_VAL(devno));
- log_debug("Attaching fd to session device [%u:%u] for session %s",
- major(dev), minor(dev), s->id);
+ log_debug("Attaching session device fd '%s' [" DEVNUM_FORMAT_STR "] to session '%s'.",
+ fdname, DEVNUM_FORMAT_VAL(devno), s->id);
session_device_attach_fd(sd, fd, s->was_active);
return 0;
}
-static int manager_attach_fds(Manager *m) {
- _cleanup_strv_free_ char **fdnames = NULL;
- int n;
+static int deliver_session_leader_fd_consume(Session *s, const char *fdname, int fd) {
+ _cleanup_(pidref_done) PidRef leader_fdstore = PIDREF_NULL;
+ int r;
- /* Upon restart, PID1 will send us back all fds of session devices that we previously opened. Each
- * file descriptor is associated with a given session. The session ids are passed through FDNAMES. */
+ assert(s);
+ assert(fdname);
+ assert(fd >= 0);
- assert(m);
+ if (!pid_is_valid(s->deserialized_pid)) {
+ r = log_warning_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
+ "Got leader pidfd for session '%s', but LEADER= is not set, refusing.",
+ s->id);
+ goto fail_close;
+ }
- n = sd_listen_fds_with_names(true, &fdnames);
- if (n < 0)
- return log_warning_errno(n, "Failed to acquire passed fd list: %m");
- if (n == 0)
- return 0;
+ if (!s->leader_fd_saved)
+ log_warning("Got leader pidfd for session '%s', but not recorded in session state, proceeding anyway.",
+ s->id);
+ else
+ assert(!pidref_is_set(&s->leader));
+
+ r = pidref_set_pidfd_take(&leader_fdstore, fd);
+ if (r < 0) {
+ if (r == -ESRCH)
+ log_debug_errno(r, "Leader of session '%s' is gone while deserializing.", s->id);
+ else
+ log_warning_errno(r, "Failed to create reference to leader of session '%s': %m", s->id);
+ goto fail_close;
+ }
- for (int i = 0; i < n; i++) {
- int fd = SD_LISTEN_FDS_START + i;
+ if (leader_fdstore.pid != s->deserialized_pid)
+ log_warning("Leader from pidfd (" PID_FMT ") doesn't match with LEADER=" PID_FMT " for session '%s', proceeding anyway.",
+ leader_fdstore.pid, s->deserialized_pid, s->id);
- if (deliver_fd(m, fdnames[i], fd) >= 0)
- continue;
+ r = session_set_leader_consume(s, TAKE_PIDREF(leader_fdstore));
+ if (r < 0)
+ return log_warning_errno(r, "Failed to attach leader pidfd for session '%s': %m", s->id);
+
+ return 0;
+
+fail_close:
+ close_and_notify_warn(fd, fdname);
+ return r;
+}
+
+static int manager_attach_session_fd_one_consume(Manager *m, const char *fdname, int fd) {
+ _cleanup_free_ char *id = NULL;
+ dev_t devno = 0; /* Explicit initialization to appease gcc */
+ Session *s;
+ int r;
- /* Hmm, we couldn't deliver the fd to any session device object? If so, let's close the fd
- * and remove it from fdstore. */
- close_and_notify_warn(fd, fdnames[i]);
+ assert(m);
+ assert(fdname);
+ assert(fd >= 0);
+
+ r = parse_fdname(fdname, &id, &devno);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse stored fd name '%s': %m", fdname);
+ goto fail_close;
}
- return 0;
+ s = hashmap_get(m->sessions, id);
+ if (!s) {
+ /* If the session doesn't exist anymore, let's simply close this fd. */
+ r = log_debug_errno(SYNTHETIC_ERRNO(ENXIO),
+ "Cannot attach fd '%s' to unknown session '%s', ignoring.", fdname, id);
+ goto fail_close;
+ }
+
+ if (devno > 0) {
+ r = deliver_session_device_fd(s, fdname, fd, devno);
+ if (r < 0)
+ goto fail_close;
+ return 0;
+ }
+
+ /* Takes ownership of fd on both success and failure */
+ return deliver_session_leader_fd_consume(s, fdname, fd);
+
+fail_close:
+ close_and_notify_warn(fd, fdname);
+ return r;
}
static int manager_enumerate_sessions(Manager *m) {
+ _cleanup_strv_free_ char **fdnames = NULL;
_cleanup_closedir_ DIR *d = NULL;
- int r = 0, k;
+ int r = 0, n;
assert(m);
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/sessions: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/sessions/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
- struct Session *s;
+ Session *s;
+ int k;
if (!dirent_is_file(de))
continue;
k = manager_add_session(m, de->d_name, &s);
if (k < 0) {
- r = log_warning_errno(k, "Failed to add session by file name %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to add session by filename %s, ignoring: %m", de->d_name));
continue;
}
k = session_load(s);
if (k < 0)
- r = k;
+ RET_GATHER(r, log_warning_errno(k, "Failed to deserialize session '%s', ignoring: %m", s->id));
}
- /* We might be restarted and PID1 could have sent us back the session device fds we previously
- * saved. */
- (void) manager_attach_fds(m);
+ n = sd_listen_fds_with_names(/* unset_environment = */ true, &fdnames);
+ if (n < 0)
+ return log_error_errno(n, "Failed to acquire passed fd list: %m");
+
+ for (int i = 0; i < n; i++) {
+ int fd = SD_LISTEN_FDS_START + i;
+
+ RET_GATHER(r, manager_attach_session_fd_one_consume(m, fdnames[i], fd));
+ }
return r;
}
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/inhibit: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/inhibit/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
- int k;
Inhibitor *i;
+ int k;
if (!dirent_is_file(de))
continue;
k = manager_add_inhibitor(m, de->d_name, &i);
if (k < 0) {
- r = log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name));
continue;
}
- k = inhibitor_load(i);
- if (k < 0)
- r = k;
+ RET_GATHER(r, inhibitor_load(i));
}
return r;
m->event,
&m->idle_action_event_source,
CLOCK_MONOTONIC,
- elapse, USEC_PER_SEC*30,
+ elapse, MIN(USEC_PER_SEC*30, m->idle_action_usec), /* accuracy of 30s, but don't have an accuracy lower than the idle action timeout */
manager_dispatch_idle_action, m);
if (r < 0)
return log_error_errno(r, "Failed to add idle event source: %m");
if (!logind_running())
goto success;
- /* Make sure we don't enter a loop by talking to
- * systemd-logind when it is actually waiting for the
- * background to finish start-up. If the service is
- * "systemd-user" we simply set XDG_RUNTIME_DIR and
+ r = pam_get_item_many(
+ handle,
+ PAM_SERVICE, &service,
+ PAM_XDISPLAY, &display,
+ PAM_TTY, &tty,
+ PAM_RUSER, &remote_user,
+ PAM_RHOST, &remote_host);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
+
+ /* Make sure we don't enter a loop by talking to systemd-logind when it is actually waiting for the
+ * background to finish start-up. If the service is "systemd-user" we simply set XDG_RUNTIME_DIR and
* leave. */
- r = pam_get_item(handle, PAM_SERVICE, (const void**) &service);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM service: @PAMERR@");
if (streq_ptr(service, "systemd-user")) {
char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
/* Otherwise, we ask logind to create a session for us */
- r = pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_XDISPLAY: @PAMERR@");
- r = pam_get_item(handle, PAM_TTY, (const void**) &tty);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_TTY: @PAMERR@");
- r = pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RUSER: @PAMERR@");
- r = pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RHOST: @PAMERR@");
-
seat = getenv_harder(handle, "XDG_SEAT", NULL);
cvtnr = getenv_harder(handle, "XDG_VTNR", NULL);
type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
- tty = strempty(tty);
-
- if (strchr(tty, ':')) {
+ if (tty && strchr(tty, ':')) {
/* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
* and don't pretend that an X display was a tty. */
if (isempty(display))
display = tty;
tty = NULL;
- } else if (streq(tty, "cron")) {
+ } else if (streq_ptr(tty, "cron")) {
/* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
* probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set
* (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
class = "background";
tty = NULL;
- } else if (streq(tty, "ssh")) {
+ } else if (streq_ptr(tty, "ssh")) {
/* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
* details look for "PAM_TTY_KLUDGE" in the openssh sources). */
- type ="tty";
+ type = "tty";
class = "user";
tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
* associated with a pty — won't be tracked by their tty in logind. This is because ssh
* much later (this is because it doesn't know yet if it needs one at all, as whether to
* register a pty or not is negotiated much later in the protocol). */
- } else
+ } else if (tty)
/* Chop off leading /dev prefix that some clients specify, but others do not. */
tty = skip_dev_prefix(tty);
!isempty(tty) ? "tty" : "unspecified";
if (isempty(class))
- class = streq(type, "unspecified") ? "background" : "user";
+ class = streq(type, "unspecified") ? "background" :
+ ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) &&
+ streq(type, "tty")) ? "user-early" : "user");
remote = !isempty(remote_host) && !is_localhost(remote_host);
sd_event_source_unref(m->nscd_cache_flush_event);
#endif
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
manager_varlink_done(m);
return hashmap_isempty(m->machines);
}
-static int manager_run(Manager *m) {
- assert(m);
-
- return bus_event_loop_with_idle(
- m->event,
- m->bus,
- "org.freedesktop.machine1",
- DEFAULT_EXIT_USEC,
- check_idle, m);
-}
-
static int run(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
if (r < 0)
return log_error_errno(r, "Failed to fully start up daemon: %m");
- log_debug("systemd-machined running as pid "PID_FMT, getpid_cached());
r = sd_notify(false, NOTIFY_READY);
if (r < 0)
log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
- r = manager_run(m);
+ r = bus_event_loop_with_idle(
+ m->event,
+ m->bus,
+ "org.freedesktop.machine1",
+ DEFAULT_EXIT_USEC,
+ check_idle, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run main loop: %m");
- (void) sd_notify(false, NOTIFY_STOPPING);
- log_debug("systemd-machined stopped as pid "PID_FMT, getpid_cached());
- return r;
+ return 0;
}
DEFINE_MAIN_FUNCTION(run);
#include <getopt.h>
#include "build.h"
+#include "copy.h"
+#include "creds-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "generator.h"
#include "network-generator.h"
#include "path-util.h"
#include "proc-cmdline.h"
+#include "recurse-dir.h"
#define NETWORKD_UNIT_DIRECTORY "/run/systemd/network"
return r;
}
+static int pick_up_credentials(void) {
+ _cleanup_close_ int credential_dir_fd = -EBADF;
+ int r, ret = 0;
+
+ credential_dir_fd = open_credentials_dir();
+ if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */
+ return 0;
+ if (credential_dir_fd < 0)
+ return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m");
+
+ _cleanup_free_ DirectoryEntries *des = NULL;
+ r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate credentials: %m");
+
+ FOREACH_ARRAY(i, des->entries, des->n_entries) {
+ static const struct {
+ const char *credential_prefix;
+ const char *filename_suffix;
+ } table[] = {
+ { "network.link.", ".link" },
+ { "network.netdev.", ".netdev" },
+ { "network.network.", ".network" },
+ };
+
+ _cleanup_free_ char *fn = NULL;
+ struct dirent *de = *i;
+
+ if (de->d_type != DT_REG)
+ continue;
+
+ FOREACH_ARRAY(t, table, ELEMENTSOF(table)) {
+ const char *e = startswith(de->d_name, t->credential_prefix);
+
+ if (e) {
+ fn = strjoin(e, t->filename_suffix);
+ if (!fn)
+ return log_oom();
+
+ break;
+ }
+ }
+
+ if (!fn)
+ continue;
+
+ if (!filename_is_valid(fn)) {
+ log_warning("Passed credential '%s' would result in invalid filename '%s', ignoring.", de->d_name, fn);
+ continue;
+ }
+
+ _cleanup_free_ char *output = path_join(NETWORKD_UNIT_DIRECTORY, fn);
+ if (!output)
+ return log_oom();
+
+ r = copy_file_at(
+ credential_dir_fd, de->d_name,
+ AT_FDCWD, output,
+ /* open_flags= */ 0,
+ 0644,
+ /* flags= */ 0);
+ if (r < 0)
+ RET_GATHER(ret, log_warning_errno(r, "Failed to copy credential %s → file %s: %m", de->d_name, output));
+ else
+ log_info("Installed %s from credential.", output);
+ }
+
+ return ret;
+}
+
static int help(void) {
printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n"
" -h --help Show this help\n"
static int run(int argc, char *argv[]) {
_cleanup_(context_clear) Context context = {};
- int r;
+ int r, ret = 0;
log_setup();
if (r < 0)
return log_warning_errno(r, "Failed to merge multiple command line options: %m");
- return context_save(&context);
+ RET_GATHER(ret, context_save(&context));
+ RET_GATHER(ret, pick_up_credentials());
+
+ return ret;
}
DEFINE_MAIN_FUNCTION(run);
'networkd-nexthop.c',
'networkd-queue.c',
'networkd-radv.c',
- 'networkd-route-util.c',
'networkd-route.c',
+ 'networkd-route-metric.c',
+ 'networkd-route-nexthop.c',
+ 'networkd-route-util.c',
'networkd-routing-policy-rule.c',
'networkd-setlink.c',
'networkd-speed-meter.c',
dest = a ? &a->sa : &b->sa;
- r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l);
+ r = unhexmem_full(rvalue, SIZE_MAX, /* secure = */ true, &p, &l);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse key. Ignoring assignment: %m");
return 0;
if (r < 0)
return log_oom();
- r = unhexmem(rvalue, strlen(rvalue), &p, &l);
+ r = unhexmem(rvalue, &p, &l);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
static NetDev *netdev_free(NetDev *netdev) {
assert(netdev);
- netdev_detach_from_manager(netdev);
-
- free(netdev->filename);
-
- free(netdev->description);
- free(netdev->ifname);
- condition_free_list(netdev->conditions);
-
/* Invoke the per-kind done() destructor, but only if the state field is initialized. We conditionalize that
* because we parse .netdev files twice: once to determine the kind (with a short, minimal NetDev structure
* allocation, with no room for per-kind fields), and once to read the kind's properties (with a full,
NETDEV_VTABLE(netdev)->done)
NETDEV_VTABLE(netdev)->done(netdev);
+ netdev_detach_from_manager(netdev);
+
+ condition_free_list(netdev->conditions);
+ free(netdev->filename);
+ free(netdev->description);
+ free(netdev->ifname);
+
return mfree(netdev);
}
VxLan *v = ASSERT_PTR(userdata);
int r;
- r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high);
+ r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high, /* allow_zero = */ false);
if (r < 0)
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse VXLAN port range '%s'. Port should be greater than 0 and less than 65535.", rvalue);
#include "sd-resolve.h"
#include "alloc-util.h"
+#include "creds-util.h"
#include "dns-domain.h"
#include "event-util.h"
#include "fd-util.h"
#include "networkd-util.h"
#include "parse-helpers.h"
#include "parse-util.h"
+#include "path-util.h"
#include "random-util.h"
#include "resolve-private.h"
#include "string-util.h"
const char *lvalue) {
_cleanup_(erase_and_freep) void *key = NULL;
+ _cleanup_(erase_and_freep) char *cred = NULL;
+ const char *cred_name;
size_t len;
int r;
return 0;
}
- if (!streq(lvalue, "PublicKey"))
+ cred_name = startswith(rvalue, "@");
+ if (cred_name) {
+ r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to read credential for wireguard key (%s=), ignoring assignment: %m",
+ lvalue);
+ return 0;
+ }
+
+ } else if (!streq(lvalue, "PublicKey"))
(void) warn_file_is_world_accessible(filename, NULL, unit, line);
- r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len);
+ r = unbase64mem_full(cred ?: rvalue, SIZE_MAX, /* secure = */ true, &key, &len);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
void *data,
void *userdata) {
- assert(filename);
- assert(rvalue);
- assert(userdata);
-
Wireguard *w = WIREGUARD(userdata);
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
- _cleanup_free_ char *host = NULL;
- union in_addr_union addr;
- const char *p;
+ _cleanup_free_ char *cred = NULL;
+ const char *cred_name, *endpoint;
uint16_t port;
- int family, r;
+ int r;
+
+ assert(filename);
+ assert(rvalue);
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
- r = in_addr_port_ifindex_name_from_string_auto(rvalue, &family, &addr, &port, NULL, NULL);
+ cred_name = startswith(rvalue, "@");
+ if (cred_name) {
+ r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to read credential for wireguard endpoint, ignoring assignment: %m");
+ return 0;
+ }
+
+ endpoint = strstrip(cred);
+ } else
+ endpoint = rvalue;
+
+ union in_addr_union addr;
+ int family;
+
+ r = in_addr_port_ifindex_name_from_string_auto(endpoint, &family, &addr, &port, NULL, NULL);
if (r >= 0) {
if (family == AF_INET)
peer->endpoint.in = (struct sockaddr_in) {
return 0;
}
- p = strrchr(rvalue, ':');
+ _cleanup_free_ char *host = NULL;
+ const char *p;
+
+ p = strrchr(endpoint, ':');
if (!p) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unable to find port of endpoint, ignoring assignment: %s",
- rvalue);
+ rvalue); /* We log the original assignment instead of resolved credential here,
+ as the latter might be previously encrypted and we'd expose them in
+ unprotected logs otherwise. */
return 0;
}
- host = strndup(rvalue, p - rvalue);
+ host = strndup(endpoint, p - endpoint);
if (!host)
return log_oom();
+ p++;
if (!dns_name_is_valid(host)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
return 0;
}
- p++;
r = parse_ip_port(p, &port);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
return 0;
}
+static int wireguard_read_default_key_cred(NetDev *netdev, const char *filename) {
+ Wireguard *w = WIREGUARD(netdev);
+ _cleanup_free_ char *config_name = NULL;
+ int r;
+
+ assert(filename);
+
+ r = path_extract_filename(filename, &config_name);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: Failed to extract config name, ignoring network device: %m",
+ filename);
+
+ char *p = endswith(config_name, ".netdev");
+ if (!p)
+ /* Fuzzer run? Then we just ignore this device. */
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: Invalid netdev config name, refusing default key lookup.",
+ filename);
+ *p = '\0';
+
+ _cleanup_(erase_and_freep) char *cred = NULL;
+
+ r = read_credential(strjoina("network.wireguard.private.", config_name), (void**) &cred, /* ret_size = */ NULL);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: No private key specified and default key isn't available, "
+ "ignoring network device: %m",
+ filename);
+
+ _cleanup_(erase_and_freep) void *key = NULL;
+ size_t len;
+
+ r = unbase64mem_full(cred, SIZE_MAX, /* secure = */ true, &key, &len);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: No private key specified and default key cannot be parsed, "
+ "ignoring network device: %m",
+ filename);
+ if (len != WG_KEY_LEN)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: No private key specified and default key is invalid. "
+ "Ignoring network device.",
+ filename);
+
+ memcpy(w->private_key, key, WG_KEY_LEN);
+ return 0;
+}
+
static int wireguard_verify(NetDev *netdev, const char *filename) {
Wireguard *w = WIREGUARD(netdev);
int r;
"Failed to read private key from %s. Ignoring network device.",
w->private_key_file);
- if (eqzero(w->private_key))
- return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
- "%s: Missing PrivateKey= or PrivateKeyFile=, "
- "Ignoring network device.", filename);
+ if (eqzero(w->private_key)) {
+ r = wireguard_read_default_key_cred(netdev, filename);
+ if (r < 0)
+ return r;
+ }
LIST_FOREACH(peers, peer, w->peers) {
if (wireguard_peer_verify(peer) < 0) {
{},
};
- r = json_dispatch(reply, dispatch_table, JSON_LOG, &id);
+ r = json_dispatch(reply, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &id);
if (r < 0)
return r;
route->priority = link->network->dhcp_route_metric;
if (!route->table_set)
route->table = link_get_dhcp4_route_table(link);
- if (route->mtu == 0)
- route->mtu = link->network->dhcp_route_mtu;
- if (route->quickack < 0)
- route->quickack = link->network->dhcp_quickack;
- if (route->initcwnd == 0)
- route->initcwnd = link->network->dhcp_initial_congestion_window;
- if (route->initrwnd == 0)
- route->initrwnd = link->network->dhcp_advertised_receive_window;
+ r = route_metric_set(&route->metric, RTAX_MTU, link->network->dhcp_route_mtu);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_INITCWND, link->network->dhcp_initial_congestion_window);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_INITRWND, link->network->dhcp_advertised_receive_window);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->dhcp_quickack);
+ if (r < 0)
+ return r;
if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
link->dhcp4_configured = false;
JSON_BUILD_PAIR_UNSIGNED("Priority", route->priority),
JSON_BUILD_PAIR_UNSIGNED("Table", route->table),
JSON_BUILD_PAIR_STRING("TableString", table),
- JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route->mtu),
+ JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route_metric_get(&route->metric, RTAX_MTU)),
JSON_BUILD_PAIR_UNSIGNED("Preference", route->pref),
JSON_BUILD_PAIR_UNSIGNED("Flags", route->flags),
JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)),
usec = 5 * USEC_PER_SEC;
else
- /* Otherwise, use the currently set value. */
+ /* Otherwise, use the implied default value. */
usec = link->network->ignore_carrier_loss_usec;
if (usec == USEC_INFINITY)
if (master_ifindex == link->ifindex)
master_ifindex = 0;
- if (master_ifindex == link->master_ifindex)
- return 0;
-
- if (link->master_ifindex == 0)
- log_link_debug(link, "Attached to master interface: %i", master_ifindex);
- else if (master_ifindex == 0)
- log_link_debug(link, "Detached from master interface: %i", link->master_ifindex);
- else
- log_link_debug(link, "Master interface changed: %i %s %i", link->master_ifindex,
- special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), master_ifindex);
-
- link_drop_from_master(link);
+ if (master_ifindex != link->master_ifindex) {
+ if (link->master_ifindex == 0)
+ log_link_debug(link, "Attached to master interface: %i", master_ifindex);
+ else if (master_ifindex == 0)
+ log_link_debug(link, "Detached from master interface: %i", link->master_ifindex);
+ else
+ log_link_debug(link, "Master interface changed: %i %s %i", link->master_ifindex,
+ special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), master_ifindex);
- link->master_ifindex = master_ifindex;
+ link_drop_from_master(link);
+ link->master_ifindex = master_ifindex;
+ }
r = link_append_to_master(link);
if (r < 0)
sd_device_monitor_unref(m->device_monitor);
manager_varlink_done(m);
-
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
free(m->dynamic_timezone);
FirewallContext *fw_ctx;
+ bool request_queued;
OrderedSet *request_queue;
Hashmap *tuntap_fds_by_name;
ndisc_set_route_priority(link, route);
if (!route->protocol_set)
route->protocol = RTPROT_RA;
- if (route->quickack < 0)
- route->quickack = link->network->ipv6_accept_ra_quickack;
- if (route->mtu == 0)
- route->mtu = mtu;
- if (route->hop_limit == 0)
- route->hop_limit = hop_limit;
+ r = route_metric_set(&route->metric, RTAX_MTU, mtu);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_HOPLIMIT, hop_limit);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->ipv6_accept_ra_quickack);
+ if (r < 0)
+ return r;
is_new = route_get(NULL, link, route, NULL) < 0;
Route.Scope, config_parse_route_scope, 0, 0
Route.PreferredSource, config_parse_preferred_src, 0, 0
Route.Table, config_parse_route_table, 0, 0
-Route.MTUBytes, config_parse_route_mtu, AF_UNSPEC, 0
-Route.GatewayOnLink, config_parse_route_boolean, 0, 0
-Route.GatewayOnlink, config_parse_route_boolean, 0, 0
+Route.GatewayOnLink, config_parse_route_gateway_onlink, 0, 0
+Route.GatewayOnlink, config_parse_route_gateway_onlink, 0, 0
Route.IPv6Preference, config_parse_ipv6_route_preference, 0, 0
Route.Protocol, config_parse_route_protocol, 0, 0
Route.Type, config_parse_route_type, 0, 0
-Route.TCPRetransmissionTimeoutSec, config_parse_route_tcp_rto, 0, 0
-Route.HopLimit, config_parse_route_hop_limit, 0, 0
-Route.InitialCongestionWindow, config_parse_route_tcp_window, 0, 0
-Route.InitialAdvertisedReceiveWindow, config_parse_route_tcp_window, 0, 0
-Route.TCPAdvertisedMaximumSegmentSize, config_parse_tcp_advmss, 0, 0
-Route.TCPCongestionControlAlgorithm, config_parse_tcp_congestion, 0, 0
-Route.QuickAck, config_parse_route_boolean, 0, 0
-Route.FastOpenNoCookie, config_parse_route_boolean, 0, 0
-Route.TTLPropagate, config_parse_warn_compat, DISABLED_LEGACY, 0
Route.MultiPathRoute, config_parse_multipath_route, 0, 0
Route.NextHop, config_parse_route_nexthop, 0, 0
+Route.MTUBytes, config_parse_route_metric_mtu, RTAX_MTU, 0
+Route.TCPAdvertisedMaximumSegmentSize, config_parse_route_metric_advmss, RTAX_ADVMSS, 0
+Route.HopLimit, config_parse_route_metric_hop_limit, RTAX_HOPLIMIT, 0
+Route.InitialCongestionWindow, config_parse_route_metric_tcp_window, RTAX_INITCWND, 0
+Route.TCPRetransmissionTimeoutSec, config_parse_route_metric_tcp_rto, RTAX_RTO_MIN, 0
+Route.InitialAdvertisedReceiveWindow, config_parse_route_metric_tcp_window, RTAX_INITRWND, 0
+Route.QuickAck, config_parse_route_metric_boolean, RTAX_QUICKACK, 0
+Route.TCPCongestionControlAlgorithm, config_parse_route_metric_tcp_congestion, RTAX_CC_ALGO, 0
+Route.FastOpenNoCookie, config_parse_route_metric_boolean, RTAX_FASTOPEN_NO_COOKIE, 0
+Route.TTLPropagate, config_parse_warn_compat, DISABLED_LEGACY, 0
NextHop.Id, config_parse_nexthop_id, 0, 0
NextHop.Gateway, config_parse_nexthop_gateway, 0, 0
NextHop.Family, config_parse_nexthop_family, 0, 0
network->ignore_carrier_loss_usec = USEC_INFINITY;
}
- if (!network->ignore_carrier_loss_set) {
- network->ignore_carrier_loss_set = true;
+ if (!network->ignore_carrier_loss_set) /* Set implied default. */
network->ignore_carrier_loss_usec = network->configure_without_carrier ? USEC_INFINITY : 0;
- }
if (IN_SET(network->activation_policy, ACTIVATION_POLICY_DOWN, ACTIVATION_POLICY_ALWAYS_DOWN, ACTIVATION_POLICY_MANUAL)) {
if (network->required_for_online < 0 ||
assert(manager);
+ if (id == 0)
+ return -EINVAL;
+
req = ordered_set_get(
manager->request_queue,
&(Request) {
return 1;
}
+int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret) {
+ NextHop *nexthop;
+
+ assert(manager);
+
+ if (id == 0)
+ return -EINVAL;
+
+ if (nexthop_get_request_by_id(manager, id, NULL) >= 0)
+ goto not_ready;
+
+ if (nexthop_get_by_id(manager, id, &nexthop) < 0)
+ goto not_ready;
+
+ if (!nexthop_exists(nexthop))
+ goto not_ready;
+
+ if (ret)
+ *ret = nexthop;
+
+ return true;
+
+not_ready:
+ if (ret)
+ *ret = NULL;
+
+ return false;
+}
+
static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) {
struct nexthop_grp *nhg;
+ int r;
assert(link);
assert(nexthop);
/* All group members must be configured first. */
HASHMAP_FOREACH(nhg, nexthop->group) {
- NextHop *g;
-
- if (nexthop_get_by_id(link->manager, nhg->id, &g) < 0)
- return false;
-
- if (!nexthop_exists(g))
- return false;
+ r = nexthop_is_ready(link->manager, nhg->id, NULL);
+ if (r <= 0)
+ return r;
}
return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw);
int link_request_static_nexthops(Link *link, bool only_ipv4);
int nexthop_get_by_id(Manager *manager, uint32_t id, NextHop **ret);
+int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret);
int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m);
int manager_build_nexthop_ids(Manager *manager);
if (req->counter)
(*req->counter)++;
+ /* If this is called in the ORDERED_SET_FOREACH() loop of manager_process_requests(), we need to
+ * exit from the loop, due to the limitation of the iteration on OrderedSet. */
+ manager->request_queued = true;
+
if (ret)
*ret = req;
}
int manager_process_requests(Manager *manager) {
+ Request *req;
int r;
assert(manager);
- for (;;) {
- bool processed = false;
- Request *req;
-
- ORDERED_SET_FOREACH(req, manager->request_queue) {
- _cleanup_(link_unrefp) Link *link = link_ref(req->link);
+ manager->request_queued = false;
- assert(req->process);
+ ORDERED_SET_FOREACH(req, manager->request_queue) {
+ _cleanup_(link_unrefp) Link *link = link_ref(req->link);
- if (req->waiting_reply)
- continue; /* Waiting for netlink reply. */
+ assert(req->process);
- /* Typically, requests send netlink message asynchronously. If there are many requests
- * queued, then this event may make reply callback queue in sd-netlink full. */
- if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
- netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
- fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD)
- return 0;
+ if (req->waiting_reply)
+ continue; /* Waiting for netlink reply. */
- r = req->process(req, link, req->userdata);
- if (r == 0)
- continue;
+ /* Typically, requests send netlink message asynchronously. If there are many requests
+ * queued, then this event may make reply callback queue in sd-netlink full. */
+ if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
+ netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
+ fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD)
+ return 0;
- processed = true;
+ r = req->process(req, link, req->userdata);
+ if (r == 0) { /* The request is not ready. */
+ if (manager->request_queued)
+ break; /* a new request is queued during processing the request. */
+ continue;
+ }
- /* If the request sends netlink message, e.g. for Address or so, the Request object
- * is referenced by the netlink slot, and will be detached later by its destroy callback.
- * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */
- if (!req->waiting_reply)
- request_detach(manager, req);
+ /* If the request sends netlink message, e.g. for Address or so, the Request object is
+ * referenced by the netlink slot, and will be detached later by its destroy callback.
+ * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */
+ if (!req->waiting_reply)
+ request_detach(manager, req);
- if (r < 0 && link) {
- link_enter_failed(link);
- /* link_enter_failed() may remove multiple requests,
- * hence we need to exit from the loop. */
- break;
- }
+ if (r < 0 && link) {
+ link_enter_failed(link);
+ /* link_enter_failed() may remove multiple requests,
+ * hence we need to exit from the loop. */
+ break;
}
- /* When at least one request is processed, then another request may be ready now. */
- if (!processed)
+ if (manager->request_queued)
break;
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "netlink-util.h"
+#include "networkd-route.h"
+#include "networkd-route-metric.h"
+#include "parse-util.h"
+#include "string-util.h"
+
+void route_metric_done(RouteMetric *metric) {
+ assert(metric);
+
+ free(metric->metrics);
+ free(metric->metrics_set);
+ free(metric->tcp_congestion_control_algo);
+}
+
+int route_metric_copy(const RouteMetric *src, RouteMetric *dest) {
+ assert(src);
+ assert(dest);
+
+ dest->n_metrics = src->n_metrics;
+ if (src->n_metrics > 0) {
+ assert(src->n_metrics != 1);
+
+ dest->metrics = newdup(uint32_t, src->metrics, src->n_metrics);
+ if (!dest->metrics)
+ return -ENOMEM;
+ } else
+ dest->metrics = NULL;
+
+ dest->n_metrics_set = src->n_metrics_set;
+ if (src->n_metrics_set > 0) {
+ assert(src->n_metrics_set != 1);
+
+ dest->metrics_set = newdup(bool, src->metrics_set, src->n_metrics_set);
+ if (!dest->metrics_set)
+ return -ENOMEM;
+ } else
+ dest->metrics_set = NULL;
+
+ return free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo);
+}
+
+void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) {
+ assert(metric);
+
+ siphash24_compress_typesafe(metric->n_metrics, state);
+ siphash24_compress_safe(metric->metrics, sizeof(uint32_t) * metric->n_metrics, state);
+ siphash24_compress_string(metric->tcp_congestion_control_algo, state);
+}
+
+int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = memcmp_nn(a->metrics, a->n_metrics * sizeof(uint32_t), b->metrics, b->n_metrics * sizeof(uint32_t));
+ if (r != 0)
+ return r;
+
+ return strcmp_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo);
+}
+
+int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force) {
+ assert(metric);
+
+ if (force) {
+ if (!GREEDY_REALLOC0(metric->metrics_set, attr + 1))
+ return -ENOMEM;
+
+ metric->metrics_set[attr] = true;
+ metric->n_metrics_set = MAX(metric->n_metrics_set, (size_t) (attr + 1));
+ } else {
+ /* Do not override the values specified in conf parsers. */
+ if (metric->n_metrics_set > attr && metric->metrics_set[attr])
+ return 0;
+ }
+
+ if (value != 0) {
+ if (!GREEDY_REALLOC0(metric->metrics, attr + 1))
+ return -ENOMEM;
+
+ metric->metrics[attr] = value;
+ metric->n_metrics = MAX(metric->n_metrics, (size_t) (attr + 1));
+ return 0;
+ }
+
+ if (metric->n_metrics <= attr)
+ return 0;
+
+ metric->metrics[attr] = 0;
+
+ for (size_t i = metric->n_metrics; i > 0; i--)
+ if (metric->metrics[i-1] != 0) {
+ metric->n_metrics = i;
+ return 0;
+ }
+
+ metric->n_metrics = 0;
+ return 0;
+}
+
+static void route_metric_unset(RouteMetric *metric, uint16_t attr) {
+ assert(metric);
+
+ if (metric->n_metrics_set > attr)
+ metric->metrics_set[attr] = false;
+
+ assert_se(route_metric_set_full(metric, attr, 0, /* force = */ false) >= 0);
+}
+
+uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr) {
+ assert(metric);
+
+ if (metric->n_metrics <= attr)
+ return 0;
+
+ return metric->metrics[attr];
+}
+
+int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m) {
+ int r;
+
+ assert(metric);
+ assert(m);
+
+ if (metric->n_metrics <= 0 && isempty(metric->tcp_congestion_control_algo))
+ return 0;
+
+ r = sd_netlink_message_open_container(m, RTA_METRICS);
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < metric->n_metrics; i++) {
+ if (i == RTAX_CC_ALGO)
+ continue;
+
+ if (metric->metrics[i] == 0)
+ continue;
+
+ r = sd_netlink_message_append_u32(m, i, metric->metrics[i]);
+ if (r < 0)
+ return r;
+ }
+
+ if (!isempty(metric->tcp_congestion_control_algo)) {
+ r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, metric->tcp_congestion_control_algo);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *m) {
+ _cleanup_free_ void *data = NULL;
+ size_t len;
+ int r;
+
+ assert(metric);
+ assert(m);
+
+ r = sd_netlink_message_read_data(m, RTA_METRICS, &len, &data);
+ if (r == -ENODATA)
+ return 0;
+ if (r < 0)
+ return log_warning_errno(r, "rtnl: Could not read RTA_METRICS attribute, ignoring: %m");
+
+ for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ size_t rta_type = RTA_TYPE(rta);
+
+ if (rta_type == RTAX_CC_ALGO) {
+ char *p = memdup_suffix0(RTA_DATA(rta), RTA_PAYLOAD(rta));
+ if (!p)
+ return log_oom();
+
+ free_and_replace(metric->tcp_congestion_control_algo, p);
+
+ } else {
+ if (RTA_PAYLOAD(rta) != sizeof(uint32_t))
+ continue;
+
+ r = route_metric_set(metric, rta_type, *(uint32_t*) RTA_DATA(rta));
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ return 0;
+}
+
+static int config_parse_route_metric_advmss_impl(
+ 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) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ uint64_t u;
+ int r;
+
+ assert(rvalue);
+
+ r = parse_size(rvalue, 1024, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (u == 0 || u > UINT32_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = (uint32_t) u;
+ return 1;
+}
+
+static int config_parse_route_metric_hop_limit_impl(
+ 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) {
+
+ uint32_t k, *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+ if (k == 0 || k > 255) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = k;
+ return 1;
+}
+
+int config_parse_tcp_window(
+ 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) {
+
+ uint32_t k, *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+ if (k == 0 || k >= 1024) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = k;
+ return 1;
+}
+
+static int config_parse_route_metric_tcp_rto_impl(
+ 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) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ usec_t usec;
+ int r;
+
+ assert(rvalue);
+
+ r = parse_sec(rvalue, &usec);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (!timestamp_is_set(usec) ||
+ DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = (uint32_t) DIV_ROUND_UP(usec, USEC_PER_MSEC);
+ return 1;
+}
+
+static int config_parse_route_metric_boolean_impl(
+ 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) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = r;
+ return 1;
+}
+
+#define DEFINE_CONFIG_PARSE_ROUTE_METRIC(name, parser) \
+ int config_parse_route_metric_##name( \
+ const char *unit, \
+ const char *filename, \
+ unsigned line, \
+ const char *section, \
+ unsigned section_line, \
+ const char *lvalue, \
+ int ltype, \
+ const char *rvalue, \
+ void *data, \
+ void *userdata) { \
+ \
+ Network *network = ASSERT_PTR(userdata); \
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL; \
+ uint16_t attr_type = ltype; \
+ int r; \
+ \
+ assert(filename); \
+ assert(section); \
+ assert(lvalue); \
+ assert(rvalue); \
+ \
+ r = route_new_static(network, filename, section_line, &route); \
+ if (r == -ENOMEM) \
+ return log_oom(); \
+ if (r < 0) { \
+ log_syntax(unit, LOG_WARNING, filename, line, r, \
+ "Failed to allocate route, ignoring assignment: %m"); \
+ return 0; \
+ } \
+ \
+ if (isempty(rvalue)) { \
+ route_metric_unset(&route->metric, attr_type); \
+ TAKE_PTR(route); \
+ return 0; \
+ } \
+ \
+ uint32_t k; \
+ r = parser(unit, filename, line, section, section_line, \
+ lvalue, /* ltype = */ 0, rvalue, \
+ &k, userdata); \
+ if (r <= 0) \
+ return r; \
+ \
+ if (route_metric_set_full( \
+ &route->metric, \
+ attr_type, \
+ k, \
+ /* force = */ true) < 0) \
+ return log_oom(); \
+ \
+ TAKE_PTR(route); \
+ return 0; \
+ }
+
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(mtu, config_parse_mtu);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(advmss, config_parse_route_metric_advmss_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(hop_limit, config_parse_route_metric_hop_limit_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_window, config_parse_tcp_window);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_rto, config_parse_route_metric_tcp_rto_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(boolean, config_parse_route_metric_boolean_impl);
+
+int config_parse_route_metric_tcp_congestion(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = ASSERT_PTR(userdata);
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ int r;
+
+ assert(filename);
+ assert(rvalue);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ r = config_parse_string(unit, filename, line, section, section_line, lvalue, 0,
+ rvalue, &route->metric.tcp_congestion_control_algo, userdata);
+ if (r <= 0)
+ return r;
+
+ TAKE_PTR(route);
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "hash-funcs.h"
+
+typedef struct RouteMetric {
+ size_t n_metrics; /* maximum metric attr type with non-zero value */
+ uint32_t *metrics; /* RTAX_*, except for RTAX_CC_ALGO */
+
+ size_t n_metrics_set;
+ bool *metrics_set; /* used by conf parsers */
+
+ char *tcp_congestion_control_algo; /* RTAX_CC_ALGO */
+} RouteMetric;
+
+#define ROUTE_METRIC_NULL ((const RouteMetric) {})
+
+void route_metric_done(RouteMetric *metric);
+int route_metric_copy(const RouteMetric *src, RouteMetric *dest);
+
+void route_metric_hash_func(const RouteMetric *metric, struct siphash *state);
+int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b);
+
+int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force);
+static inline int route_metric_set(RouteMetric *metric, uint16_t attr, uint32_t value) {
+ return route_metric_set_full(metric, attr, value, false);
+}
+uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr);
+
+int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m);
+int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *message);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_mtu);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_advmss);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_hop_limit);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_window);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_rto);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_boolean);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_congestion);
+CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/nexthop.h>
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "netlink-util.h"
+#include "networkd-route.h"
+#include "networkd-route-nexthop.h"
+#include "parse-util.h"
+#include "string-util.h"
+
+int config_parse_route_nexthop(
+ 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;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ uint32_t id;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ if (isempty(rvalue)) {
+ route->nexthop_id = 0;
+ TAKE_PTR(route);
+ return 0;
+ }
+
+ r = safe_atou32(rvalue, &id);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (id == 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ route->nexthop_id = id;
+ TAKE_PTR(route);
+ return 0;
+}
+
+int config_parse_multipath_route(
+ 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) {
+
+ _cleanup_(multipath_route_freep) MultipathRoute *m = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ _cleanup_free_ char *word = NULL;
+ Network *network = userdata;
+ union in_addr_union a;
+ int family, r;
+ const char *p;
+ char *dev;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ if (isempty(rvalue)) {
+ route->multipath_routes = ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free);
+ TAKE_PTR(route);
+ return 0;
+ }
+
+ m = new0(MultipathRoute, 1);
+ if (!m)
+ return log_oom();
+
+ p = rvalue;
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r <= 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid multipath route option, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ dev = strchr(word, '@');
+ if (dev) {
+ *dev++ = '\0';
+
+ r = parse_ifindex(dev);
+ if (r > 0)
+ m->ifindex = r;
+ else {
+ if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue);
+ return 0;
+ }
+
+ m->ifname = strdup(dev);
+ if (!m->ifname)
+ return log_oom();
+ }
+ }
+
+ r = in_addr_from_string_auto(word, &family, &a);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue);
+ return 0;
+ }
+ m->gateway.address = a;
+ m->gateway.family = family;
+
+ if (!isempty(p)) {
+ r = safe_atou32(p, &m->weight);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid multipath route weight, ignoring assignment: %s", p);
+ return 0;
+ }
+ /* ip command takes weight in the range 1…255, while kernel takes the value in the
+ * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip
+ * command uses, then networkd decreases by one and stores it to match the range which
+ * kernel uses. */
+ if (m->weight == 0 || m->weight > 256) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid multipath route weight, ignoring assignment: %s", p);
+ return 0;
+ }
+ m->weight--;
+ }
+
+ r = ordered_set_ensure_put(&route->multipath_routes, NULL, m);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to store multipath route, ignoring assignment: %m");
+ return 0;
+ }
+
+ TAKE_PTR(m);
+ TAKE_PTR(route);
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "conf-parser.h"
+
+CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop);
+CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route);
.type = RTN_UNICAST,
.table = RT_TABLE_MAIN,
.lifetime_usec = USEC_INFINITY,
- .quickack = -1,
- .fast_open_no_cookie = -1,
.gateway_onlink = -1,
};
return 0;
}
-static int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) {
+int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) {
_cleanup_(config_section_freep) ConfigSection *n = NULL;
_cleanup_(route_freep) Route *route = NULL;
int r;
set_remove(route->manager->routes, route);
ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free);
-
+ route_metric_done(&route->metric);
sd_event_source_disable_unref(route->expire);
- free(route->tcp_congestion_control_algo);
-
return mfree(route);
}
siphash24_compress_typesafe(route->protocol, state);
siphash24_compress_typesafe(route->scope, state);
siphash24_compress_typesafe(route->type, state);
-
- siphash24_compress_typesafe(route->initcwnd, state);
- siphash24_compress_typesafe(route->initrwnd, state);
-
- siphash24_compress_typesafe(route->advmss, state);
+ route_metric_hash_func(&route->metric, state);
siphash24_compress_typesafe(route->nexthop_id, state);
break;
if (r != 0)
return r;
- r = CMP(a->initcwnd, b->initcwnd);
- if (r != 0)
- return r;
-
- r = CMP(a->initrwnd, b->initrwnd);
- if (r != 0)
- return r;
-
- r = CMP(a->advmss, b->advmss);
+ r = route_metric_compare_func(&a->metric, &b->metric);
if (r != 0)
return r;
dest->link = NULL;
dest->manager = NULL;
dest->multipath_routes = NULL;
+ dest->metric = ROUTE_METRIC_NULL;
dest->expire = NULL;
- dest->tcp_congestion_control_algo = NULL;
- r = free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo);
+ r = route_metric_copy(&src->metric, &dest->metric);
if (r < 0)
return r;
return r;
}
- r = sd_netlink_message_open_container(m, RTA_METRICS);
- if (r < 0)
- return r;
-
- if (route->mtu > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_MTU, route->mtu);
- if (r < 0)
- return r;
- }
-
- if (route->initcwnd > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_INITCWND, route->initcwnd);
- if (r < 0)
- return r;
- }
-
- if (route->initrwnd > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_INITRWND, route->initrwnd);
- if (r < 0)
- return r;
- }
-
- if (route->quickack >= 0) {
- r = sd_netlink_message_append_u32(m, RTAX_QUICKACK, route->quickack);
- if (r < 0)
- return r;
- }
-
- if (route->fast_open_no_cookie >= 0) {
- r = sd_netlink_message_append_u32(m, RTAX_FASTOPEN_NO_COOKIE, route->fast_open_no_cookie);
- if (r < 0)
- return r;
- }
-
- if (route->advmss > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_ADVMSS, route->advmss);
- if (r < 0)
- return r;
- }
-
- if (!isempty(route->tcp_congestion_control_algo)) {
- r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, route->tcp_congestion_control_algo);
- if (r < 0)
- return r;
- }
-
- if (route->hop_limit > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_HOPLIMIT, route->hop_limit);
- if (r < 0)
- return r;
- }
-
- if (route->tcp_rto_usec > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_RTO_MIN, DIV_ROUND_UP(route->tcp_rto_usec, USEC_PER_MSEC));
- if (r < 0)
- return r;
- }
-
- r = sd_netlink_message_close_container(m);
+ /* metrics */
+ r = route_metric_set_netlink_message(&route->metric, m);
if (r < 0)
return r;
struct nexthop_grp *nhg;
NextHop *nh;
- if (nexthop_get_by_id(link->manager, route->nexthop_id, &nh) < 0)
- return false;
-
- if (!nexthop_exists(nh))
- return false;
+ r = nexthop_is_ready(link->manager, route->nexthop_id, &nh);
+ if (r <= 0)
+ return r;
HASHMAP_FOREACH(nhg, nh->group) {
- NextHop *g;
-
- if (nexthop_get_by_id(link->manager, nhg->id, &g) < 0)
- return false;
-
- if (!nexthop_exists(g))
- return false;
+ r = nexthop_is_ready(link->manager, nhg->id, NULL);
+ if (r <= 0)
+ return r;
}
}
return 0;
}
- r = sd_netlink_message_enter_container(message, RTA_METRICS);
- if (r < 0 && r != -ENODATA) {
- log_link_error_errno(link, r, "rtnl: Could not enter RTA_METRICS container, ignoring: %m");
+ /* metrics */
+ if (route_metric_read_netlink_message(&tmp->metric, message) < 0)
return 0;
- }
- if (r >= 0) {
- r = sd_netlink_message_read_u32(message, RTAX_INITCWND, &tmp->initcwnd);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid initcwnd, ignoring: %m");
- return 0;
- }
-
- r = sd_netlink_message_read_u32(message, RTAX_INITRWND, &tmp->initrwnd);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid initrwnd, ignoring: %m");
- return 0;
- }
-
- r = sd_netlink_message_read_u32(message, RTAX_ADVMSS, &tmp->advmss);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid advmss, ignoring: %m");
- return 0;
- }
-
- r = sd_netlink_message_exit_container(message);
- if (r < 0) {
- log_link_error_errno(link, r, "rtnl: Could not exit from RTA_METRICS container, ignoring: %m");
- return 0;
- }
- }
r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta_multipath);
if (r < 0 && r != -ENODATA) {
}
int network_add_ipv4ll_route(Network *network) {
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
unsigned section_line;
int r;
return r;
/* IPv4LLRoute= is in [Network] section. */
- r = route_new_static(network, network->filename, section_line, &n);
+ r = route_new_static(network, network->filename, section_line, &route);
if (r < 0)
return r;
- r = in_addr_from_string(AF_INET, "169.254.0.0", &n->dst);
+ r = in_addr_from_string(AF_INET, "169.254.0.0", &route->dst);
if (r < 0)
return r;
- n->family = AF_INET;
- n->dst_prefixlen = 16;
- n->scope = RT_SCOPE_LINK;
- n->scope_set = true;
- n->table_set = true;
- n->priority = IPV4LL_ROUTE_METRIC;
- n->protocol = RTPROT_STATIC;
+ route->family = AF_INET;
+ route->dst_prefixlen = 16;
+ route->scope = RT_SCOPE_LINK;
+ route->scope_set = true;
+ route->table_set = true;
+ route->priority = IPV4LL_ROUTE_METRIC;
+ route->protocol = RTPROT_STATIC;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
int network_add_default_route_on_device(Network *network) {
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
unsigned section_line;
int r;
return r;
/* DefaultRouteOnDevice= is in [Network] section. */
- r = route_new_static(network, network->filename, section_line, &n);
+ r = route_new_static(network, network->filename, section_line, &route);
if (r < 0)
return r;
- n->family = AF_INET;
- n->scope = RT_SCOPE_LINK;
- n->scope_set = true;
- n->protocol = RTPROT_STATIC;
+ route->family = AF_INET;
+ route->scope = RT_SCOPE_LINK;
+ route->scope_set = true;
+ route->protocol = RTPROT_STATIC;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
if (streq(section, "Network")) {
/* we are not in an Route section, so use line number instead */
- r = route_new_static(network, filename, line, &n);
+ r = route_new_static(network, filename, line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
} else {
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
}
if (isempty(rvalue)) {
- n->gateway_from_dhcp_or_ra = false;
- n->gw_family = AF_UNSPEC;
- n->gw = IN_ADDR_NULL;
- TAKE_PTR(n);
+ route->gateway_from_dhcp_or_ra = false;
+ route->gw_family = AF_UNSPEC;
+ route->gw = IN_ADDR_NULL;
+ TAKE_PTR(route);
return 0;
}
if (streq(rvalue, "_dhcp")) {
- n->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(n);
+ route->gateway_from_dhcp_or_ra = true;
+ TAKE_PTR(route);
return 0;
}
if (streq(rvalue, "_dhcp4")) {
- n->gw_family = AF_INET;
- n->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(n);
+ route->gw_family = AF_INET;
+ route->gateway_from_dhcp_or_ra = true;
+ TAKE_PTR(route);
return 0;
}
if (streq(rvalue, "_ipv6ra")) {
- n->gw_family = AF_INET6;
- n->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(n);
+ route->gw_family = AF_INET6;
+ route->gateway_from_dhcp_or_ra = true;
+ TAKE_PTR(route);
return 0;
}
}
- r = in_addr_from_string_auto(rvalue, &n->gw_family, &n->gw);
+ r = in_addr_from_string_auto(rvalue, &route->gw_family, &route->gw);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
return 0;
}
- n->gateway_from_dhcp_or_ra = false;
- TAKE_PTR(n);
+ route->gateway_from_dhcp_or_ra = false;
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- if (n->family == AF_UNSPEC)
- r = in_addr_from_string_auto(rvalue, &n->family, &n->prefsrc);
+ if (route->family == AF_UNSPEC)
+ r = in_addr_from_string_auto(rvalue, &route->family, &route->prefsrc);
else
- r = in_addr_from_string(n->family, rvalue, &n->prefsrc);
+ r = in_addr_from_string(route->family, rvalue, &route->prefsrc);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
"Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
return 0;
}
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
union in_addr_union *buffer;
unsigned char *prefixlen;
int r;
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
}
if (streq(lvalue, "Destination")) {
- buffer = &n->dst;
- prefixlen = &n->dst_prefixlen;
+ buffer = &route->dst;
+ prefixlen = &route->dst_prefixlen;
} else if (streq(lvalue, "Source")) {
- buffer = &n->src;
- prefixlen = &n->src_prefixlen;
+ buffer = &route->src;
+ prefixlen = &route->src_prefixlen;
} else
assert_not_reached();
- if (n->family == AF_UNSPEC)
- r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen);
+ if (route->family == AF_UNSPEC)
+ r = in_addr_prefix_from_string_auto(rvalue, &route->family, buffer, prefixlen);
else
- r = in_addr_prefix_from_string(rvalue, n->family, buffer, prefixlen);
+ r = in_addr_prefix_from_string(rvalue, route->family, buffer, prefixlen);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
"Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
return 0;
}
- (void) in_addr_mask(n->family, buffer, *prefixlen);
+ (void) in_addr_mask(route->family, buffer, *prefixlen);
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- r = safe_atou32(rvalue, &n->priority);
+ r = safe_atou32(rvalue, &route->priority);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Could not parse route priority \"%s\", ignoring assignment: %m", rvalue);
return 0;
}
- n->priority_set = true;
- TAKE_PTR(n);
+ route->priority_set = true;
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- n->scope = r;
- n->scope_set = true;
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_nexthop(
- 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;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- uint32_t id;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->nexthop_id = 0;
- TAKE_PTR(n);
- return 0;
- }
-
- r = safe_atou32(rvalue, &id);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue);
- return 0;
- }
- if (id == 0) {
- log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue);
- return 0;
- }
-
- n->nexthop_id = id;
- TAKE_PTR(n);
+ route->scope = r;
+ route->scope_set = true;
+ TAKE_PTR(route);
return 0;
}
void *data,
void *userdata) {
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
Network *network = userdata;
int r;
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- r = manager_get_route_table_from_string(network->manager, rvalue, &n->table);
+ r = manager_get_route_table_from_string(network->manager, rvalue, &route->table);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Could not parse route table \"%s\", ignoring assignment: %m", rvalue);
return 0;
}
- n->table_set = true;
- TAKE_PTR(n);
+ route->table_set = true;
+ TAKE_PTR(route);
return 0;
}
-int config_parse_route_boolean(
+int config_parse_route_gateway_onlink(
const char *unit,
const char *filename,
unsigned line,
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- if (STR_IN_SET(lvalue, "GatewayOnLink", "GatewayOnlink"))
- n->gateway_onlink = r;
- else if (streq(lvalue, "QuickAck"))
- n->quickack = r;
- else if (streq(lvalue, "FastOpenNoCookie"))
- n->fast_open_no_cookie = r;
- else
- assert_not_reached();
+ route->gateway_onlink = r;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
}
if (streq(rvalue, "low"))
- n->pref = ICMPV6_ROUTER_PREF_LOW;
+ route->pref = ICMPV6_ROUTER_PREF_LOW;
else if (streq(rvalue, "medium"))
- n->pref = ICMPV6_ROUTER_PREF_MEDIUM;
+ route->pref = ICMPV6_ROUTER_PREF_MEDIUM;
else if (streq(rvalue, "high"))
- n->pref = ICMPV6_ROUTER_PREF_HIGH;
+ route->pref = ICMPV6_ROUTER_PREF_HIGH;
else {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown route preference: %s", rvalue);
return 0;
}
- n->pref_set = true;
- TAKE_PTR(n);
+ route->pref_set = true;
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- n->protocol = r;
+ route->protocol = r;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int t, r;
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- n->type = (unsigned char) t;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_hop_limit(
- 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) {
-
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- Network *network = userdata;
- uint32_t k;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->hop_limit = 0;
- TAKE_PTR(n);
- return 0;
- }
-
- r = safe_atou32(rvalue, &k);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Could not parse per route hop limit, ignoring assignment: %s", rvalue);
- return 0;
- }
- if (k > 255) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Specified per route hop limit \"%s\" is too large, ignoring assignment: %m", rvalue);
- return 0;
- }
- if (k == 0) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid per route hop limit \"%s\", ignoring assignment: %m", rvalue);
- return 0;
- }
-
- n->hop_limit = k;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_tcp_congestion(
- 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;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype,
- rvalue, &n->tcp_congestion_control_algo, userdata);
- if (r < 0)
- return r;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_tcp_advmss(
- 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) {
-
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- Network *network = userdata;
- uint64_t u;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->advmss = 0;
- TAKE_PTR(n);
- return 0;
- }
-
- r = parse_size(rvalue, 1024, &u);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Could not parse TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue);
- return 0;
- }
-
- if (u == 0 || u > UINT32_MAX) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue);
- return 0;
- }
-
- n->advmss = u;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_tcp_window(
- 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) {
-
- uint32_t *window = ASSERT_PTR(data);
- uint32_t k;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = safe_atou32(rvalue, &k);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Could not parse TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
- if (k >= 1024) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Specified TCP %s \"%s\" is too large, ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
- if (k == 0) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
-
- *window = k;
- return 0;
-}
-
-int config_parse_route_tcp_window(
- 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) {
-
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- Network *network = userdata;
- uint32_t *d;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (streq(lvalue, "InitialCongestionWindow"))
- d = &n->initcwnd;
- else if (streq(lvalue, "InitialAdvertisedReceiveWindow"))
- d = &n->initrwnd;
- else
- assert_not_reached();
-
- r = config_parse_tcp_window(unit, filename, line, section, section_line, lvalue, ltype, rvalue, d, userdata);
- if (r < 0)
- return r;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_mtu(
- 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;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- r = config_parse_mtu(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->mtu, userdata);
- if (r < 0)
- return r;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_tcp_rto(
- 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;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- usec_t usec;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- r = parse_sec(rvalue, &usec);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to parse route TCP retransmission timeout (RTO), ignoring assignment: %s", rvalue);
- return 0;
- }
-
- if (!timestamp_is_set(usec) ||
- DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Route TCP retransmission timeout (RTO) must be in the range 0…%"PRIu32"ms, ignoring assignment: %s", UINT32_MAX, rvalue);
- return 0;
- }
-
- n->tcp_rto_usec = usec;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_multipath_route(
- 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) {
-
- _cleanup_(multipath_route_freep) MultipathRoute *m = NULL;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- _cleanup_free_ char *word = NULL;
- Network *network = userdata;
- union in_addr_union a;
- int family, r;
- const char *p;
- char *dev;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->multipath_routes = ordered_set_free_with_destructor(n->multipath_routes, multipath_route_free);
- return 0;
- }
-
- m = new0(MultipathRoute, 1);
- if (!m)
- return log_oom();
-
- p = rvalue;
- r = extract_first_word(&p, &word, NULL, 0);
- if (r == -ENOMEM)
- return log_oom();
- if (r <= 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid multipath route option, ignoring assignment: %s", rvalue);
- return 0;
- }
-
- dev = strchr(word, '@');
- if (dev) {
- *dev++ = '\0';
-
- r = parse_ifindex(dev);
- if (r > 0)
- m->ifindex = r;
- else {
- if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue);
- return 0;
- }
-
- m->ifname = strdup(dev);
- if (!m->ifname)
- return log_oom();
- }
- }
-
- r = in_addr_from_string_auto(word, &family, &a);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue);
- return 0;
- }
- m->gateway.address = a;
- m->gateway.family = family;
-
- if (!isempty(p)) {
- r = safe_atou32(p, &m->weight);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid multipath route weight, ignoring assignment: %s", p);
- return 0;
- }
- /* ip command takes weight in the range 1…255, while kernel takes the value in the
- * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip
- * command uses, then networkd decreases by one and stores it to match the range which
- * kernel uses. */
- if (m->weight == 0 || m->weight > 256) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid multipath route weight, ignoring assignment: %s", p);
- return 0;
- }
- m->weight--;
- }
-
- r = ordered_set_ensure_put(&n->multipath_routes, NULL, m);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to store multipath route, ignoring assignment: %m");
- return 0;
- }
+ route->type = (unsigned char) t;
- TAKE_PTR(m);
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
#include "conf-parser.h"
#include "in-addr-util.h"
#include "networkd-link.h"
+#include "networkd-route-metric.h"
+#include "networkd-route-nexthop.h"
#include "networkd-util.h"
typedef struct Manager Manager;
int family;
int gw_family;
uint32_t gw_weight;
- int quickack;
- int fast_open_no_cookie;
unsigned char dst_prefixlen;
unsigned char src_prefixlen;
unsigned char tos;
uint32_t priority; /* note that ip(8) calls this 'metric' */
uint32_t table;
- uint32_t mtu;
- uint32_t initcwnd;
- uint32_t initrwnd;
- uint32_t advmss;
- uint32_t hop_limit;
- char *tcp_congestion_control_algo;
unsigned char pref;
unsigned flags;
int gateway_onlink; /* Only used in conf parser and route_section_verify(). */
uint32_t nexthop_id;
- usec_t tcp_rto_usec;
+
+ /* metrics (RTA_METRICS) */
+ RouteMetric metric;
bool scope_set:1;
bool table_set:1;
extern const struct hash_ops route_hash_ops;
int route_new(Route **ret);
+int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret);
Route *route_free(Route *route);
DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_free);
int route_dup(const Route *src, Route **ret);
CONFIG_PARSER_PROTOTYPE(config_parse_route_priority);
CONFIG_PARSER_PROTOTYPE(config_parse_route_scope);
CONFIG_PARSER_PROTOTYPE(config_parse_route_table);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_boolean);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_gateway_onlink);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_route_preference);
CONFIG_PARSER_PROTOTYPE(config_parse_route_protocol);
CONFIG_PARSER_PROTOTYPE(config_parse_route_type);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_window);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_hop_limit);
-CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_rto);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu);
-CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route);
-CONFIG_PARSER_PROTOTYPE(config_parse_tcp_congestion);
-CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop);
if (r < 0)
return log_oom();
- r = parse_ip_port_range(rvalue, &low, &high);
+ r = parse_ip_port_range(rvalue, &low, &high, /* allow_zero = */ false);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse routing policy rule port range '%s'", rvalue);
return 0;
if (!c || c->n_data == 0)
return 0;
- r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ r = make_run_host(root);
if (r < 0)
- return log_error_errno(r, "Failed to create /run/host: %m");
+ return r;
r = userns_mkdir(root, "/run/host/home", 0755, 0, 0);
if (r < 0)
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#if HAVE_BLKID
-#endif
#include <errno.h>
#include <getopt.h>
#include <linux/fs.h>
#include "umask-util.h"
#include "unit-name.h"
#include "user-util.h"
+#include "vpick.h"
/* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */
#define NSPAWN_NOTIFY_SOCKET_PATH "/run/host/notify"
_cleanup_free_ void *k = NULL;
size_t l;
- r = unhexmem(optarg, strlen(optarg), &k, &l);
+ r = unhexmem(optarg, &k, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash: %s", optarg);
if (l < sizeof(sd_id128_t))
void *p;
if ((value = startswith(optarg, "base64:"))) {
- r = unbase64mem(value, strlen(value), &p, &l);
+ r = unbase64mem(value, &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
return 0;
}
+int make_run_host(const char *root) {
+ int r;
+
+ assert(root);
+
+ r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create /run/host/: %m");
+
+ return 0;
+}
+
static int setup_credentials(const char *root) {
const char *q;
int r;
if (arg_credentials.n_credentials == 0)
return 0;
- r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ r = make_run_host(root);
if (r < 0)
- return log_error_errno(r, "Failed to create /run/host: %m");
+ return r;
r = userns_mkdir(root, "/run/host/credentials", 0700, 0, 0);
if (r < 0)
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
(void) mkdir_p(p, 0600);
- r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ r = make_run_host(root);
if (r < 0)
- return log_error_errno(r, "Failed to create /run/host: %m");
+ return r;
r = userns_mkdir(root, NSPAWN_MOUNT_TUNNEL, 0600, 0, 0);
if (r < 0)
return 0;
}
+static int pick_paths(void) {
+ int r;
+
+ if (arg_directory) {
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ PickFilter filter = pick_filter_image_dir;
+
+ filter.architecture = arg_architecture;
+
+ r = path_pick_update_warn(
+ &arg_directory,
+ &filter,
+ PICK_ARCHITECTURE|PICK_TRIES,
+ &result);
+ if (r < 0) {
+ /* Accept ENOENT here so that the --template= logic can work */
+ if (r != -ENOENT)
+ return r;
+ } else
+ arg_architecture = result.architecture;
+ }
+
+ if (arg_image) {
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ PickFilter filter = pick_filter_image_raw;
+
+ filter.architecture = arg_architecture;
+
+ r = path_pick_update_warn(
+ &arg_image,
+ &filter,
+ PICK_ARCHITECTURE|PICK_TRIES,
+ &result);
+ if (r < 0)
+ return r;
+
+ arg_architecture = result.architecture;
+ }
+
+ if (arg_template) {
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ PickFilter filter = pick_filter_image_dir;
+
+ filter.architecture = arg_architecture;
+
+ r = path_pick_update_warn(
+ &arg_template,
+ &filter,
+ PICK_ARCHITECTURE,
+ &result);
+ if (r < 0)
+ return r;
+
+ arg_architecture = result.architecture;
+ }
+
+ return 0;
+}
+
static int determine_names(void) {
int r;
if (arg_template && !arg_directory && arg_machine) {
- /* If --template= was specified then we should not
- * search for a machine, but instead create a new one
- * in /var/lib/machine. */
+ /* If --template= was specified then we should not search for a machine, but instead create a
+ * new one in /var/lib/machine. */
arg_directory = path_join("/var/lib/machines", arg_machine);
if (!arg_directory)
(void) mkdir_parents(NSPAWN_NOTIFY_SOCKET_PATH, 0755);
(void) sockaddr_un_unlink(&sa.un);
- r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
- if (r < 0)
- return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m");
+ WITH_UMASK(0577) { /* only set "w" bit, which is all that's necessary for connecting from the container */
+ r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0)
+ return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m");
+ }
r = userns_lchown(NSPAWN_NOTIFY_SOCKET_PATH, 0, 0);
if (r < 0)
/* The same stuff as the $container env var, but nicely readable for the entire payload */
p = prefix_roota(directory, "/run/host/container-manager");
- (void) write_string_file(p, arg_container_service_name, WRITE_STRING_FILE_CREATE);
+ (void) write_string_file(p, arg_container_service_name, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0444);
/* The same stuff as the $container_uuid env var */
p = prefix_roota(directory, "/run/host/container-uuid");
- (void) write_string_filef(p, WRITE_STRING_FILE_CREATE, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid));
+ (void) write_string_filef(p, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0444, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid));
if (!arg_use_cgns) {
r = mount_cgroups(
if (r < 0)
goto finish;
+ r = pick_paths();
+ if (r < 0)
+ goto finish;
+
r = determine_names();
if (r < 0)
goto finish;
int userns_lchown(const char *p, uid_t uid, gid_t gid);
int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid);
+int make_run_host(const char *root);
#include "strv.h"
#include "varlink.h"
-static JsonDispatchFlags json_dispatch_flags = 0;
+static JsonDispatchFlags json_dispatch_flags = JSON_ALLOW_EXTENSIONS;
static void setup_logging(void) {
log_parse_environment_variables();
free(message->property);
}
-static int managed_oom_mode(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
- ManagedOOMMode *mode = userdata, m;
- const char *s;
-
- assert(mode);
- assert_se(s = json_variant_string(v));
-
- m = managed_oom_mode_from_string(s);
- if (m < 0)
- return json_log(v, flags, m, "%s is not a valid ManagedOOMMode", s);
-
- *mode = m;
- return 0;
-}
+static JSON_DISPATCH_ENUM_DEFINE(dispatch_managed_oom_mode, ManagedOOMMode, managed_oom_mode_from_string);
static int process_managed_oom_message(Manager *m, uid_t uid, JsonVariant *parameters) {
JsonVariant *c, *cgroups;
int r;
static const JsonDispatch dispatch_table[] = {
- { "mode", JSON_VARIANT_STRING, managed_oom_mode, offsetof(ManagedOOMMessage, mode), JSON_MANDATORY },
- { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, path), JSON_MANDATORY },
- { "property", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, property), JSON_MANDATORY },
- { "limit", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 },
+ { "mode", JSON_VARIANT_STRING, dispatch_managed_oom_mode, offsetof(ManagedOOMMessage, mode), JSON_MANDATORY },
+ { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, path), JSON_MANDATORY },
+ { "property", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, property), JSON_MANDATORY },
+ { "limit", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 },
{},
};
sd_event_source_unref(m->mem_pressure_context_event_source);
sd_event_unref(m->event);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
hashmap_free(m->monitored_swap_cgroup_contexts);
static EmptyMode arg_empty = EMPTY_UNSET;
static bool arg_dry_run = true;
-static const char *arg_node = NULL;
+static char *arg_node = NULL;
static char *arg_root = NULL;
static char *arg_image = NULL;
static char **arg_definitions = NULL;
static char *arg_copy_source = NULL;
static char *arg_make_ddi = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
}
-static void context_bar_char_process_partition(
+static int context_bar_char_process_partition(
Context *context,
Partition *bar[],
size_t n,
Partition *p,
- size_t *ret_start) {
+ size_t **start_array,
+ size_t *n_start_array) {
uint64_t from, to, total;
size_t x, y;
assert(bar);
assert(n > 0);
assert(p);
+ assert(start_array);
+ assert(n_start_array);
if (p->dropped)
- return;
+ return 0;
assert(p->offset != UINT64_MAX);
assert(p->new_size != UINT64_MAX);
for (size_t i = x; i < y; i++)
bar[i] = p;
- *ret_start = x;
+ if (!GREEDY_REALLOC_APPEND(*start_array, *n_start_array, &x, 1))
+ return log_oom();
+
+ return 1;
}
static int partition_hint(const Partition *p, const char *node, char **ret) {
static int context_dump_partition_bar(Context *context) {
_cleanup_free_ Partition **bar = NULL;
_cleanup_free_ size_t *start_array = NULL;
+ size_t n_start_array = 0;
Partition *last = NULL;
bool z = false;
size_t c, j = 0;
+ int r;
assert_se((c = columns()) >= 2);
c -= 2; /* We do not use the leftmost and rightmost character cell */
if (!bar)
return log_oom();
- start_array = new(size_t, context->n_partitions);
- if (!start_array)
- return log_oom();
-
- LIST_FOREACH(partitions, p, context->partitions)
- context_bar_char_process_partition(context, bar, c, p, start_array + j++);
+ LIST_FOREACH(partitions, p, context->partitions) {
+ r = context_bar_char_process_partition(context, bar, c, p, &start_array, &n_start_array);
+ if (r < 0)
+ return r;
+ }
putc(' ', stdout);
fputs(ansi_normal(), stdout);
putc('\n', stdout);
- for (size_t i = 0; i < context->n_partitions; i++) {
+ for (size_t i = 0; i < n_start_array; i++) {
_cleanup_free_ char **line = NULL;
line = new0(char*, c);
j = 0;
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_free_ char *d = NULL;
+
+ if (p->dropped)
+ continue;
+
j++;
- if (i < context->n_partitions - j) {
+ if (i < n_start_array - j) {
if (line[start_array[j-1]]) {
const char *e;
return log_oom();
}
- } else if (i == context->n_partitions - j) {
+ } else if (i == n_start_array - j) {
_cleanup_free_ char *hint = NULL;
(void) partition_hint(p, context->node, &hint);
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) {
#if HAVE_TPM2
+ _cleanup_(iovec_done) struct iovec pubkey = {}, blob = {}, srk = {};
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- _cleanup_(erase_and_freep) void *secret = NULL;
- _cleanup_free_ void *pubkey = NULL;
- _cleanup_free_ void *blob = NULL, *srk_buf = NULL;
- size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0;
ssize_t base64_encoded_size;
int keyslot;
TPM2Flags flags = 0;
if (arg_tpm2_public_key_pcr_mask != 0) {
- r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (arg_tpm2_public_key || r != -ENOENT)
return log_error_errno(r, "Failed to read TPM PCR public key: %m");
}
TPM2B_PUBLIC public;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ if (iovec_is_set(&pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
}
r = tpm2_calculate_sealing_policy(
arg_tpm2_hash_pcr_values,
arg_tpm2_n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false,
arg_tpm2_pcrlock ? &pcrlock_policy : NULL,
&policy);
arg_tpm2_seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
- /* secret= */ NULL, /* secret_size= */ 0,
+ /* secret= */ NULL,
&policy,
/* pin= */ NULL,
- &secret, &secret_size,
- &blob, &blob_size,
- &srk_buf, &srk_buf_size);
+ &secret,
+ &blob,
+ &srk);
else
r = tpm2_seal(tpm2_context,
arg_tpm2_seal_key_handle,
&policy,
/* pin= */ NULL,
- &secret, &secret_size,
- &blob, &blob_size,
+ &secret,
+ &blob,
/* ret_primary_alg= */ NULL,
- &srk_buf, &srk_buf_size);
+ &srk);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
- base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
keyslot,
hash_pcr_mask,
hash_pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
arg_tpm2_public_key_pcr_mask,
/* primary_alg= */ 0,
- blob, blob_size,
- policy.buffer, policy.size,
- NULL, 0, /* no salt because tpm2_seal has no pin */
- srk_buf, srk_buf_size,
+ &blob,
+ &IOVEC_MAKE(policy.buffer, policy.size),
+ /* salt= */ NULL, /* no salt because tpm2_seal has no pin */
+ &srk,
flags,
&v);
if (r < 0)
return log_oom();
}
- arg_node = argc > optind ? argv[optind] : NULL;
+ if (argc > optind) {
+ arg_node = strdup(argv[optind]);
+ if (!arg_node)
+ return log_oom();
+ }
if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node && !arg_image)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
#define PCRLOCK_FIRMWARE_CONFIG_LATE_PATH "/var/lib/pcrlock.d/550-firmware-config-late.pcrlock.d/generated.pcrlock"
#define PCRLOCK_GPT_PATH "/var/lib/pcrlock.d/600-gpt.pcrlock.d/generated.pcrlock"
#define PCRLOCK_SECUREBOOT_AUTHORITY_PATH "/var/lib/pcrlock.d/620-secureboot-authority.pcrlock.d/generated.pcrlock"
-#define PCRLOCK_KERNEL_CMDLINE_PATH "/var/lib/pcrlock.d/710-kernel-cmdline.pcrlock/generated.pcrlock"
-#define PCRLOCK_KERNEL_INITRD_PATH "/var/lib/pcrlock.d/720-kernel-initrd.pcrlock/generated.pcrlock"
+#define PCRLOCK_KERNEL_CMDLINE_PATH "/var/lib/pcrlock.d/710-kernel-cmdline.pcrlock.d/generated.pcrlock"
+#define PCRLOCK_KERNEL_INITRD_PATH "/var/lib/pcrlock.d/720-kernel-initrd.pcrlock.d/generated.pcrlock"
#define PCRLOCK_MACHINE_ID_PATH "/var/lib/pcrlock.d/820-machine-id.pcrlock"
#define PCRLOCK_ROOT_FILE_SYSTEM_PATH "/var/lib/pcrlock.d/830-root-file-system.pcrlock"
#define PCRLOCK_FILE_SYSTEM_PATH_PREFIX "/var/lib/pcrlock.d/840-file-system-"
* policies).
*
* Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a
- * PolicyAuthorizeNV epxression that pins the NV index in the policy, and permits access to any
+ * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any
* policies matching the current NV index contents.
*
* We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we
return r;
}
- log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%" PRIu32 ".", path, nv_index);
+ log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index);
log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
sd_event_source_unref(m->image_cache_defer_event);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
sd_event_unref(m->event);
return !m->operations;
}
-static int manager_run(Manager *m) {
- assert(m);
-
- return bus_event_loop_with_idle(
- m->event,
- m->bus,
- "org.freedesktop.portable1",
- DEFAULT_EXIT_USEC,
- check_idle, m);
-}
-
static int run(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
if (r < 0)
return log_error_errno(r, "Failed to fully start up daemon: %m");
- log_debug("systemd-portabled running as pid " PID_FMT, getpid_cached());
r = sd_notify(false, NOTIFY_READY);
if (r < 0)
log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
- r = manager_run(m);
+ r = bus_event_loop_with_idle(
+ m->event,
+ m->bus,
+ "org.freedesktop.portable1",
+ DEFAULT_EXIT_USEC,
+ check_idle, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run main loop: %m");
- (void) sd_notify(false, NOTIFY_STOPPING);
- log_debug("systemd-portabled stopped as pid " PID_FMT, getpid_cached());
- return r;
+ return 0;
}
DEFINE_MAIN_FUNCTION(run);
],
'include_directories' : resolve_includes,
},
+ test_template + {
+ 'sources' : [
+ files('test-resolved-dummy-server.c'),
+ basic_dns_sources,
+ systemd_resolved_sources,
+ ],
+ 'dependencies' : [
+ lib_openssl_or_gcrypt,
+ libm,
+ systemd_resolved_dependencies,
+ ],
+ 'include_directories' : resolve_includes,
+ 'type' : 'manual',
+ },
resolve_fuzz_template + {
'sources' : files('fuzz-dns-packet.c'),
},
static void monitor_query_dump(JsonVariant *v) {
_cleanup_(json_variant_unrefp) JsonVariant *question = NULL, *answer = NULL, *collected_questions = NULL;
- int rcode = -1, error = 0, r;
- const char *state = NULL;
+ int rcode = -1, error = 0, ede_code = -1;
+ const char *state = NULL, *result = NULL, *ede_msg = NULL;
assert(v);
JsonDispatch dispatch_table[] = {
- { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
- { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
- { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
- { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
- { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
- { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
+ { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
+ { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
+ { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
+ { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
+ { "result", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&result), 0 },
+ { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
+ { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
+ { "extendedDNSErrorCode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&ede_code), 0 },
+ { "extendedDNSErrorMessage", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&ede_msg), 0 },
{}
};
- r = json_dispatch(v, dispatch_table, 0, NULL);
- if (r < 0)
- return (void) log_warning("Received malformed monitor message, ignoring.");
+ if (json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, NULL) < 0)
+ return;
/* First show the current question */
print_question('Q', ansi_highlight_cyan(), question);
/* And then show the questions that led to this one in case this was a CNAME chain */
print_question('C', ansi_highlight_grey(), collected_questions);
- printf("%s%s S%s: %s\n",
+ printf("%s%s S%s: %s",
streq_ptr(state, "success") ? ansi_highlight_green() : ansi_highlight_red(),
special_glyph(SPECIAL_GLYPH_ARROW_LEFT),
ansi_normal(),
streq_ptr(state, "rcode-failure") ? dns_rcode_to_string(rcode) :
state));
+ if (!isempty(result))
+ printf(": %s", result);
+
+ if (ede_code >= 0)
+ printf(" (%s%s%s)",
+ FORMAT_DNS_EDE_RCODE(ede_code),
+ !isempty(ede_msg) ? ": " : "",
+ strempty(ede_msg));
+
+ puts("");
+
print_answer(answer);
}
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
int r, c = 0;
- r = json_dispatch(item, dispatch_table, JSON_LOG, &item_info);
+ r = json_dispatch(item, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &item_info);
if (r < 0)
return r;
{},
};
- r = json_dispatch(scope, dispatch_table, JSON_LOG, &scope_info);
+ r = json_dispatch(scope, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &scope_info);
if (r < 0)
return r;
{},
};
- r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_PERMISSIVE, &server_state);
+ r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &server_state);
if (r < 0)
return r;
return reply_method_errorf(q, BUS_ERROR_ABORTED, "Query aborted");
case DNS_TRANSACTION_DNSSEC_FAILED:
- return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s",
- dnssec_result_to_string(q->answer_dnssec_result));
+ return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s%s%s%s%s%s",
+ dnssec_result_to_string(q->answer_dnssec_result),
+ q->answer_ede_rcode >= 0 ? " (" : "",
+ q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
+ (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
+ q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
+ q->answer_ede_rcode >= 0 ? ")" : "");
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return reply_method_errorf(q, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known");
rc = FORMAT_DNS_RCODE(q->answer_rcode);
n = strjoina(_BUS_ERROR_DNS, rc);
- sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc);
+ sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error: %s%s%s%s%s%s",
+ dns_query_string(q), rc,
+ q->answer_ede_rcode >= 0 ? " (" : "",
+ q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
+ (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
+ q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
+ q->answer_ede_rcode >= 0 ? ")" : "");
}
return sd_bus_reply_method_error(req, &error);
case DNS_TXT_ITEM_DATA:
if (value) {
- r = unbase64mem(value, strlen(value), &decoded, &length);
+ r = unbase64mem(value, &decoded, &length);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
+ [DNSSEC_UPSTREAM_FAILURE] = "upstream-failure",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
- /* These two are added by the DnsTransaction logic */
+ /* These five are added by the DnsTransaction logic */
DNSSEC_UNSIGNED,
DNSSEC_FAILED_AUXILIARY,
DNSSEC_NSEC_MISMATCH,
DNSSEC_INCOMPATIBLE_SERVER,
+ DNSSEC_UPSTREAM_FAILURE,
_DNSSEC_RESULT_MAX,
_DNSSEC_RESULT_INVALID = -EINVAL,
#include "alloc-util.h"
#include "dns-domain.h"
+#include "escape.h"
#include "memory-util.h"
#include "resolved-dns-packet.h"
#include "set.h"
switch (p->protocol) {
- case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_DNS:
- if (DNS_PACKET_TC(p)) /* mDNS query may have truncation flag. */
+ if (DNS_PACKET_TC(p))
+ return -EBADMSG;
+
+ if (DNS_PACKET_QDCOUNT(p) != 1)
+ return -EBADMSG;
+
+ if (DNS_PACKET_ANCOUNT(p) > 0)
+ return -EBADMSG;
+
+ /* Note, in most cases, DNS query packet does not have authority section. But some query
+ * types, e.g. IXFR, have Authority sections. Hence, unlike the check for LLMNR, we do not
+ * check DNS_PACKET_NSCOUNT(p) here. */
+ break;
+
+ case DNS_PROTOCOL_LLMNR:
+ if (DNS_PACKET_TC(p))
return -EBADMSG;
/* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
break;
case DNS_PROTOCOL_MDNS:
+ /* Note, mDNS query may have truncation flag. So, unlike the check for DNS and LLMNR,
+ * we do not check DNS_PACKET_TC(p) here. */
+
/* RFC 6762, Section 18 specifies that messages with non-zero RCODE
* must be silently ignored, and that we must ignore the values of
* AA, RD, RA, AD, and CD bits. */
static const uint8_t rfc6975[] = {
- 0, 5, /* OPTION_CODE: DAU */
+ 0, DNS_EDNS_OPT_DAU, /* OPTION_CODE */
#if PREFER_OPENSSL || (HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600)
0, 7, /* LIST_LENGTH */
#else
DNSSEC_ALGORITHM_ED25519,
#endif
- 0, 6, /* OPTION_CODE: DHU */
+ 0, DNS_EDNS_OPT_DHU, /* OPTION_CODE */
0, 3, /* LIST_LENGTH */
DNSSEC_DIGEST_SHA1,
DNSSEC_DIGEST_SHA256,
DNSSEC_DIGEST_SHA384,
- 0, 7, /* OPTION_CODE: N3U */
+ 0, DNS_EDNS_OPT_N3U, /* OPTION_CODE */
0, 1, /* LIST_LENGTH */
NSEC3_ALGORITHM_SHA1,
};
return false;
/* RFC 6975 DAU, DHU or N3U fields found. */
- if (IN_SET(option_code, 5, 6, 7))
+ if (IN_SET(option_code, DNS_EDNS_OPT_DAU, DNS_EDNS_OPT_DHU, DNS_EDNS_OPT_N3U))
found_dau_dhu_n3u = true;
p += option_length + 4U;
return dns_packet_compare_func(a, b) == 0;
}
+int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg) {
+ const uint8_t *d;
+ size_t l;
+ int r;
+
+ assert(p);
+
+ if (!p->opt)
+ return -ENOENT;
+
+ d = p->opt->opt.data;
+ l = p->opt->opt.data_size;
+
+ while (l > 0) {
+ uint16_t code, length;
+
+ if (l < 4U)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "EDNS0 variable part has invalid size.");
+
+ code = unaligned_read_be16(d);
+ length = unaligned_read_be16(d + 2);
+
+ if (l < 4U + length)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "Truncated option in EDNS0 variable part.");
+
+ if (code == DNS_EDNS_OPT_EXT_ERROR) {
+ _cleanup_free_ char *msg = NULL;
+
+ if (length < 2U)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "EDNS0 truncated EDE info code.");
+
+ r = make_cstring((char *) d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg);
+ if (r < 0)
+ return log_debug_errno(r, "Invalid EDE text in opt.");
+
+ if (ret_ede_msg) {
+ if (!utf8_is_valid(msg)) {
+ _cleanup_free_ char *msg_escaped = NULL;
+
+ msg_escaped = cescape(msg);
+ if (!msg_escaped)
+ return log_oom_debug();
+
+ *ret_ede_msg = TAKE_PTR(msg_escaped);
+ } else
+ *ret_ede_msg = TAKE_PTR(msg);
+ }
+
+ if (ret_ede_rcode)
+ *ret_ede_rcode = unaligned_read_be16(d + 4);
+
+ return 0;
+ }
+
+ d += 4U + length;
+ l -= 4U + length;
+ }
+
+ return -ENOENT;
+}
+
+bool dns_ede_rcode_is_dnssec(int ede_rcode) {
+ return IN_SET(ede_rcode,
+ DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG,
+ DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST,
+ DNS_EDE_RCODE_DNSSEC_INDETERMINATE,
+ DNS_EDE_RCODE_DNSSEC_BOGUS,
+ DNS_EDE_RCODE_SIG_EXPIRED,
+ DNS_EDE_RCODE_SIG_NOT_YET_VALID,
+ DNS_EDE_RCODE_DNSKEY_MISSING,
+ DNS_EDE_RCODE_RRSIG_MISSING,
+ DNS_EDE_RCODE_NO_ZONE_KEY_BIT,
+ DNS_EDE_RCODE_NSEC_MISSING
+ );
+}
+
int dns_packet_has_nsid_request(DnsPacket *p) {
bool has_nsid = false;
const uint8_t *d;
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Truncated option in EDNS0 variable part.");
- if (code == 3) {
+ if (code == DNS_EDNS_OPT_NSID) {
if (has_nsid)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Duplicate NSID option in EDNS0 variable part.");
[DNS_RCODE_NXRRSET] = "NXRRSET",
[DNS_RCODE_NOTAUTH] = "NOTAUTH",
[DNS_RCODE_NOTZONE] = "NOTZONE",
+ [DNS_RCODE_DSOTYPENI] = "DSOTYPENI",
[DNS_RCODE_BADVERS] = "BADVERS",
[DNS_RCODE_BADKEY] = "BADKEY",
[DNS_RCODE_BADTIME] = "BADTIME",
return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i);
}
+static const char* const dns_ede_rcode_table[_DNS_EDE_RCODE_MAX_DEFINED] = {
+ [DNS_EDE_RCODE_OTHER] = "Other",
+ [DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG] = "Unsupported DNSKEY Algorithm",
+ [DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST] = "Unsupported DS Digest Type",
+ [DNS_EDE_RCODE_STALE_ANSWER] = "Stale Answer",
+ [DNS_EDE_RCODE_FORGED_ANSWER] = "Forged Answer",
+ [DNS_EDE_RCODE_DNSSEC_INDETERMINATE] = "DNSSEC Indeterminate",
+ [DNS_EDE_RCODE_DNSSEC_BOGUS] = "DNSSEC Bogus",
+ [DNS_EDE_RCODE_SIG_EXPIRED] = "Signature Expired",
+ [DNS_EDE_RCODE_SIG_NOT_YET_VALID] = "Signature Not Yet Valid",
+ [DNS_EDE_RCODE_DNSKEY_MISSING] = "DNSKEY Missing",
+ [DNS_EDE_RCODE_RRSIG_MISSING] = "RRSIG Missing",
+ [DNS_EDE_RCODE_NO_ZONE_KEY_BIT] = "No Zone Key Bit Set",
+ [DNS_EDE_RCODE_NSEC_MISSING] = "NSEC Missing",
+ [DNS_EDE_RCODE_CACHED_ERROR] = "Cached Error",
+ [DNS_EDE_RCODE_NOT_READY] = "Not Ready",
+ [DNS_EDE_RCODE_BLOCKED] = "Blocked",
+ [DNS_EDE_RCODE_CENSORED] = "Censored",
+ [DNS_EDE_RCODE_FILTERED] = "Filtered",
+ [DNS_EDE_RCODE_PROHIBITIED] = "Prohibited",
+ [DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER] = "Stale NXDOMAIN Answer",
+ [DNS_EDE_RCODE_NOT_AUTHORITATIVE] = "Not Authoritative",
+ [DNS_EDE_RCODE_NOT_SUPPORTED] = "Not Supported",
+ [DNS_EDE_RCODE_UNREACH_AUTHORITY] = "No Reachable Authority",
+ [DNS_EDE_RCODE_NET_ERROR] = "Network Error",
+ [DNS_EDE_RCODE_INVALID_DATA] = "Invalid Data",
+ [DNS_EDE_RCODE_SIG_NEVER] = "Signature Never Valid",
+ [DNS_EDE_RCODE_TOO_EARLY] = "Too Early",
+ [DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER] = "Unsupported NSEC3 Iterations",
+ [DNS_EDE_RCODE_TRANSPORT_POLICY] = "Impossible Transport Policy",
+ [DNS_EDE_RCODE_SYNTHESIZED] = "Synthesized",
+};
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_ede_rcode, int);
+
+const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) {
+ const char *p = dns_ede_rcode_to_string(i);
+ if (p)
+ return p;
+
+ return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i);
+}
+
static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
[DNS_PROTOCOL_DNS] = "dns",
[DNS_PROTOCOL_MDNS] = "mdns",
bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b);
+int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg);
+bool dns_ede_rcode_is_dnssec(int ede_rcode);
int dns_packet_has_nsid_request(DnsPacket *p);
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */
enum {
- DNS_RCODE_SUCCESS = 0,
- DNS_RCODE_FORMERR = 1,
- DNS_RCODE_SERVFAIL = 2,
- DNS_RCODE_NXDOMAIN = 3,
- DNS_RCODE_NOTIMP = 4,
- DNS_RCODE_REFUSED = 5,
- DNS_RCODE_YXDOMAIN = 6,
- DNS_RCODE_YXRRSET = 7,
- DNS_RCODE_NXRRSET = 8,
- DNS_RCODE_NOTAUTH = 9,
- DNS_RCODE_NOTZONE = 10,
- DNS_RCODE_BADVERS = 16,
- DNS_RCODE_BADSIG = 16, /* duplicate value! */
- DNS_RCODE_BADKEY = 17,
- DNS_RCODE_BADTIME = 18,
- DNS_RCODE_BADMODE = 19,
- DNS_RCODE_BADNAME = 20,
- DNS_RCODE_BADALG = 21,
- DNS_RCODE_BADTRUNC = 22,
- DNS_RCODE_BADCOOKIE = 23,
+ DNS_RCODE_SUCCESS = 0,
+ DNS_RCODE_FORMERR = 1,
+ DNS_RCODE_SERVFAIL = 2,
+ DNS_RCODE_NXDOMAIN = 3,
+ DNS_RCODE_NOTIMP = 4,
+ DNS_RCODE_REFUSED = 5,
+ DNS_RCODE_YXDOMAIN = 6,
+ DNS_RCODE_YXRRSET = 7,
+ DNS_RCODE_NXRRSET = 8,
+ DNS_RCODE_NOTAUTH = 9,
+ DNS_RCODE_NOTZONE = 10,
+ DNS_RCODE_DSOTYPENI = 11,
+ /* 12-15 are unassigned. */
+ DNS_RCODE_BADVERS = 16,
+ DNS_RCODE_BADSIG = 16, /* duplicate value! */
+ DNS_RCODE_BADKEY = 17,
+ DNS_RCODE_BADTIME = 18,
+ DNS_RCODE_BADMODE = 19,
+ DNS_RCODE_BADNAME = 20,
+ DNS_RCODE_BADALG = 21,
+ DNS_RCODE_BADTRUNC = 22,
+ DNS_RCODE_BADCOOKIE = 23,
+ /* 24-3840 are unassigned. */
+ /* 3841-4095 are for private use. */
+ /* 4096-65534 are unassigned. */
_DNS_RCODE_MAX_DEFINED,
- _DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */
+ _DNS_RCODE_MAX = 65535, /* reserved */
+ _DNS_RCODE_INVALID = -EINVAL,
+};
+
+/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 */
+enum {
+ DNS_EDNS_OPT_RESERVED = 0, /* RFC 6891 */
+ DNS_EDNS_OPT_LLQ = 1, /* RFC 8764 */
+ DNS_EDNS_OPT_UL = 2,
+ DNS_EDNS_OPT_NSID = 3, /* RFC 5001 */
+ /* DNS_EDNS_OPT_RESERVED = 4 */
+ DNS_EDNS_OPT_DAU = 5, /* RFC 6975 */
+ DNS_EDNS_OPT_DHU = 6, /* RFC 6975 */
+ DNS_EDNS_OPT_N3U = 7, /* RFC 6975 */
+ DNS_EDNS_OPT_CLIENT_SUBNET = 8, /* RFC 7871 */
+ DNS_EDNS_OPT_EXPIRE = 9, /* RFC 7314 */
+ DNS_EDNS_OPT_COOKIE = 10, /* RFC 7873 */
+ DNS_EDNS_OPT_TCP_KEEPALIVE = 11, /* RFC 7828 */
+ DNS_EDNS_OPT_PADDING = 12, /* RFC 7830 */
+ DNS_EDNS_OPT_CHAIN = 13, /* RFC 7901 */
+ DNS_EDNS_OPT_KEY_TAG = 14, /* RFC 8145 */
+ DNS_EDNS_OPT_EXT_ERROR = 15, /* RFC 8914 */
+ DNS_EDNS_OPT_CLIENT_TAG = 16,
+ DNS_EDNS_OPT_SERVER_TAG = 17,
+ _DNS_EDNS_OPT_MAX_DEFINED,
+ _DNS_EDNS_OPT_INVALID = -EINVAL,
+};
+
+/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes */
+enum {
+ DNS_EDE_RCODE_OTHER = 0, /* RFC 8914, Section 4.1 */
+ DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG = 1, /* RFC 8914, Section 4.2 */
+ DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST = 2, /* RFC 8914, Section 4.3 */
+ DNS_EDE_RCODE_STALE_ANSWER = 3, /* RFC 8914, Section 4.4 */
+ DNS_EDE_RCODE_FORGED_ANSWER = 4, /* RFC 8914, Section 4.5 */
+ DNS_EDE_RCODE_DNSSEC_INDETERMINATE = 5, /* RFC 8914, Section 4.6 */
+ DNS_EDE_RCODE_DNSSEC_BOGUS = 6, /* RFC 8914, Section 4.7 */
+ DNS_EDE_RCODE_SIG_EXPIRED = 7, /* RFC 8914, Section 4.8 */
+ DNS_EDE_RCODE_SIG_NOT_YET_VALID = 8, /* RFC 8914, Section 4.9 */
+ DNS_EDE_RCODE_DNSKEY_MISSING = 9, /* RFC 8914, Section 4.10 */
+ DNS_EDE_RCODE_RRSIG_MISSING = 10, /* RFC 8914, Section 4.11 */
+ DNS_EDE_RCODE_NO_ZONE_KEY_BIT = 11, /* RFC 8914, Section 4.12 */
+ DNS_EDE_RCODE_NSEC_MISSING = 12, /* RFC 8914, Section 4.13 */
+ DNS_EDE_RCODE_CACHED_ERROR = 13, /* RFC 8914, Section 4.14 */
+ DNS_EDE_RCODE_NOT_READY = 14, /* RFC 8914, Section 4.15 */
+ DNS_EDE_RCODE_BLOCKED = 15, /* RFC 8914, Section 4.16 */
+ DNS_EDE_RCODE_CENSORED = 16, /* RFC 8914, Section 4.17 */
+ DNS_EDE_RCODE_FILTERED = 17, /* RFC 8914, Section 4.18 */
+ DNS_EDE_RCODE_PROHIBITIED = 18, /* RFC 8914, Section 4.19 */
+ DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER = 19, /* RFC 8914, Section 4.20 */
+ DNS_EDE_RCODE_NOT_AUTHORITATIVE = 20, /* RFC 8914, Section 4.21 */
+ DNS_EDE_RCODE_NOT_SUPPORTED = 21, /* RFC 8914, Section 4.22 */
+ DNS_EDE_RCODE_UNREACH_AUTHORITY = 22, /* RFC 8914, Section 4.23 */
+ DNS_EDE_RCODE_NET_ERROR = 23, /* RFC 8914, Section 4.24 */
+ DNS_EDE_RCODE_INVALID_DATA = 24, /* RFC 8914, Section 4.25 */
+ DNS_EDE_RCODE_SIG_NEVER = 25,
+ DNS_EDE_RCODE_TOO_EARLY = 26, /* RFC 9250 */
+ DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER = 27, /* RFC 9276 */
+ DNS_EDE_RCODE_TRANSPORT_POLICY = 28,
+ DNS_EDE_RCODE_SYNTHESIZED = 29,
+ _DNS_EDE_RCODE_MAX_DEFINED,
+ _DNS_EDE_RCODE_INVALID = -EINVAL,
};
const char* dns_rcode_to_string(int i) _const_;
const char *format_dns_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
#define FORMAT_DNS_RCODE(i) format_dns_rcode(i, (char [DECIMAL_STR_MAX(int)]) {})
+const char* dns_ede_rcode_to_string(int i) _const_;
+const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
+#define FORMAT_DNS_EDE_RCODE(i) format_dns_ede_rcode(i, (char [DECIMAL_STR_MAX(int)]) {})
+
const char* dns_protocol_to_string(DnsProtocol p) _const_;
DnsProtocol dns_protocol_from_string(const char *s) _pure_;
q->answer = dns_answer_unref(q->answer);
q->answer_rcode = 0;
+ q->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
+ q->answer_ede_msg = mfree(q->answer_ede_msg);
q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_errno = 0;
q->answer_query_flags = 0;
.question_bypass = dns_packet_ref(question_bypass),
.ifindex = ifindex,
.flags = flags,
+ .answer_ede_rcode = _DNS_EDE_RCODE_INVALID,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID,
.answer_protocol = _DNS_PROTOCOL_INVALID,
.answer_family = AF_UNSPEC,
q->state = state;
- (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->question_bypass, q->collected_questions, q->answer);
+ (void) manager_monitor_send(q->manager, q);
dns_query_stop(q);
if (q->complete)
DNS_ANSWER_REPLACE(q->answer, dns_answer_ref(t->answer));
q->answer_rcode = t->answer_rcode;
+ q->answer_ede_rcode = t->answer_ede_rcode;
+ r = free_and_strdup_warn(&q->answer_ede_msg, t->answer_ede_msg);
+ if (r < 0)
+ goto fail;
q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_query_flags = t->answer_query_flags | dns_transaction_source_to_query_flags(t->answer_source);
q->answer_errno = t->answer_errno;
/* Discovered data */
DnsAnswer *answer;
int answer_rcode;
+ int answer_ede_rcode;
+ char *answer_ede_msg;
DnssecResult answer_dnssec_result;
uint64_t answer_query_flags;
DnsProtocol answer_protocol;
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
t->answer_rcode = 0;
+ t->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
+ t->answer_ede_msg = mfree(t->answer_ede_msg);
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
t->answer_query_flags = 0;
.dns_udp_fd = -EBADF,
.answer_source = _DNS_TRANSACTION_SOURCE_INVALID,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID,
+ .answer_ede_rcode = _DNS_EDE_RCODE_INVALID,
.answer_nsec_ttl = UINT32_MAX,
.key = dns_resource_key_ref(key),
.query_flags = query_flags,
/* We handle DNSSEC failures different from other errors, as we care about the DNSSEC
* validation result */
- log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result));
- t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */
+ log_debug("Auxiliary DNSSEC RR query failed validation: %s%s%s%s%s%s",
+ dnssec_result_to_string(dt->answer_dnssec_result),
+ dt->answer_ede_rcode >= 0 ? " (" : "",
+ dt->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(dt->answer_ede_rcode) : "",
+ (dt->answer_ede_rcode >= 0 && !isempty(dt->answer_ede_msg)) ? ": " : "",
+ dt->answer_ede_rcode >= 0 ? strempty(dt->answer_ede_msg) : "",
+ dt->answer_ede_rcode >= 0 ? ")" : "");
+
+ /* Copy error code over */
+ t->answer_dnssec_result = dt->answer_dnssec_result;
+ t->answer_ede_rcode = dt->answer_ede_rcode;
+ r = free_and_strdup(&t->answer_ede_msg, dt->answer_ede_msg);
+ if (r < 0)
+ log_oom_debug();
+
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return 0;
}
}
+ if (DNS_PACKET_TC(p)) {
+
+ /* Truncated packets for mDNS are not allowed. Give up immediately. */
+ if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
+ dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ return;
+ }
+
+ /* Response was truncated, let's try again with good old TCP */
+ log_debug("Reply truncated, retrying via TCP.");
+ retry_with_tcp = true;
+
+ } else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
+ DNS_PACKET_IS_FRAGMENTED(p)) {
+
+ /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
+ if (t->server)
+ dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
+
+ if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
+ /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
+ * issues. (We don't do that on the lowest feature level however, since crappy DNS
+ * servers often do not implement TCP, hence falling back to TCP on fragmentation is
+ * counter-productive there.) */
+
+ log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)",
+ p->fragsize, p->size);
+ retry_with_tcp = true;
+ }
+ }
+
+ if (retry_with_tcp) {
+ r = dns_transaction_emit_tcp(t);
+ if (r == -ESRCH) {
+ /* No servers found? Damn! */
+ dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
+ return;
+ }
+ if (r == -EOPNOTSUPP) {
+ /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
+ dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
+ return;
+ }
+ if (r < 0) {
+ /* On LLMNR, if we cannot connect to the host,
+ * we immediately give up */
+ if (t->scope->protocol != DNS_PROTOCOL_DNS)
+ goto fail;
+
+ /* On DNS, couldn't send? Try immediately again, with a new server */
+ if (dns_transaction_limited_retry(t))
+ return;
+
+ /* No new server to try, give up */
+ dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
+ }
+
+ return;
+ }
+
+ /* After the superficial checks, actually parse the message. */
+ r = dns_packet_extract(p);
+ if (r < 0) {
+ if (t->server) {
+ dns_server_packet_invalid(t->server, t->current_feature_level);
+
+ r = dns_transaction_maybe_restart(t);
+ if (r < 0)
+ goto fail;
+ if (r > 0) /* Transaction got restarted... */
+ return;
+ }
+
+ dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ return;
+ }
+
switch (t->scope->protocol) {
- case DNS_PROTOCOL_DNS:
+ case DNS_PROTOCOL_DNS: {
assert(t->server);
+ (void) dns_packet_ede_rcode(p, &t->answer_ede_rcode, &t->answer_ede_msg);
+
if (!t->bypass &&
IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
+ /* If the server has replied with detailed error data, using a degraded feature set
+ * will likely not help anyone. Examine the detailed error to determine the best
+ * course of action. */
+ if (t->answer_ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) {
+ /* These codes are related to DNSSEC configuration errors. If accurate,
+ * this is the domain operator's problem, and retrying won't help. */
+ if (dns_ede_rcode_is_dnssec(t->answer_ede_rcode)) {
+ log_debug("Server returned error: %s (%s%s%s). Lookup failed.",
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
+
+ t->answer_dnssec_result = DNSSEC_UPSTREAM_FAILURE;
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return;
+ }
+
+ /* These codes probably indicate a transient error. Let's try again. */
+ if (IN_SET(t->answer_ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) {
+ log_debug("Server returned error: %s (%s%s%s), retrying transaction.",
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
+ dns_transaction_retry(t, false);
+ return;
+ }
+
+ /* OK, the query failed, but we still shouldn't degrade the feature set for
+ * this server. */
+ log_debug("Server returned error: %s (%s%s%s)",
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
+ break;
+ }
/* Request failed, immediately try again with reduced features */
if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) {
/* This server refused our request? If so, try again, use a different server */
- log_debug("Server returned REFUSED, switching servers, and retrying.");
+ if (t->answer_ede_rcode >= 0)
+ log_debug("Server returned REFUSED (%s), switching servers, and retrying.",
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode));
+ else
+ log_debug("Server returned REFUSED, switching servers, and retrying.");
if (dns_transaction_limited_retry(t))
return;
dns_server_packet_truncated(t->server, t->current_feature_level);
break;
+ }
case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
assert_not_reached();
}
- if (DNS_PACKET_TC(p)) {
-
- /* Truncated packets for mDNS are not allowed. Give up immediately. */
- if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
- dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
- return;
- }
-
- /* Response was truncated, let's try again with good old TCP */
- log_debug("Reply truncated, retrying via TCP.");
- retry_with_tcp = true;
-
- } else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
- DNS_PACKET_IS_FRAGMENTED(p)) {
-
- /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
- if (t->server)
- dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
-
- if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
- /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
- * issues. (We don't do that on the lowest feature level however, since crappy DNS
- * servers often do not implement TCP, hence falling back to TCP on fragmentation is
- * counter-productive there.) */
-
- log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)",
- p->fragsize, p->size);
- retry_with_tcp = true;
- }
- }
-
- if (retry_with_tcp) {
- r = dns_transaction_emit_tcp(t);
- if (r == -ESRCH) {
- /* No servers found? Damn! */
- dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
- return;
- }
- if (r == -EOPNOTSUPP) {
- /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
- dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
- return;
- }
- if (r < 0) {
- /* On LLMNR, if we cannot connect to the host,
- * we immediately give up */
- if (t->scope->protocol != DNS_PROTOCOL_DNS)
- goto fail;
-
- /* On DNS, couldn't send? Try immediately again, with a new server */
- if (dns_transaction_limited_retry(t))
- return;
-
- /* No new server to try, give up */
- dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
- }
-
- return;
- }
-
- /* After the superficial checks, actually parse the message. */
- r = dns_packet_extract(p);
- if (r < 0) {
- if (t->server) {
- dns_server_packet_invalid(t->server, t->current_feature_level);
-
- r = dns_transaction_maybe_restart(t);
- if (r < 0)
- goto fail;
- if (r > 0) /* Transaction got restarted... */
- return;
- }
-
- dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
- return;
- }
-
if (t->server) {
/* Report that we successfully received a valid packet with a good rcode after we initially got a bad
* rcode and subsequently downgraded the protocol */
t->answer_source = DNS_TRANSACTION_CACHE;
if (t->answer_rcode == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
- else
+ else {
+ if (t->received)
+ (void) dns_packet_ede_rcode(t->received, &t->answer_ede_rcode, &t->answer_ede_msg);
+
dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
+ }
return 0;
}
}
DnsAnswer *answer;
int answer_rcode;
+ int answer_ede_rcode;
+ char *answer_ede_msg;
DnssecResult answer_dnssec_result;
DnsTransactionSource answer_source;
uint32_t answer_nsec_ttl;
return -EINVAL;
}
- r = unhexmem(p, strlen(p), &dd, &l);
+ r = unhexmem(p, &dd, &l);
if (r < 0) {
log_warning("Failed to parse DS digest %s on line %s:%u", p, path, line);
return -EINVAL;
return -EINVAL;
}
- r = unbase64mem(p, strlen(p), &k, &l);
+ r = unbase64mem(p, &k, &l);
if (r < 0)
return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", p, path, line);
ordered_set_free(m->dns_extra_stub_listeners);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
return 1;
}
-static int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
+int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
usec_t end;
int r;
return 0;
}
-int manager_monitor_send(
- Manager *m,
- int state,
- int rcode,
- int error,
- DnsQuestion *question_idna,
- DnsQuestion *question_utf8,
- DnsPacket *question_bypass,
- DnsQuestion *collected_questions,
- DnsAnswer *answer) {
-
+int manager_monitor_send(Manager *m, DnsQuery *q) {
_cleanup_(json_variant_unrefp) JsonVariant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL;
_cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL;
Varlink *connection;
return 0;
/* Merge all questions into one */
- r = dns_question_merge(question_idna, question_utf8, &merged);
+ r = dns_question_merge(q->question_idna, q->question_utf8, &merged);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions: %m");
- if (question_bypass) {
+ if (q->question_bypass) {
_cleanup_(dns_question_unrefp) DnsQuestion *merged2 = NULL;
- r = dns_question_merge(merged, question_bypass->question, &merged2);
+ r = dns_question_merge(merged, q->question_bypass->question, &merged2);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions and DNS packet question: %m");
return log_error_errno(r, "Failed to convert question to JSON: %m");
/* Generate a JSON array of the questions preceding the current one in the CNAME chain */
- r = dns_question_to_json(collected_questions, &jcollected_questions);
+ r = dns_question_to_json(q->collected_questions, &jcollected_questions);
if (r < 0)
return log_error_errno(r, "Failed to convert question to JSON: %m");
- DNS_ANSWER_FOREACH_ITEM(rri, answer) {
+ DNS_ANSWER_FOREACH_ITEM(rri, q->answer) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = dns_resource_record_to_json(rri->rr, &v);
SET_FOREACH(connection, m->varlink_subscription) {
r = varlink_notifyb(connection,
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(state))),
- JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_RCODE_FAILURE, "rcode", JSON_BUILD_INTEGER(rcode)),
- JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_ERRNO, "errno", JSON_BUILD_INTEGER(error)),
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED,
+ "result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_RCODE_FAILURE,
+ "rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_ERRNO,
+ "errno", JSON_BUILD_INTEGER(q->answer_errno)),
+ JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
+ DNS_TRANSACTION_DNSSEC_FAILED,
+ DNS_TRANSACTION_RCODE_FAILURE) &&
+ q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
+ DNS_TRANSACTION_DNSSEC_FAILED,
+ DNS_TRANSACTION_RCODE_FAILURE) &&
+ q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)),
JSON_BUILD_PAIR("question", JSON_BUILD_VARIANT(jquestion)),
- JSON_BUILD_PAIR_CONDITION(jcollected_questions, "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
- JSON_BUILD_PAIR_CONDITION(janswer, "answer", JSON_BUILD_VARIANT(janswer))));
+ JSON_BUILD_PAIR_CONDITION(jcollected_questions,
+ "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
+ JSON_BUILD_PAIR_CONDITION(janswer,
+ "answer", JSON_BUILD_VARIANT(janswer))));
if (r < 0)
log_debug_errno(r, "Failed to send monitor event, ignoring: %m");
}
uint32_t manager_find_mtu(Manager *m);
-int manager_monitor_send(Manager *m, int state, int rcode, int error, DnsQuestion *question_idna, DnsQuestion *question_utf8, DnsPacket *question_bypass, DnsQuestion *collected_questions, DnsAnswer *answer);
+int manager_monitor_send(Manager *m, DnsQuery *q);
+int sendmsg_loop(int fd, struct msghdr *mh, int flags);
int manager_write(Manager *m, int fd, DnsPacket *p);
int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p);
int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret);
case DNS_TRANSACTION_DNSSEC_FAILED:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result)))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL);
case DNS_TRANSACTION_RCODE_FAILURE:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-daemon.h"
+
+#include "fd-util.h"
+#include "iovec-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "resolved-dns-packet.h"
+#include "resolved-manager.h"
+#include "socket-netlink.h"
+#include "socket-util.h"
+
+/* Taken from resolved-dns-stub.c */
+#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U)
+
+/* This is more or less verbatim manager_recv() from resolved-manager.c, sans the manager stuff */
+static int server_recv(int fd, DnsPacket **ret) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ CMSG_BUFFER_TYPE(CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
+ + CMSG_SPACE(int) /* ttl/hoplimit */
+ + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */) control;
+ union sockaddr_union sa;
+ struct iovec iov;
+ struct msghdr mh = {
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t ms, l;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ ms = next_datagram_size_fd(fd);
+ if (ms < 0)
+ return ms;
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, ms, DNS_PACKET_SIZE_MAX);
+ if (r < 0)
+ return r;
+
+ iov = IOVEC_MAKE(DNS_PACKET_DATA(p), p->allocated);
+
+ l = recvmsg_safe(fd, &mh, 0);
+ if (ERRNO_IS_NEG_TRANSIENT(l))
+ return 0;
+ if (l <= 0)
+ return l;
+
+ assert(!(mh.msg_flags & MSG_TRUNC));
+
+ p->size = (size_t) l;
+
+ p->family = sa.sa.sa_family;
+ p->ipproto = IPPROTO_UDP;
+ if (p->family == AF_INET) {
+ p->sender.in = sa.in.sin_addr;
+ p->sender_port = be16toh(sa.in.sin_port);
+ } else if (p->family == AF_INET6) {
+ p->sender.in6 = sa.in6.sin6_addr;
+ p->sender_port = be16toh(sa.in6.sin6_port);
+ p->ifindex = sa.in6.sin6_scope_id;
+ } else
+ return -EAFNOSUPPORT;
+
+ p->timestamp = now(CLOCK_BOOTTIME);
+
+ CMSG_FOREACH(cmsg, &mh) {
+
+ if (cmsg->cmsg_level == IPPROTO_IPV6) {
+ assert(p->family == AF_INET6);
+
+ switch (cmsg->cmsg_type) {
+
+ case IPV6_PKTINFO: {
+ struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
+
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi6_ifindex;
+
+ p->destination.in6 = i->ipi6_addr;
+ break;
+ }
+
+ case IPV6_HOPLIMIT:
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+
+ case IPV6_RECVFRAGSIZE:
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+ }
+ } else if (cmsg->cmsg_level == IPPROTO_IP) {
+ assert(p->family == AF_INET);
+
+ switch (cmsg->cmsg_type) {
+
+ case IP_PKTINFO: {
+ struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
+
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi_ifindex;
+
+ p->destination.in = i->ipi_addr;
+ break;
+ }
+
+ case IP_TTL:
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+
+ case IP_RECVFRAGSIZE:
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+ }
+ }
+ }
+
+ /* The Linux kernel sets the interface index to the loopback
+ * device if the packet came from the local host since it
+ * avoids the routing table in such a case. Let's unset the
+ * interface index in such a case. */
+ if (p->ifindex == LOOPBACK_IFINDEX)
+ p->ifindex = 0;
+
+ log_debug("Received DNS UDP packet of size %zu, ifindex=%i, ttl=%u, fragsize=%zu, sender=%s, destination=%s",
+ p->size, p->ifindex, p->ttl, p->fragsize,
+ IN_ADDR_TO_STRING(p->family, &p->sender),
+ IN_ADDR_TO_STRING(p->family, &p->destination));
+
+ *ret = TAKE_PTR(p);
+ return 1;
+}
+
+/* Same as above, see manager_ipv4_send() in resolved-manager.c */
+static int server_ipv4_send(
+ int fd,
+ const struct in_addr *destination,
+ uint16_t port,
+ const struct in_addr *source,
+ DnsPacket *packet) {
+
+ union sockaddr_union sa;
+ struct iovec iov;
+ struct msghdr mh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa.in),
+ };
+
+ assert(fd >= 0);
+ assert(destination);
+ assert(port > 0);
+ assert(packet);
+
+ iov = IOVEC_MAKE(DNS_PACKET_DATA(packet), packet->size);
+
+ sa = (union sockaddr_union) {
+ .in.sin_family = AF_INET,
+ .in.sin_addr = *destination,
+ .in.sin_port = htobe16(port),
+ };
+
+ return sendmsg_loop(fd, &mh, 0);
+}
+
+static int make_reply_packet(DnsPacket *packet, DnsPacket **ret) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ int r;
+
+ assert(packet);
+ assert(ret);
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_PAYLOAD_SIZE_MAX(packet));
+ if (r < 0)
+ return r;
+
+ r = dns_packet_append_question(p, packet->question);
+ if (r < 0)
+ return r;
+
+ DNS_PACKET_HEADER(p)->id = DNS_PACKET_ID(packet);
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(packet->question));
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
+
+static int reply_append_edns(DnsPacket *packet, DnsPacket *reply, const char *extra_text, size_t rcode, uint16_t ede_code) {
+ size_t saved_size;
+ int r;
+
+ assert(packet);
+ assert(reply);
+
+ /* Append EDNS0 stuff (inspired by dns_packet_append_opt() from resolved-dns-packet.c).
+ *
+ * Relevant headers from RFC 6891:
+ *
+ * +------------+--------------+------------------------------+
+ * | Field Name | Field Type | Description |
+ * +------------+--------------+------------------------------+
+ * | NAME | domain name | MUST be 0 (root domain) |
+ * | TYPE | u_int16_t | OPT (41) |
+ * | CLASS | u_int16_t | requestor's UDP payload size |
+ * | TTL | u_int32_t | extended RCODE and flags |
+ * | RDLEN | u_int16_t | length of all RDATA |
+ * | RDATA | octet stream | {attribute,value} pairs |
+ * +------------+--------------+------------------------------+
+ *
+ * +0 (MSB) +1 (LSB)
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | OPTION-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 2: | OPTION-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | |
+ * / OPTION-DATA /
+ * / /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ *
+ * And from RFC 8914:
+ *
+ * 1 1 1 1 1 1
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | OPTION-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 2: | OPTION-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | INFO-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 6: / EXTRA-TEXT ... /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ */
+
+ saved_size = reply->size;
+
+ /* empty name */
+ r = dns_packet_append_uint8(reply, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* type */
+ r = dns_packet_append_uint16(reply, DNS_TYPE_OPT, NULL);
+ if (r < 0)
+ return r;
+
+ /* class: maximum udp packet that can be received */
+ r = dns_packet_append_uint16(reply, ADVERTISE_DATAGRAM_SIZE_MAX, NULL);
+ if (r < 0)
+ return r;
+
+ /* extended RCODE and VERSION */
+ r = dns_packet_append_uint16(reply, ((uint16_t) rcode & 0x0FF0) << 4, NULL);
+ if (r < 0)
+ return r;
+
+ /* flags: DNSSEC OK (DO), see RFC3225 */
+ r = dns_packet_append_uint16(reply, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* RDATA */
+
+ size_t extra_text_len = isempty(extra_text) ? 0 : strlen(extra_text);
+ /* RDLENGTH (OPTION CODE + OPTION LENGTH + INFO-CODE + EXTRA-TEXT) */
+ r = dns_packet_append_uint16(reply, 2 + 2 + 2 + extra_text_len, NULL);
+ if (r < 0)
+ return 0;
+
+ /* OPTION-CODE: 15 for EDE */
+ r = dns_packet_append_uint16(reply, 15, NULL);
+ if (r < 0)
+ return r;
+
+ /* OPTION-LENGTH: INFO-CODE + EXTRA-TEXT */
+ r = dns_packet_append_uint16(reply, 2 + extra_text_len, NULL);
+ if (r < 0)
+ return r;
+
+ /* INFO-CODE: EDE code */
+ r = dns_packet_append_uint16(reply, ede_code, NULL);
+ if (r < 0)
+ return r;
+
+ /* EXTRA-TEXT */
+ if (extra_text_len > 0) {
+ /* From RFC 8914:
+ * EDE text may be null terminated but MUST NOT be assumed to be; the length MUST be derived
+ * from the OPTION-LENGTH field
+ *
+ * Let's exercise our code on the receiving side and not NUL-terminate the EXTRA-TEXT field
+ */
+ r = dns_packet_append_blob(reply, extra_text, extra_text_len, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ DNS_PACKET_HEADER(reply)->arcount = htobe16(DNS_PACKET_ARCOUNT(reply) + 1);
+ reply->opt_start = saved_size;
+ reply->opt_size = reply->size - saved_size;
+
+ /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
+ DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
+ return 0;
+}
+
+static void server_fail(DnsPacket *packet, DnsPacket *reply, int rcode) {
+ assert(reply);
+
+ /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
+ DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
+}
+
+static int server_handle_edns_bogus_dnssec(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+
+ return reply_append_edns(packet, reply, NULL, DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_DNSSEC_BOGUS);
+}
+
+static int server_handle_edns_extra_text(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+
+ return reply_append_edns(packet, reply, "Nothing to see here!", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_CENSORED);
+}
+
+static int server_handle_edns_invalid_code(DnsPacket *packet, DnsPacket *reply, const char *extra_text) {
+ assert(packet);
+ assert(reply);
+ assert_cc(_DNS_EDE_RCODE_MAX_DEFINED < UINT16_MAX);
+
+ return reply_append_edns(packet, reply, extra_text, DNS_RCODE_SERVFAIL, _DNS_EDE_RCODE_MAX_DEFINED + 1);
+}
+
+static int server_handle_edns_code_zero(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+ assert_cc(DNS_EDE_RCODE_OTHER == 0);
+
+ return reply_append_edns(packet, reply, "\xF0\x9F\x90\xB1", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_OTHER);
+}
+
+static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+ const char *name;
+ int r;
+
+ assert(fd >= 0);
+
+ r = server_recv(fd, &packet);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to receive packet, ignoring: %m");
+ return 0;
+ }
+
+ r = dns_packet_validate_query(packet);
+ if (r < 0) {
+ log_debug_errno(r, "Invalid DNS UDP packet, ignoring.");
+ return 0;
+ }
+
+ r = dns_packet_extract(packet);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract DNS packet, ignoring: %m");
+ return 0;
+ }
+
+ name = dns_question_first_name(packet->question);
+ log_info("Processing question for name '%s'", name);
+
+ (void) dns_question_dump(packet->question, stdout);
+
+ r = make_reply_packet(packet, &reply);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to make reply packet, ignoring: %m");
+ return 0;
+ }
+
+ if (streq_ptr(name, "edns-bogus-dnssec.forwarded.test"))
+ r = server_handle_edns_bogus_dnssec(packet, reply);
+ else if (streq_ptr(name, "edns-extra-text.forwarded.test"))
+ r = server_handle_edns_extra_text(packet, reply);
+ else if (streq_ptr(name, "edns-invalid-code.forwarded.test"))
+ r = server_handle_edns_invalid_code(packet, reply, NULL);
+ else if (streq_ptr(name, "edns-invalid-code-with-extra-text.forwarded.test"))
+ r = server_handle_edns_invalid_code(packet, reply, "Hello [#]$%~ World");
+ else if (streq_ptr(name, "edns-code-zero.forwarded.test"))
+ r = server_handle_edns_code_zero(packet, reply);
+ else
+ r = log_debug_errno(SYNTHETIC_ERRNO(EFAULT), "Unhandled name '%s', ignoring.", name);
+ if (r < 0)
+ server_fail(packet, reply, DNS_RCODE_NXDOMAIN);
+
+ r = server_ipv4_send(fd, &packet->sender.in, packet->sender_port, &packet->destination.in, reply);
+ if (r < 0)
+ log_debug_errno(r, "Failed to send reply, ignoring: %m");
+
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ log_setup();
+
+ if (argc != 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "This program takes one argument in format ip_address:port");
+
+ fd = make_socket_fd(LOG_DEBUG, argv[1], SOCK_DGRAM, SOCK_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to listen on address '%s': %m", argv[1]);
+
+ r = sd_event_default(&event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate event: %m");
+
+ r = sd_event_add_io(event, NULL, fd, EPOLLIN, on_dns_packet, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add IO event source: %m");
+
+ r = sd_event_set_signal_exit(event, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
+
+ (void) sd_notify(/* unset_environment=false */ false, "READY=1");
+
+ r = sd_event_loop(event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run event loop: %m");
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
#include "ptyfwd.h"
#include "signal-util.h"
#include "spawn-polkit-agent.h"
+#include "special.h"
#include "strv.h"
#include "terminal-util.h"
#include "unit-def.h"
strv_free_and_replace(arg_cmdline, l);
if (!arg_slice) {
- arg_slice = strdup("user.slice");
+ arg_slice = strdup(SPECIAL_USER_SLICE);
if (!arg_slice)
return log_oom();
}
#include "bus-message.h"
#include "bus-polkit.h"
#include "bus-util.h"
+#include "process-util.h"
#include "strv.h"
#include "user-util.h"
-static int check_good_user(sd_bus_message *m, uid_t good_user) {
+static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
uid_t sender_uid;
int r;
assert(m);
if (good_user == UID_INVALID)
- return 0;
+ return false;
r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
if (r < 0)
return r;
}
-static int bus_message_new_polkit_auth_call(
+static int bus_message_new_polkit_auth_call_for_bus(
sd_bus_message *m,
const char *action,
const char **details,
/* Tests non-interactively! */
- r = check_good_user(call, good_user);
+ r = bus_message_check_good_user(call, good_user);
if (r != 0)
return r;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL, *reply = NULL;
int authorized = false, challenge = false;
- r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request);
+ r = bus_message_new_polkit_auth_call_for_bus(call, action, details, /* interactive = */ false, &request);
if (r < 0)
return r;
AsyncPolkitQueryAction *action;
- sd_bus_message *request;
+ sd_bus *bus;
+ sd_bus_message *request; /* the original bus method call that triggered the polkit auth, NULL in case of varlink */
sd_bus_slot *slot;
+ Varlink *link; /* the original varlink method call that triggered the polkit auth, NULL in case of bus */
Hashmap *registry;
sd_event_source *defer_event_source;
sd_bus_slot_unref(q->slot);
- if (q->registry && q->request)
- hashmap_remove(q->registry, q->request);
+ if (q->registry) {
+ if (q->request)
+ hashmap_remove(q->registry, q->request);
+ if (q->link)
+ hashmap_remove(q->registry, q->link);
+ }
sd_bus_message_unref(q->request);
+ sd_bus_unref(q->bus);
+ varlink_unref(q->link);
+
async_polkit_query_action_free(q->action);
sd_event_source_disable_unref(q->defer_event_source);
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(AsyncPolkitQuery, async_polkit_query, async_polkit_query_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQuery*, async_polkit_query_unref);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ async_polkit_query_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ AsyncPolkitQuery,
+ async_polkit_query_unref);
+
static int async_polkit_defer(sd_event_source *s, void *userdata) {
AsyncPolkitQuery *q = ASSERT_PTR(userdata);
if (!q->defer_event_source) {
r = sd_event_add_defer(
- sd_bus_get_event(sd_bus_message_get_bus(reply)),
+ sd_bus_get_event(q->bus),
&q->defer_event_source,
async_polkit_defer,
q);
if (r < 0)
return r;
- r = sd_bus_message_rewind(q->request, true);
- if (r < 0)
- return r;
+ if (q->request) {
+ r = sd_bus_message_rewind(q->request, true);
+ if (r < 0)
+ return r;
- r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request);
- if (r < 0)
- return r;
+ r = sd_bus_enqueue_for_read(q->bus, q->request);
+ if (r < 0)
+ return r;
+ }
+
+ if (q->link) {
+ r = varlink_dispatch_again(q->link);
+ if (r < 0)
+ return r;
+ }
return 1;
}
r = async_polkit_process_reply(reply, q);
if (r < 0) {
log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m");
- (void) sd_bus_reply_method_errno(q->request, r, NULL);
+ if (q->request)
+ (void) sd_bus_reply_method_errno(q->request, r, NULL);
+ if (q->link)
+ varlink_error_errno(q->link, r);
async_polkit_query_unref(q);
}
return r;
assert(q);
assert(action);
- assert(ret_error);
LIST_FOREACH(authorized, a, q->authorized_actions)
if (streq(a->action, action) && strv_equal(a->details, (char**) details))
- return 1;
+ return 1; /* Allow! */
if (q->error_action && streq(q->error_action->action, action))
return sd_bus_error_copy(ret_error, &q->error);
assert(registry);
assert(ret_error);
- r = check_good_user(call, good_user);
+ r = bus_message_check_good_user(call, good_user);
if (r != 0)
return r;
if (c > 0)
interactive = true;
- r = hashmap_ensure_allocated(registry, NULL);
- if (r < 0)
- return r;
-
- r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk);
+ r = bus_message_new_polkit_auth_call_for_bus(call, action, details, interactive, &pk);
if (r < 0)
return r;
*q = (AsyncPolkitQuery) {
.n_ref = 1,
.request = sd_bus_message_ref(call),
+ .bus = sd_bus_ref(sd_bus_message_get_bus(call)),
};
}
return -ENOMEM;
if (!q->registry) {
- r = hashmap_put(*registry, call, q);
+ r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, call, q);
if (r < 0)
return r;
return -EACCES;
}
-Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) {
+static int varlink_check_good_user(Varlink *link, uid_t good_user) {
+ int r;
+
+ assert(link);
+
+ if (good_user == UID_INVALID)
+ return false;
+
+ uid_t peer_uid;
+ r = varlink_get_peer_uid(link, &peer_uid);
+ if (r < 0)
+ return r;
+
+ return good_user == peer_uid;
+}
+
+static int varlink_check_peer_privilege(Varlink *link) {
+ int r;
+
+ assert(link);
+
+ uid_t peer_uid;
+ r = varlink_get_peer_uid(link, &peer_uid);
+ if (r < 0)
+ return r;
+
+ uid_t our_uid = getuid();
+ return peer_uid == our_uid ||
+ (our_uid != 0 && peer_uid == 0);
+}
+
#if ENABLE_POLKIT
- return hashmap_free_with_destructor(registry, async_polkit_query_unref);
-#else
- assert(hashmap_isempty(registry));
- return hashmap_free(registry);
+static int bus_message_new_polkit_auth_call_for_varlink(
+ sd_bus *bus,
+ Varlink *link,
+ const char *action,
+ const char **details,
+ bool interactive,
+ sd_bus_message **ret) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ int r;
+
+ assert(bus);
+ assert(link);
+ assert(action);
+ assert(ret);
+
+ r = varlink_get_peer_pidref(link, &pidref);
+ if (r < 0)
+ return r;
+ if (r == 0) /* if we couldn't get a pidfd this returns == 0 */
+ return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Failed to get peer pidfd, cannot securely authenticate.");
+
+ uid_t uid;
+ r = varlink_get_peer_uid(link, &uid);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &c,
+ "org.freedesktop.PolicyKit1",
+ "/org/freedesktop/PolicyKit1/Authority",
+ "org.freedesktop.PolicyKit1.Authority",
+ "CheckAuthorization");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(
+ c,
+ "(sa{sv})s",
+ "unix-process", 2,
+ "pidfd", "h", (uint32_t) pidref.fd,
+ "uid", "i", (int32_t) uid,
+ action);
+ if (r < 0)
+ return r;
+
+ r = bus_message_append_strv_key_value(c, details);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(c, "us", interactive, NULL);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(c);
+ return 0;
+}
+
+static bool varlink_allow_interactive_authentication(Varlink *link) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ assert(link);
+
+ /* We look for the allowInteractiveAuthentication field in the message currently being dispatched,
+ * always under the same name. */
+
+ r = varlink_get_current_parameters(link, &v);
+ if (r < 0)
+ return r;
+
+ JsonVariant *b;
+ b = json_variant_by_key(v, "allowInteractiveAuthentication");
+ if (b) {
+ if (json_variant_is_boolean(b))
+ return json_variant_boolean(b);
+
+ log_debug("Incoming 'allowInteractiveAuthentication' field is not a boolean, ignoring.");
+ }
+
+ return false;
+}
#endif
+
+int varlink_verify_polkit_async(
+ Varlink *link,
+ sd_bus *bus,
+ const char *action,
+ const char **details,
+ uid_t good_user,
+ Hashmap **registry) {
+
+ int r;
+
+ assert(link);
+ assert(registry);
+
+ /* This is the same as bus_verify_polkit_async_full(), but authenticates the peer of a varlink
+ * connection rather than the sender of a bus message. */
+
+ r = varlink_check_good_user(link, good_user);
+ if (r != 0)
+ return r;
+
+ r = varlink_check_peer_privilege(link);
+ if (r != 0)
+ return r;
+
+#if ENABLE_POLKIT
+ _cleanup_(async_polkit_query_unrefp) AsyncPolkitQuery *q = NULL;
+
+ q = async_polkit_query_ref(hashmap_get(*registry, link));
+ /* This is a repeated invocation of this function, hence let's check if we've already got
+ * a response from polkit for this action */
+ if (q) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ r = async_polkit_query_check_action(q, action, details, &error);
+ if (r < 0) {
+ /* Reply with a nice error */
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED))
+ return varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL);
+
+ if (ERRNO_IS_NEG_PRIVILEGE(r))
+ return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL);
+
+ return r;
+ }
+ if (r > 0)
+ return r;
+ }
+
+ _cleanup_(sd_bus_unrefp) sd_bus *mybus = NULL;
+ if (!bus) {
+ r = sd_bus_open_system(&mybus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_attach_event(mybus, varlink_get_event(link), 0);
+ if (r < 0)
+ return r;
+
+ bus = mybus;
+ }
+
+ bool interactive = varlink_allow_interactive_authentication(link);
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+ r = bus_message_new_polkit_auth_call_for_varlink(bus, link, action, details, interactive, &pk);
+ if (r < 0)
+ return r;
+
+ if (!q) {
+ q = new(AsyncPolkitQuery, 1);
+ if (!q)
+ return -ENOMEM;
+
+ *q = (AsyncPolkitQuery) {
+ .n_ref = 1,
+ .link = varlink_ref(link),
+ .bus = sd_bus_ref(bus),
+ };
+ }
+
+ assert(!q->action);
+ q->action = new(AsyncPolkitQueryAction, 1);
+ if (!q->action)
+ return -ENOMEM;
+
+ *q->action = (AsyncPolkitQueryAction) {
+ .action = strdup(action),
+ .details = strv_copy((char**) details),
+ };
+ if (!q->action->action || !q->action->details)
+ return -ENOMEM;
+
+ if (!q->registry) {
+ r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, link, q);
+ if (r < 0)
+ return r;
+
+ q->registry = *registry;
+ }
+
+ r = sd_bus_call_async(bus, &q->slot, pk, async_polkit_callback, q, 0);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(q);
+
+ return 0;
+#endif
+
+ return -EACCES;
}
#include "hashmap.h"
#include "user-util.h"
+#include "varlink.h"
int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e);
return bus_verify_polkit_async_full(call, action, details, false, UID_INVALID, registry, ret_error);
}
-Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry);
+int varlink_verify_polkit_async(Varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, Hashmap **registry);
+
+/* A JsonDispatch initializer that makes sure the allowInteractiveAuthentication boolean field we want for
+ * polkit support in Varlink calls is ignored while regular dispatching (and does not result in errors
+ * regarding unexpected fields) */
+#define VARLINK_DISPATCH_POLKIT_FIELD { \
+ .name = "allowInteractiveAuthentication", \
+ .type = JSON_VARIANT_BOOLEAN, \
+ }
bus_print_property_value(name, expected_value, flags, "[not set]");
- else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") &&
+ else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin",
+ "MemoryHigh", "MemoryMax",
+ "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") &&
u == CGROUP_LIMIT_MAX) ||
- (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) ||
+ (endswith(name, "TasksMax") && u == UINT64_MAX) ||
(startswith(name, "Limit") && u == UINT64_MAX) ||
(startswith(name, "DefaultLimit") && u == UINT64_MAX))
_cleanup_free_ void *decoded = NULL;
size_t decoded_size;
- r = unbase64mem(p, SIZE_MAX, &decoded, &decoded_size);
+ r = unbase64mem(p, &decoded, &decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to base64 decode encrypted credential: %m");
_cleanup_free_ void *decoded = NULL;
size_t sz;
- r = unbase64mem(eq, SIZE_MAX, &decoded, &sz);
+ r = unbase64mem(eq, &decoded, &sz);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 data '%s': %m", eq);
return bus_append_string(m, "RootHashPath", eq);
/* We have a roothash to decode, eg: RootHash=012345789abcdef */
- r = unhexmem(eq, strlen(eq), &roothash_decoded, &roothash_decoded_size);
+ r = unhexmem(eq, &roothash_decoded, &roothash_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHash= '%s': %m", eq);
if (roothash_decoded_size < sizeof(sd_id128_t))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:': %m", eq);
/* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */
- r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size);
+ r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHashSignature= '%s': %m", eq);
#include "bus-internal.h"
#include "bus-label.h"
#include "bus-util.h"
+#include "daemon-util.h"
#include "data-fd-util.h"
#include "fd-util.h"
#include "memstream-util.h"
if (r == 0 && !exiting && idle) {
/* Inform the service manager that we are going down, so that it will queue all
- * further start requests, instead of assuming we are already running. */
- sd_notify(false, "STOPPING=1");
+ * further start requests, instead of assuming we are still running. */
+ (void) sd_notify(false, NOTIFY_STOPPING);
r = bus_async_unregister_and_exit(e, bus, name);
if (r < 0)
#include "string-util.h"
#include "tomoyo-util.h"
#include "tpm2-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "virt.h"
void *userdata) {
char **s = ASSERT_PTR(data);
+ int r;
assert(filename);
assert(lvalue);
if (isempty(rvalue)) {
*s = mfree(*s);
- return 0;
+ return 1;
}
if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) {
return 0;
}
- return free_and_strdup_warn(s, empty_to_null(rvalue));
+ r = free_and_strdup_warn(s, empty_to_null(rvalue));
+ if (r < 0)
+ return r;
+
+ return 1;
}
int config_parse_dns_name(
return 0;
}
- return 0;
+ return 1;
}
int config_parse_rlimit(
}
int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) {
+ _cleanup_(iovec_done_erase) struct iovec ret_iovec = {};
_cleanup_(erase_and_freep) void *data = NULL;
_cleanup_free_ char *fn = NULL;
size_t sz = 0;
const char *d;
int r;
- assert(ret);
-
/* Just like read_credential() but will also look for encrypted credentials. Note that services only
* receive decrypted credentials, hence use read_credential() for those. This helper here is for
* generators, i.e. code that runs outside of service context, and thus has no decrypted credentials
now(CLOCK_REALTIME),
/* tpm2_device = */ NULL,
/* tpm2_signature_path = */ NULL,
- data,
- sz,
- ret,
- ret_size);
+ &IOVEC_MAKE(data, sz),
+ /* flags= */ 0,
+ &ret_iovec);
if (r < 0)
return r;
+ if (ret)
+ *ret = TAKE_PTR(ret_iovec.iov_base);
+ if (ret_size)
+ *ret_size = ret_iovec.iov_len;
+
return 1; /* found */
not_found:
- *ret = NULL;
-
+ if (ret)
+ *ret = NULL;
if (ret_size)
*ret_size = 0;
...) {
_cleanup_free_ void *b = NULL;
+ bool all = true;
int r, ret = 0;
/* Reads a bunch of credentials into the specified buffers. If the specified buffers are already
r = read_credential(first_name, &b, NULL);
if (r == -ENXIO) /* No creds passed at all? Bail immediately. */
return 0;
- if (r < 0) {
- if (r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*first_value, b);
va_list ap;
if (!name)
break;
- value = va_arg(ap, char **);
- if (*value)
- continue;
+ value = ASSERT_PTR(va_arg(ap, char **));
r = read_credential(name, &bb, NULL);
- if (r < 0) {
- if (ret >= 0 && r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*value, bb);
}
va_end(ap);
- return ret;
+ return ret < 0 ? ret : all;
}
int read_credential_bool(const char *name) {
CredentialSecretFlags flags,
const char *dirname,
const char *fn,
- void **ret_data,
- size_t *ret_size) {
+ struct iovec *ret) {
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -EBADF;
goto fail;
}
- if (ret_data) {
+ if (ret) {
void *copy;
copy = memdup(buf.data, sizeof(buf.data));
goto fail;
}
- *ret_data = copy;
+ *ret = IOVEC_MAKE(copy, sizeof(buf.data));
}
- if (ret_size)
- *ret_size = sizeof(buf.data);
-
return 0;
fail:
return r;
}
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) {
_cleanup_free_ char *_dirname = NULL, *_filename = NULL;
_cleanup_close_ int dfd = -EBADF;
sd_id128_t machine_id;
"Failed to open %s/%s: %m", dirname, filename);
- r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret, ret_size);
+ r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret);
if (r == -EEXIST) {
log_debug_errno(r, "Credential secret %s/%s appeared while we were creating it, rereading.",
dirname, filename);
if (!copy)
return log_oom_debug();
- *ret = copy;
+ *ret = IOVEC_MAKE(copy, sz);
}
- if (ret_size)
- *ret_size = sz;
-
return 0;
}
#define CREDENTIAL_FIELD_SIZE_MAX (16U*1024U)
static int sha256_hash_host_and_tpm2_key(
- const void *host_key,
- size_t host_key_size,
- const void *tpm2_key,
- size_t tpm2_key_size,
+ const struct iovec *host_key,
+ const struct iovec *tpm2_key,
uint8_t ret[static SHA256_DIGEST_LENGTH]) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL;
unsigned l;
- assert(host_key_size == 0 || host_key);
- assert(tpm2_key_size == 0 || tpm2_key);
+ assert(iovec_is_valid(host_key));
+ assert(iovec_is_valid(tpm2_key));
assert(ret);
/* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */
if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context.");
- if (host_key && EVP_DigestUpdate(md, host_key, host_key_size) != 1)
+ if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key.");
- if (tpm2_key && EVP_DigestUpdate(md, tpm2_key, tpm2_key_size) != 1)
+ if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key.");
assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH);
uint32_t tpm2_hash_pcr_mask,
const char *tpm2_pubkey_path,
uint32_t tpm2_pubkey_pcr_mask,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
+ _cleanup_(iovec_done) struct iovec tpm2_blob = {}, tpm2_policy_hash = {}, iv = {}, pubkey = {};
+ _cleanup_(iovec_done_erase) struct iovec tpm2_key = {}, output = {}, host_key = {};
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml;
- _cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL;
_cleanup_free_ struct metadata_credential_header *m = NULL;
uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0;
struct encrypted_credential_header *h;
int ksz, bsz, ivsz, tsz, added, r;
- _cleanup_free_ void *pubkey = NULL;
- size_t pubkey_size = 0;
uint8_t md[SHA256_DIGEST_LENGTH];
const EVP_CIPHER *cc;
sd_id128_t id;
+ size_t p, ml;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
if (!sd_id128_in_set(with_key,
_CRED_AUTO,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
- CRED_AES256_GCM_BY_TPM2_ABSENT))
+ CRED_AES256_GCM_BY_NULL))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key));
if (name && !credential_name_valid(name))
CREDENTIAL_SECRET_GENERATE|
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED|
(sd_id128_equal(with_key, _CRED_AUTO) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
- &host_key,
- &host_key_size);
+ &host_key);
if (r == -ENOMEDIUM && sd_id128_equal(with_key, _CRED_AUTO))
log_debug_errno(r, "Credential host secret location on temporary file system, not using.");
else if (r < 0)
/* Load public key for PCR policies, if one is specified, or explicitly requested */
- r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD))
return log_error_errno(r, "Failed read TPM PCR public key: %m");
}
}
- if (!pubkey)
+ if (!iovec_is_set(&pubkey))
tpm2_pubkey_pcr_mask = 0;
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
return log_error_errno(r, "Could not read PCR values: %m");
TPM2B_PUBLIC public;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ if (iovec_is_set(&pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
}
r = tpm2_calculate_sealing_policy(
tpm2_hash_pcr_values,
tpm2_n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false,
/* pcrlock_policy= */ NULL,
&tpm2_policy);
/* seal_key_handle= */ 0,
&tpm2_policy,
/* pin= */ NULL,
- &tpm2_key, &tpm2_key_size,
- &tpm2_blob, &tpm2_blob_size,
+ &tpm2_key,
+ &tpm2_blob,
&tpm2_primary_alg,
- /* ret_srk_buf= */ NULL,
- /* ret_srk_buf_size= */ NULL);
+ /* ret_srk= */ NULL);
if (r < 0) {
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m");
}
- tpm2_policy_hash_size = tpm2_policy.size;
- tpm2_policy_hash = malloc(tpm2_policy_hash_size);
- if (!tpm2_policy_hash)
+ if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash))
return log_oom();
- memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size);
- assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX);
- assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
}
#endif
if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
/* Let's settle the key type in auto mode now. */
- if (host_key && tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
- else if (tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
- else if (host_key)
+ if (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key))
+ id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
+ else if (iovec_is_set(&tpm2_key))
+ id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
+ else if (iovec_is_set(&host_key))
id = CRED_AES256_GCM_BY_HOST;
else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
- id = CRED_AES256_GCM_BY_TPM2_ABSENT;
+ id = CRED_AES256_GCM_BY_NULL;
else
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"TPM2 not available and host key located on temporary file system, no encryption key available.");
} else
id = with_key;
- if (sd_id128_equal(id, CRED_AES256_GCM_BY_TPM2_ABSENT))
+ if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided.");
/* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */
- r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
+ r = sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
if (r < 0)
return r;
if (ivsz > 0) {
assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX);
- iv = malloc(ivsz);
- if (!iv)
+ iv.iov_base = malloc(ivsz);
+ if (!iv.iov_base)
return log_oom();
- r = crypto_random_bytes(iv, ivsz);
+ iv.iov_len = ivsz;
+
+ r = crypto_random_bytes(iv.iov_base, iv.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to acquired randomized IV: %m");
}
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_EncryptInit_ex(context, cc, NULL, md, iv) != 1)
+ if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s",
ERR_error_string(ERR_get_error(), NULL));
/* Just an upper estimate */
- output_size =
+ output.iov_len =
ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
- ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) +
- ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) +
+ ALIGN8(iovec_is_set(&tpm2_key) ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len : 0) +
+ ALIGN8(iovec_is_set(&pubkey) ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) +
- input_size + 2U * (size_t) bsz +
+ input->iov_len + 2U * (size_t) bsz +
tsz;
- output = malloc0(output_size);
- if (!output)
+ output.iov_base = malloc0(output.iov_len);
+ if (!output.iov_base)
return log_oom();
- h = (struct encrypted_credential_header*) output;
+ h = (struct encrypted_credential_header*) output.iov_base;
h->id = id;
h->block_size = htole32(bsz);
h->key_size = htole32(ksz);
h->tag_size = htole32(tsz);
h->iv_size = htole32(ivsz);
- memcpy(h->iv, iv, ivsz);
+ memcpy(h->iv, iv.iov_base, ivsz);
p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz);
- if (tpm2_key) {
+ if (iovec_is_set(&tpm2_key)) {
struct tpm2_credential_header *t;
- t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
+ t = (struct tpm2_credential_header*) ((uint8_t*) output.iov_base + p);
t->pcr_mask = htole64(tpm2_hash_pcr_mask);
t->pcr_bank = htole16(tpm2_pcr_bank);
t->primary_alg = htole16(tpm2_primary_alg);
- t->blob_size = htole32(tpm2_blob_size);
- t->policy_hash_size = htole32(tpm2_policy_hash_size);
- memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size);
- memcpy(t->policy_hash_and_blob + tpm2_blob_size, tpm2_policy_hash, tpm2_policy_hash_size);
+ t->blob_size = htole32(tpm2_blob.iov_len);
+ t->policy_hash_size = htole32(tpm2_policy_hash.iov_len);
+ memcpy(t->policy_hash_and_blob, tpm2_blob.iov_base, tpm2_blob.iov_len);
+ memcpy(t->policy_hash_and_blob + tpm2_blob.iov_len, tpm2_policy_hash.iov_base, tpm2_policy_hash.iov_len);
- p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size);
+ p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len);
}
- if (pubkey) {
+ if (iovec_is_set(&pubkey)) {
struct tpm2_public_key_credential_header *z;
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output.iov_base + p);
z->pcr_mask = htole64(tpm2_pubkey_pcr_mask);
- z->size = htole32(pubkey_size);
- memcpy(z->data, pubkey, pubkey_size);
+ z->size = htole32(pubkey.iov_len);
+ memcpy(z->data, pubkey.iov_base, pubkey.iov_len);
- p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size);
+ p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len);
}
/* Pass the encrypted + TPM2 header as AAD */
- if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1)
+ if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
memcpy_safe(m->name, name, ml);
/* And encrypt the metadata header */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Then encrypt the plaintext */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, input, input_size) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Finalize */
- if (EVP_EncryptFinal_ex(context, (uint8_t*) output + p, &added) != 1)
+ if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
- assert(p <= output_size - tsz);
+ assert(p <= output.iov_len - tsz);
/* Append tag */
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output + p) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s",
ERR_error_string(ERR_get_error(), NULL));
p += tsz;
- assert(p <= output_size);
+ assert(p <= output.iov_len);
+ output.iov_len = p;
- if (DEBUG_LOGGING && input_size > 0) {
+ if (DEBUG_LOGGING && input->iov_len > 0) {
size_t base64_size;
- base64_size = DIV_ROUND_UP(p * 4, 3); /* Include base64 size increase in debug output */
- assert(base64_size >= input_size);
- log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input_size, base64_size, base64_size * 100 / input_size - 100);
+ base64_size = DIV_ROUND_UP(output.iov_len * 4, 3); /* Include base64 size increase in debug output */
+ assert(base64_size >= input->iov_len);
+ log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input->iov_len, base64_size, base64_size * 100 / input->iov_len - 100);
}
- *ret = TAKE_PTR(output);
- *ret_size = p;
-
+ *ret = TAKE_STRUCT(output);
return 0;
}
usec_t validate_timestamp,
const char *tpm2_device,
const char *tpm2_signature_path,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec host_key = {}, plaintext = {}, tpm2_key = {};
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs;
struct encrypted_credential_header *h;
struct metadata_credential_header *m;
uint8_t md[SHA256_DIGEST_LENGTH];
- bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk;
+ bool with_tpm2, with_tpm2_pk, with_host_key, with_null;
const EVP_CIPHER *cc;
+ size_t p, hs;
int r, added;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
- h = (struct encrypted_credential_header*) input;
+ h = (struct encrypted_credential_header*) input->iov_base;
/* The ID must fit in, for the current and all future formats */
- if (input_size < sizeof(h->id))
+ if (input->iov_len < sizeof(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk;
- is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT);
+ with_null = sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL);
- if (!with_host_key && !with_tpm2 && !is_tpm2_absent)
+ if (!with_host_key && !with_tpm2 && !with_null)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
if (with_tpm2_pk) {
return log_error_errno(r, "Failed to load pcr signature: %m");
}
- if (is_tpm2_absent) {
+ if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) {
/* So this is a credential encrypted with a zero length key. We support this to cover for the
* case where neither a host key not a TPM2 are available (specifically: initrd environments
* where the host key is not yet accessible and no TPM2 chip exists at all), to minimize
}
/* Now we know the minimum header size */
- if (input_size < offsetof(struct encrypted_credential_header, iv))
+ if (input->iov_len < offsetof(struct encrypted_credential_header, iv))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
/* Verify some basic header values */
/* Ensure we have space for the full header now (we don't know the size of the name hence this is a
* lower limit only) */
- if (input_size <
+ if (input->iov_len <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
if (with_tpm2) {
#if HAVE_TPM2
- struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p);
+ struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input->iov_base + p);
struct tpm2_public_key_credential_header *z = NULL;
if (!TPM2_PCR_MASK_VALID(t->pcr_mask))
/* Ensure we have space for the full TPM2 header now (still don't know the name, and its size
* though, hence still just a lower limit test only) */
- if (input_size <
+ if (input->iov_len <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
le32toh(t->policy_hash_size));
if (with_tpm2_pk) {
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input->iov_base + p);
if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
if (le32toh(z->size) > PUBLIC_KEY_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size.");
- if (input_size <
+ if (input->iov_len <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
r = tpm2_unseal(tpm2_context,
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
- z ? z->data : NULL,
- z ? le32toh(z->size) : 0,
+ z ? &IOVEC_MAKE(z->data, le32toh(z->size)) : NULL,
z ? le64toh(z->pcr_mask) : 0,
signature_json,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
le16toh(t->primary_alg),
- t->policy_hash_and_blob,
- le32toh(t->blob_size),
- t->policy_hash_and_blob + le32toh(t->blob_size),
- le32toh(t->policy_hash_size),
- /* srk_buf= */ NULL,
- /* srk_buf_size= */ 0,
- &tpm2_key,
- &tpm2_key_size);
+ &IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)),
+ &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)),
+ /* srk= */ NULL,
+ &tpm2_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
#else
}
if (with_host_key) {
- r = get_credential_host_secret(
- 0,
- &host_key,
- &host_key_size);
+ r = get_credential_host_secret(/* flags= */ 0, &host_key);
if (r < 0)
return log_error_errno(r, "Failed to determine local credential key: %m");
}
- if (is_tpm2_absent)
+ if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Warning: using a null key for decryption and authentication. Confidentiality or authenticity are not provided.");
- sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
+ sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
assert_se(cc = EVP_aes_256_gcm());
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptUpdate(context, NULL, &added, input, p) != 1)
+ if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext = malloc(input_size - p - le32toh(h->tag_size));
- if (!plaintext)
+ plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size));
+ if (!plaintext.iov_base)
return -ENOMEM;
if (EVP_DecryptUpdate(
context,
- plaintext,
+ plaintext.iov_base,
&added,
- (uint8_t*) input + p,
- input_size - p - le32toh(h->tag_size)) != 1)
+ (uint8_t*) input->iov_base + p,
+ input->iov_len - p - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= input_size - p - le32toh(h->tag_size));
- plaintext_size = added;
+ assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size));
+ plaintext.iov_len = added;
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input + input_size - le32toh(h->tag_size)) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext + plaintext_size, &added) != 1)
+ if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext_size += added;
+ plaintext.iov_len += added;
- if (plaintext_size < ALIGN8(offsetof(struct metadata_credential_header, name)))
+ if (plaintext.iov_len < ALIGN8(offsetof(struct metadata_credential_header, name)))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
- m = plaintext;
+ m = plaintext.iov_base;
if (le64toh(m->timestamp) != USEC_INFINITY &&
le64toh(m->not_after) != USEC_INFINITY &&
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name too long, refusing.");
hs = ALIGN8(offsetof(struct metadata_credential_header, name) + le32toh(m->name_size));
- if (plaintext_size < hs)
+ if (plaintext.iov_len < hs)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
if (le32toh(m->name_size) > 0) {
}
if (ret) {
- char *without_metadata;
+ _cleanup_(iovec_done_erase) struct iovec without_metadata = {};
- without_metadata = memdup((uint8_t*) plaintext + hs, plaintext_size - hs);
- if (!without_metadata)
+ without_metadata.iov_len = plaintext.iov_len - hs;
+ without_metadata.iov_base = memdup_suffix0((uint8_t*) plaintext.iov_base + hs, without_metadata.iov_len);
+ if (!without_metadata.iov_base)
return log_oom();
- *ret = without_metadata;
+ *ret = TAKE_STRUCT(without_metadata);
}
- if (ret_size)
- *ret_size = plaintext_size - hs;
-
return 0;
}
#else
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS = 1 << 2,
} CredentialSecretFlags;
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size);
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret);
int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed);
+typedef enum CredentialFlags {
+ CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */
+} CredentialFlags;
+
/* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of
* both, as well as one with a fixed zero length key if TPM2 is missing (the latter of course provides no
* authenticity or confidentiality, but is still useful for integrity protection, and makes things simpler
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53)
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK \
SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05)
-#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
+#define CRED_AES256_GCM_BY_NULL SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
/* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
* an initrd-specific automatic mode (i.e. tpm2 if firmware can do it, otherwise fixed zero-length key, and
#define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01)
#define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71)
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size);
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"FIDO2 token data lacks 'fido2-credential' field.");
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
+ r = unbase64mem(json_variant_string(w), &cid, &cid_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'fido2-credential' field.");
assert(!salt);
assert(salt_size == 0);
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
+ r = unbase64mem(json_variant_string(w), &salt, &salt_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded salt.");
#include "alloc-util.h"
#include "dev-setup.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "label-util.h"
#include "lock-util.h"
#include "log.h"
uid_t uid,
gid_t gid) {
- static const struct {
- const char *name;
- mode_t mode;
- } table[] = {
- { "inaccessible", S_IFDIR | 0755 },
- { "inaccessible/reg", S_IFREG | 0000 },
- { "inaccessible/dir", S_IFDIR | 0000 },
- { "inaccessible/fifo", S_IFIFO | 0000 },
- { "inaccessible/sock", S_IFSOCK | 0000 },
+ static const mode_t table[] = {
+ S_IFREG,
+ S_IFDIR,
+ S_IFIFO,
+ S_IFSOCK,
/* The following two are likely to fail if we lack the privs for it (for example in an userns
* environment, if CAP_SYS_MKNOD is missing, or if a device node policy prohibits creation of
* should implement falling back to use a different node then, for example
* <root>/inaccessible/sock, which is close enough in behaviour and semantics for most uses.
*/
- { "inaccessible/chr", S_IFCHR | 0000 },
- { "inaccessible/blk", S_IFBLK | 0000 },
+ S_IFCHR,
+ S_IFBLK,
+
+ /* NB: S_IFLNK is not listed here, as there is no such thing as an inaccessible symlink */
};
+ _cleanup_close_ int parent_fd = -EBADF, inaccessible_fd = -EBADF;
int r;
if (!parent_dir)
BLOCK_WITH_UMASK(0000);
+ parent_fd = open(parent_dir, O_DIRECTORY|O_CLOEXEC|O_PATH, 0);
+ if (parent_fd < 0)
+ return -errno;
+
+ inaccessible_fd = open_mkdir_at(parent_fd, "inaccessible", O_CLOEXEC, 0755);
+ if (inaccessible_fd < 0)
+ return inaccessible_fd;
+
/* Set up inaccessible (and empty) file nodes of all types. This are used to as mount sources for over-mounting
* ("masking") file nodes that shall become inaccessible and empty for specific containers or services. We try
* 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. */
- for (size_t i = 0; i < ELEMENTSOF(table); i++) {
+ FOREACH_ARRAY(m, table, ELEMENTSOF(table)) {
_cleanup_free_ char *path = NULL;
+ mode_t inode_type = *m;
+ const char *fn;
- path = path_join(parent_dir, table[i].name);
+ fn = inode_type_to_string(inode_type);
+ path = path_join(parent_dir, fn);
if (!path)
return log_oom();
- if (S_ISDIR(table[i].mode))
- r = mkdir_label(path, table[i].mode & 07777);
+ if (S_ISDIR(inode_type))
+ r = mkdirat_label(inaccessible_fd, fn, 0000);
else
- r = mknod_label(path, table[i].mode, makedev(0, 0));
- if (r < 0) {
+ r = RET_NERRNO(mknodat(inaccessible_fd, fn, inode_type | 0000, makedev(0, 0)));
+ if (r == -EEXIST) {
+ if (fchmodat(inaccessible_fd, fn, 0000, AT_SYMLINK_NOFOLLOW) < 0)
+ log_debug_errno(errno, "Failed to adjust access mode of existing inode '%s', ignoring: %m", path);
+ } else if (r < 0) {
log_debug_errno(r, "Failed to create '%s', ignoring: %m", path);
continue;
}
- if (uid != UID_INVALID || gid != GID_INVALID) {
- if (lchown(path, uid, gid) < 0)
- log_debug_errno(errno, "Failed to chown '%s': %m", path);
- }
+ if (uid_is_valid(uid) || gid_is_valid(gid))
+ if (fchownat(inaccessible_fd, fn, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)
+ log_debug_errno(errno, "Failed to chown '%s', ignoring: %m", path);
}
+ if (fchmod(inaccessible_fd, 0555) < 0)
+ log_debug_errno(errno, "Failed to mark inaccessible directory read-only, ignoring: %m");
+
return 0;
}
#include "strv.h"
#include "time-util.h"
#include "utf8.h"
+#include "vpick.h"
#include "xattr-util.h"
static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
return 0;
}
-static int extract_pretty(
+static int extract_image_basename(
const char *path,
- const char *class_suffix,
- const char *format_suffix,
- char **ret) {
+ const char *class_suffix, /* e.g. ".sysext" (this is an optional suffix) */
+ char **format_suffixes, /* e.g. ".raw" (one of these will be required) */
+ char **ret_basename,
+ char **ret_suffix) {
- _cleanup_free_ char *name = NULL;
+ _cleanup_free_ char *name = NULL, *suffix = NULL;
int r;
assert(path);
- assert(ret);
r = path_extract_filename(path, &name);
if (r < 0)
return r;
- if (format_suffix) {
- char *e = endswith(name, format_suffix);
+ if (format_suffixes) {
+ char *e = endswith_strv(name, format_suffixes);
if (!e) /* Format suffix is required */
return -EINVAL;
+ if (ret_suffix) {
+ suffix = strdup(e);
+ if (!suffix)
+ return -ENOMEM;
+ }
+
*e = 0;
}
if (class_suffix) {
char *e = endswith(name, class_suffix);
- if (e) /* Class suffix is optional */
+ if (e) { /* Class suffix is optional */
+ if (ret_suffix) {
+ _cleanup_free_ char *j = strjoin(e, suffix);
+ if (!j)
+ return -ENOMEM;
+
+ free_and_replace(suffix, j);
+ }
+
*e = 0;
+ }
}
if (!image_name_is_valid(name))
return -EINVAL;
- *ret = TAKE_PTR(name);
+ if (ret_suffix)
+ *ret_suffix = TAKE_PTR(suffix);
+
+ if (ret_basename)
+ *ret_basename = TAKE_PTR(name);
+
return 0;
}
return 0;
if (!pretty) {
- r = extract_pretty(filename, image_class_suffix_to_string(c), NULL, &pretty_buffer);
+ r = extract_image_basename(
+ filename,
+ image_class_suffix_to_string(c),
+ /* format_suffix= */ NULL,
+ &pretty_buffer,
+ /* ret_suffix= */ NULL);
if (r < 0)
return r;
(void) fd_getcrtime_at(dfd, filename, AT_SYMLINK_FOLLOW, &crtime);
if (!pretty) {
- r = extract_pretty(filename, image_class_suffix_to_string(c), ".raw", &pretty_buffer);
+ r = extract_image_basename(
+ filename,
+ image_class_suffix_to_string(c),
+ STRV_MAKE(".raw"),
+ &pretty_buffer,
+ /* ret_suffix= */ NULL);
if (r < 0)
return r;
return 0;
if (!pretty) {
- r = extract_pretty(filename, NULL, NULL, &pretty_buffer);
+ r = extract_image_basename(
+ filename,
+ /* class_suffix= */ NULL,
+ /* format_suffix= */ NULL,
+ &pretty_buffer,
+ /* ret_suffix= */ NULL);
if (r < 0)
return r;
return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
}
+static char **make_possible_filenames(ImageClass class, const char *image_name) {
+ _cleanup_strv_free_ char **l = NULL;
+
+ assert(image_name);
+
+ FOREACH_STRING(v_suffix, "", ".v")
+ FOREACH_STRING(format_suffix, "", ".raw") {
+ _cleanup_free_ char *j = NULL;
+ const char *class_suffix;
+
+ class_suffix = image_class_suffix_to_string(class);
+ if (class_suffix) {
+ j = strjoin(image_name, class_suffix, format_suffix, v_suffix);
+ if (!j)
+ return NULL;
+
+ if (strv_consume(&l, TAKE_PTR(j)) < 0)
+ return NULL;
+ }
+
+ j = strjoin(image_name, format_suffix, v_suffix);
+ if (!j)
+ return NULL;
+
+ if (strv_consume(&l, TAKE_PTR(j)) < 0)
+ return NULL;
+ }
+
+ return TAKE_PTR(l);
+}
+
int image_find(ImageClass class,
const char *name,
const char *root,
if (!image_name_is_valid(name))
return -ENOENT;
+ _cleanup_strv_free_ char **names = make_possible_filenames(class, name);
+ if (!names)
+ return -ENOMEM;
+
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
* to symlink block devices into the search path. (For now, we disable that when operating
* relative to some root directory.) */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
- if (fstatat(dirfd(d), name, &st, flags) < 0) {
- _cleanup_free_ char *raw = NULL;
- if (errno != ENOENT)
- return -errno;
+ STRV_FOREACH(n, names) {
+ _cleanup_free_ char *fname_buf = NULL;
+ const char *fname = *n;
- raw = strjoin(name, ".raw");
- if (!raw)
- return -ENOMEM;
+ if (fstatat(dirfd(d), fname, &st, flags) < 0) {
+ if (errno != ENOENT)
+ return -errno;
- if (fstatat(dirfd(d), raw, &st, flags) < 0) {
- if (errno == ENOENT)
+ continue; /* Vanished while we were looking at it */
+ }
+
+ if (endswith(fname, ".raw")) {
+ if (!S_ISREG(st.st_mode)) {
+ log_debug("Ignoring non-regular file '%s' with .raw suffix.", fname);
continue;
+ }
- return -errno;
- }
+ } else if (endswith(fname, ".v")) {
- if (!S_ISREG(st.st_mode))
- continue;
+ if (!S_ISDIR(st.st_mode)) {
+ log_debug("Ignoring non-directory file '%s' with .v suffix.", fname);
+ continue;
+ }
+
+ _cleanup_free_ char *suffix = NULL;
+ suffix = strdup(ASSERT_PTR(startswith(fname, name)));
+ if (!suffix)
+ return -ENOMEM;
+
+ *ASSERT_PTR(endswith(suffix, ".v")) = 0;
+
+ _cleanup_free_ char *vp = path_join(resolved, fname);
+ if (!vp)
+ return -ENOMEM;
+
+ PickFilter filter = {
+ .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,
+ };
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ r = path_pick(root,
+ /* toplevel_fd= */ AT_FDCWD,
+ vp,
+ &filter,
+ PICK_ARCHITECTURE|PICK_TRIES,
+ &result);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+ continue;
+ }
+ if (!result.path) {
+ log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+ continue;
+ }
- r = image_make(class, name, dirfd(d), resolved, raw, &st, ret);
+ /* Refresh the stat data for the discovered target */
+ st = result.st;
- } else {
- if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
+ _cleanup_free_ char *bn = NULL;
+ r = path_extract_filename(result.path, &bn);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
+ continue;
+ }
+
+ fname_buf = path_join(fname, bn);
+ if (!fname_buf)
+ return log_oom();
+
+ fname = fname_buf;
+
+ } else if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+ log_debug("Ignoring non-directory and non-block device file '%s' without suffix.", fname);
continue;
+ }
- r = image_make(class, name, dirfd(d), resolved, name, &st, ret);
- }
- if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
- continue;
- if (r < 0)
- return r;
+ r = image_make(class, name, dirfd(d), resolved, fname, &st, ret);
+ if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
+ continue;
+ if (r < 0)
+ return r;
- if (ret)
- (*ret)->discoverable = true;
+ if (ret)
+ (*ret)->discoverable = true;
- return 1;
+ return 1;
+ }
}
if (class == IMAGE_MACHINE && streq(name, ".host")) {
if (ret)
(*ret)->discoverable = true;
- return r;
+ return 1;
}
return -ENOENT;
return r;
FOREACH_DIRENT_ALL(de, d, return -errno) {
+ _cleanup_free_ char *pretty = NULL, *fname_buf = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
- _cleanup_free_ char *pretty = NULL;
+ const char *fname = de->d_name;
struct stat st;
int flags;
- if (dot_or_dot_dot(de->d_name))
+ if (dot_or_dot_dot(fname))
continue;
/* As mentioned above, we follow symlinks on this fstatat(), because we want to
* permit people to symlink block devices into the search path. */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
- if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) {
+ if (fstatat(dirfd(d), fname, &st, flags) < 0) {
if (errno == ENOENT)
continue;
return -errno;
}
- if (S_ISREG(st.st_mode))
- r = extract_pretty(de->d_name, image_class_suffix_to_string(class), ".raw", &pretty);
- else if (S_ISDIR(st.st_mode))
- r = extract_pretty(de->d_name, image_class_suffix_to_string(class), NULL, &pretty);
- else if (S_ISBLK(st.st_mode))
- r = extract_pretty(de->d_name, NULL, NULL, &pretty);
- else {
- log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", de->d_name);
- continue;
- }
- if (r < 0) {
- log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", de->d_name);
+ if (S_ISREG(st.st_mode)) {
+ r = extract_image_basename(
+ fname,
+ image_class_suffix_to_string(class),
+ STRV_MAKE(".raw"),
+ &pretty,
+ /* suffix= */ NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+ continue;
+ }
+ } else if (S_ISDIR(st.st_mode)) {
+ const char *v;
+
+ v = endswith(fname, ".v");
+ if (v) {
+ _cleanup_free_ char *suffix = NULL, *nov = NULL;
+
+ nov = strndup(fname, v - fname); /* Chop off the .v */
+ if (!nov)
+ return -ENOMEM;
+
+ r = extract_image_basename(
+ nov,
+ image_class_suffix_to_string(class),
+ STRV_MAKE(".raw", ""),
+ &pretty,
+ &suffix);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like a versioned image.", fname);
+ continue;
+ }
+
+ _cleanup_free_ char *vp = path_join(resolved, fname);
+ if (!vp)
+ return -ENOMEM;
+
+ PickFilter filter = {
+ .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,
+ };
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ r = path_pick(root,
+ /* toplevel_fd= */ AT_FDCWD,
+ vp,
+ &filter,
+ PICK_ARCHITECTURE|PICK_TRIES,
+ &result);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+ continue;
+ }
+ if (!result.path) {
+ log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+ continue;
+ }
+
+ /* Refresh the stat data for the discovered target */
+ st = result.st;
+
+ _cleanup_free_ char *bn = NULL;
+ r = path_extract_filename(result.path, &bn);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
+ continue;
+ }
+
+ fname_buf = path_join(fname, bn);
+ if (!fname_buf)
+ return log_oom();
+
+ fname = fname_buf;
+ } else {
+ r = extract_image_basename(
+ fname,
+ image_class_suffix_to_string(class),
+ /* format_suffix= */ NULL,
+ &pretty,
+ /* ret_suffix= */ NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+ continue;
+ }
+ }
+
+ } else if (S_ISBLK(st.st_mode)) {
+ r = extract_image_basename(
+ fname,
+ /* class_suffix= */ NULL,
+ /* format_suffix= */ NULL,
+ &pretty,
+ /* ret_v_suffix= */ NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+ continue;
+ }
+ } else {
+ log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", fname);
continue;
}
if (hashmap_contains(h, pretty))
continue;
- r = image_make(class, pretty, dirfd(d), resolved, de->d_name, &st, &image);
+ r = image_make(class, pretty, dirfd(d), resolved, fname, &st, &image);
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
return 0;
}
+static void make_lock_dir(void) {
+ (void) mkdir_p("/run/systemd/nspawn", 0755);
+ (void) mkdir("/run/systemd/nspawn/locks", 0700);
+}
+
int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) {
_cleanup_free_ char *p = NULL;
LockFile t = LOCK_FILE_INIT;
}
if (p) {
- (void) mkdir_p("/run/systemd/nspawn/locks", 0700);
+ make_lock_dir();
r = make_lock_file(p, operation, global);
if (r < 0) {
return 0;
}
- (void) mkdir_p("/run/systemd/nspawn/locks", 0700);
+ make_lock_dir();
p = strjoina("/run/systemd/nspawn/locks/name-", name);
return make_lock_file(p, operation, ret);
/* Accept trailing slashes */
if (p[strspn(p, "/")] == 0)
return true;
-
}
return false;
}
if (text) {
- r = unhexmem(text, strlen(text), &root_hash, &root_hash_size);
+ r = unhexmem(text, &root_hash, &root_hash_size);
if (r < 0)
return r;
if (root_hash_size < sizeof(sd_id128_t))
if (!json_variant_is_string(rh))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'rootHash' field of signature JSON object is not a string.");
- r = unhexmem(json_variant_string(rh), SIZE_MAX, &root_hash, &root_hash_size);
+ r = unhexmem(json_variant_string(rh), &root_hash, &root_hash_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse root hash field: %m");
if (!json_variant_is_string(sig))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'signature' field of signature JSON object is not a string.");
- r = unbase64mem(json_variant_string(sig), SIZE_MAX, &root_hash_sig, &root_hash_sig_size);
+ r = unbase64mem(json_variant_string(sig), &root_hash_sig, &root_hash_sig_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse signature field: %m");
if (rfd < 0)
return -errno;
- r = find_esp_and_warn_at(rfd, path, unprivileged_mode,
- ret_path ? &p : NULL,
- ret_part ? &part : NULL,
- ret_pstart ? &pstart : NULL,
- ret_psize ? &psize : NULL,
- ret_uuid ? &uuid : NULL,
- ret_devid ? &devid : NULL);
+ r = find_esp_and_warn_at(
+ rfd,
+ path,
+ unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_part ? &part : NULL,
+ ret_pstart ? &pstart : NULL,
+ ret_psize ? &psize : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
if (r < 0)
return r;
}
int find_xbootldr_and_warn(
- const char *root,
- const char *path,
- int unprivileged_mode,
- char **ret_path,
- sd_id128_t *ret_uuid,
- dev_t *ret_devid) {
+ const char *root,
+ const char *path,
+ int unprivileged_mode,
+ char **ret_path,
+ sd_id128_t *ret_uuid,
+ dev_t *ret_devid) {
_cleanup_close_ int rfd = -EBADF;
_cleanup_free_ char *p = NULL;
if (rfd < 0)
return -errno;
- r = find_xbootldr_and_warn_at(rfd, path, unprivileged_mode,
- ret_path ? &p : NULL,
- ret_uuid ? &uuid : NULL,
- ret_devid ? &devid : NULL);
+ r = find_xbootldr_and_warn_at(
+ rfd,
+ path,
+ unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
if (r < 0)
return r;
#include "group-record.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
GroupRecord* group_record_new(void) {
if (r < 0)
return r;
- r = json_dispatch(h->json, group_dispatch_table, json_flags, h);
+ r = json_dispatch(h->json, group_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h);
if (r < 0)
return r;
return 0;
}
+static bool json_variant_is_sensitive_recursive(JsonVariant *v) {
+ if (!v)
+ return false;
+ if (json_variant_is_sensitive(v))
+ return true;
+ if (!json_variant_is_regular(v))
+ return false;
+ if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+ return false;
+ if (v->is_reference)
+ return json_variant_is_sensitive_recursive(v->reference);
+
+ for (size_t i = 0; i < json_variant_elements(v); i++)
+ if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
+ return true;
+
+ return false;
+}
+
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
_cleanup_(memstream_done) MemStream m = {};
size_t sz;
if (flags & JSON_FORMAT_OFF)
return -ENOEXEC;
+ if ((flags & JSON_FORMAT_REFUSE_SENSITIVE))
+ if (json_variant_is_sensitive_recursive(v))
+ return -EPERM;
+
f = memstream_init(&m);
if (!f)
return -ENOMEM;
break;
}
- case _JSON_BUILD_IOVEC_BASE64: {
+ case _JSON_BUILD_IOVEC_BASE64:
+ case _JSON_BUILD_IOVEC_HEX: {
const struct iovec *iov;
if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
goto finish;
}
- iov = ASSERT_PTR(va_arg(ap, const struct iovec*));
+ iov = va_arg(ap, const struct iovec*);
if (current->n_suppress == 0) {
- r = json_variant_new_base64(&add, iov->iov_base, iov->iov_len);
+ if (iov)
+ r = command == _JSON_BUILD_IOVEC_BASE64 ? json_variant_new_base64(&add, iov->iov_base, iov->iov_len) :
+ json_variant_new_hex(&add, iov->iov_base, iov->iov_len);
+ else
+ r = json_variant_new_string(&add, "");
if (r < 0)
goto finish;
}
done++;
} else {
- json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
+ if (flags & JSON_ALLOW_EXTENSIONS) {
+ json_log(value, flags, 0, "Unrecognized object field '%s', assuming extension.", json_variant_string(key));
+ continue;
+ }
+ json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
if (flags & JSON_PERMISSIVE)
continue;
if (!json_variant_is_string(v))
return -EINVAL;
- return unbase64mem(json_variant_string(v), SIZE_MAX, ret, ret_size);
+ return unbase64mem(json_variant_string(v), ret, ret_size);
}
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) {
if (!json_variant_is_string(v))
return -EINVAL;
- return unhexmem(json_variant_string(v), SIZE_MAX, ret, ret_size);
+ return unhexmem(json_variant_string(v), ret, ret_size);
}
static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column);
typedef enum JsonFormatFlags {
- JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
- JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
- JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */
- JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */
- JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */
- JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */
- JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
- JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
- JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
- JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
- JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
+ JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
+ JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
+ JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */
+ JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */
+ JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */
+ JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */
+ JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
+ JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
+ JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
+ JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
+ JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
+ JSON_FORMAT_REFUSE_SENSITIVE = 1 << 11, /* return EPERM if any node in the tree is marked as senstitive */
} JsonFormatFlags;
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
_JSON_BUILD_IOVEC_BASE64,
_JSON_BUILD_BASE32HEX,
_JSON_BUILD_HEX,
+ _JSON_BUILD_IOVEC_HEX,
_JSON_BUILD_OCTESCAPE,
_JSON_BUILD_ID128,
_JSON_BUILD_UUID,
#define JSON_BUILD_IOVEC_BASE64(iov) _JSON_BUILD_IOVEC_BASE64, (const struct iovec*) { iov }
#define JSON_BUILD_BASE32HEX(p, n) _JSON_BUILD_BASE32HEX, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, (const void*) { p }, (size_t) { n }
+#define JSON_BUILD_IOVEC_HEX(iov) _JSON_BUILD_IOVEC_HEX, (const struct iovec*) { iov }
#define JSON_BUILD_OCTESCAPE(p, n) _JSON_BUILD_OCTESCAPE, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, (const sd_id128_t*) { &(id) }
#define JSON_BUILD_UUID(id) _JSON_BUILD_UUID, (const sd_id128_t*) { &(id) }
#define JSON_BUILD_PAIR_BASE64(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_BASE64(p, n))
#define JSON_BUILD_PAIR_IOVEC_BASE64(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_BASE64(iov))
#define JSON_BUILD_PAIR_HEX(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_HEX(p, n))
+#define JSON_BUILD_PAIR_IOVEC_HEX(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_HEX(iov))
#define JSON_BUILD_PAIR_ID128(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_ID128(id))
#define JSON_BUILD_PAIR_UUID(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_UUID(id))
#define JSON_BUILD_PAIR_BYTE_ARRAY(name, v, n) JSON_BUILD_PAIR(name, JSON_BUILD_BYTE_ARRAY(v, n))
* entry, as well the bitmask specified for json_log() calls */
typedef enum JsonDispatchFlags {
/* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter */
- JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */
- JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */
- JSON_LOG = 1 << 2, /* Should the parser log about errors? */
- JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */
- JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */
+ JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this field or object? */
+ JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */
+ JSON_LOG = 1 << 2, /* Should the parser log about errors? */
+ JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */
+ JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */
+ JSON_ALLOW_EXTENSIONS = 1 << 5, /* Subset of JSON_PERMISSIVE: allow additional fields, but no other permissive handling */
/* The following two may be passed into log_json() in addition to those above */
- JSON_DEBUG = 1 << 5, /* Indicates that this log message is a debug message */
- JSON_WARNING = 1 << 6, /* Indicates that this log message is a warning message */
+ JSON_DEBUG = 1 << 6, /* Indicates that this log message is a debug message */
+ JSON_WARNING = 1 << 7, /* Indicates that this log message is a warning message */
} JsonDispatchFlags;
typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
assert_cc(sizeof(int32_t) == sizeof(int));
#define json_dispatch_int json_dispatch_int32
+#define JSON_DISPATCH_ENUM_DEFINE(name, type, func) \
+ int name(const char *n, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { \
+ type *c = ASSERT_PTR(userdata); \
+ \
+ assert(variant); \
+ \
+ if (json_variant_is_null(variant)) { \
+ *c = (type) -EINVAL; \
+ return 0; \
+ } \
+ \
+ if (!json_variant_is_string(variant)) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(n)); \
+ \
+ type cc = func(json_variant_string(variant)); \
+ if (cc < 0) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized.", strna(n)); \
+ \
+ *c = cc; \
+ return 0; \
+ }
+
static inline int json_dispatch_level(JsonDispatchFlags flags) {
/* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as
int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size);
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size);
+static inline int json_variant_unbase64_iovec(JsonVariant *v, struct iovec *ret) {
+ return json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
+static inline int json_variant_unhex_iovec(JsonVariant *v, struct iovec *ret) {
+ return json_variant_unhex(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
const char *json_variant_type_to_string(JsonVariantType t);
JsonVariantType json_variant_type_from_string(const char *s);
return 0;
}
+ if (identifier && set_contains(j->exclude_syslog_identifiers, identifier))
+ return 0;
+
if (!(flags & OUTPUT_SHOW_ALL))
strip_tab_ansi(&message, &message_len, highlight_shifted);
'varlink-idl.c',
'varlink-io.systemd.c',
'varlink-io.systemd.Credentials.c',
+ 'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.ManagedOOM.c',
'varlink-io.systemd.Network.c',
'verbs.c',
'vlan-util.c',
'volatile-util.c',
+ 'vpick.c',
'wall.c',
'watchdog.c',
'web-util.c',
if (!(ent->mask & MNT_INVERT))
mount_flags |= ent->id;
- else if (mount_flags & ent->id)
- mount_flags ^= ent->id;
+ else
+ mount_flags &= ~ent->id;
break;
}
/* A generic destructor for pam_set_data() that just frees the specified data */
free(data);
}
+
+int pam_get_item_many_internal(pam_handle_t *handle, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, handle);
+ for (;;) {
+ int item_type = va_arg(ap, int);
+
+ if (item_type <= 0) {
+ r = PAM_SUCCESS;
+ break;
+ }
+
+ const void **value = ASSERT_PTR(va_arg(ap, const void **));
+
+ r = pam_get_item(handle, item_type, value);
+ if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
+ break;
+ }
+ va_end(ap);
+
+ return r;
+}
+
+int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) {
+ va_list args;
+ int r;
+
+ assert(handle);
+ assert(fmt);
+
+ /* This is just like pam_prompt(), but does not noisily (i.e. beyond LOG_DEBUG) log on its own, but leaves that to the caller */
+
+ _cleanup_free_ char *msg = NULL;
+ va_start(args, fmt);
+ r = vasprintf(&msg, fmt, args);
+ va_end(args);
+ if (r < 0)
+ return PAM_BUF_ERR;
+
+ const struct pam_conv *conv = NULL;
+ r = pam_get_item(handle, PAM_CONV, (const void**) &conv);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM))
+ return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Failed to get conversation function structure: @PAMERR@");
+ if (!conv || !conv->conv) {
+ pam_syslog(handle, LOG_DEBUG, "No conversation function.");
+ return PAM_SYSTEM_ERR;
+ }
+
+ struct pam_message message = {
+ .msg_style = style,
+ .msg = msg,
+ };
+ const struct pam_message *pmessage = &message;
+ _cleanup_free_ struct pam_response *response = NULL;
+ r = conv->conv(1, &pmessage, &response, conv->appdata_ptr);
+ _cleanup_(erase_and_freep) char *rr = response ? response->resp : NULL; /* make sure string is freed + erased */
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Conversation function failed: @PAMERR@");
+
+ if (ret_response)
+ *ret_response = TAKE_PTR(rr);
+
+ return PAM_SUCCESS;
+}
int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
+
+int pam_get_item_many_internal(pam_handle_t *handle, ...);
+
+#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1)
+
+int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) _printf_(4,5);
uint16_t *nr_ports,
uint16_t *port_min) {
+ int r;
+
assert(token);
assert(nr_ports);
assert(port_min);
*nr_ports = *port_min = 0;
else {
uint16_t mn = 0, mx = 0;
- int r = parse_ip_port_range(token, &mn, &mx);
+ r = parse_ip_port_range(token, &mn, &mx, /* allow_zero = */ true);
if (r < 0)
return r;
*ip_protocol = proto;
*nr_ports = nr;
*port_min = mn;
+
return 0;
}
int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string);
void (*sym_p11_kit_uri_free)(P11KitUri *uri);
CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs);
+CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attribute)(P11KitUri *uri, CK_ATTRIBUTE_TYPE attr_type);
+int (*sym_p11_kit_uri_set_attribute)(P11KitUri *uri, CK_ATTRIBUTE_PTR attr);
CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri);
CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri);
CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri);
DLSYM_ARG(p11_kit_uri_format),
DLSYM_ARG(p11_kit_uri_free),
DLSYM_ARG(p11_kit_uri_get_attributes),
+ DLSYM_ARG(p11_kit_uri_get_attribute),
+ DLSYM_ARG(p11_kit_uri_set_attribute),
DLSYM_ARG(p11_kit_uri_get_module_info),
DLSYM_ARG(p11_kit_uri_get_slot_info),
DLSYM_ARG(p11_kit_uri_get_token_info),
if (mechanism_info.flags & CKF_EC_COMPRESS) {
#if HAVE_OPENSSL
log_debug("CKM_ECDH1_DERIVE accepts compressed EC points only, trying to convert.");
- size_t compressed_point_size;
+ size_t compressed_point_size = 0; /* Explicit initialization to appease gcc */
r = ecc_convert_to_compressed(m, session, object, encrypted_data, encrypted_data_size, &compressed_point, &compressed_point_size);
if (r < 0)
return r;
extern int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string);
extern void (*sym_p11_kit_uri_free)(P11KitUri *uri);
extern CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs);
+extern CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attribute)(P11KitUri *uri, CK_ATTRIBUTE_TYPE attr_type);
+extern int (*sym_p11_kit_uri_set_attribute)(P11KitUri *uri, CK_ATTRIBUTE_PTR attr);
extern CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri);
extern CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri);
extern CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri);
#define DEFINE_HEX_PTR(name, hex) \
_cleanup_free_ void *name = NULL; \
size_t name##_len = 0; \
- assert_se(unhexmem(hex, strlen_ptr(hex), &name, &name##_len) >= 0);
+ assert_se(unhexmem_full(hex, strlen_ptr(hex), false, &name, &name##_len) >= 0);
#define TEST_REQ_RUNNING_SYSTEMD(x) \
if (sd_booted() > 0) { \
_cleanup_free_ void *buf = NULL;
size_t buf_size = 0;
- r = unhexmem(p, SIZE_MAX, &buf, &buf_size);
+ r = unhexmem(p, &buf, &buf_size);
if (r < 0)
return log_debug_errno(r, "Invalid pcr hash value '%s': %m", p);
size_t bits = (size_t) r * 8;
_cleanup_free_ void *seed = NULL;
- size_t seed_size;
+ size_t seed_size = 0; /* Explicit initialization to appease gcc */
r = tpm2_kdfe(parent->publicArea.nameAlg,
shared_secret,
shared_secret_size,
log_debug("Calculating encrypted seed for sealed object.");
_cleanup_free_ void *seed = NULL, *encrypted_seed = NULL;
- size_t seed_size, encrypted_seed_size;
+ size_t seed_size = 0, encrypted_seed_size = 0; /* Explicit initialization to appease gcc */
if (parent->publicArea.type == TPM2_ALG_RSA)
r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size);
else if (parent->publicArea.type == TPM2_ALG_ECC)
TPM2_HANDLE parent_handle,
const TPM2B_PUBLIC *parent_public,
const TPMA_OBJECT *attributes,
- const void *secret,
- size_t secret_size,
+ const struct iovec *secret,
const TPM2B_DIGEST *policy,
const char *pin,
- void **ret_secret,
- size_t *ret_secret_size,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_serialized_parent,
- size_t *ret_serialized_parent_size) {
+ struct iovec *ret_secret,
+ struct iovec *ret_blob,
+ struct iovec *ret_serialized_parent) {
#if HAVE_OPENSSL
int r;
assert(parent_public);
- assert(secret || secret_size == 0);
+ assert(iovec_is_valid(secret));
assert(secret || ret_secret);
assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */
assert(ret_blob);
- assert(ret_blob_size);
assert(ret_serialized_parent);
- assert(ret_serialized_parent_size);
log_debug("Calculating sealed object.");
parent_handle);
}
- _cleanup_(erase_and_freep) void *generated_secret = NULL;
+ _cleanup_(iovec_done_erase) struct iovec generated_secret = {};
if (!secret) {
/* No secret provided, generate a random secret. We use SHA256 digest length, though it can
* be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash
* size. */
- secret_size = TPM2_SHA256_DIGEST_SIZE;
- generated_secret = malloc(secret_size);
- if (!generated_secret)
+ generated_secret.iov_len = TPM2_SHA256_DIGEST_SIZE;
+ generated_secret.iov_base = malloc(generated_secret.iov_len);
+ if (!generated_secret.iov_base)
return log_oom_debug();
- r = crypto_random_bytes(generated_secret, secret_size);
+ r = crypto_random_bytes(generated_secret.iov_base, generated_secret.iov_len);
if (r < 0)
return log_debug_errno(r, "Failed to generate secret key: %m");
- secret = generated_secret;
+ secret = &generated_secret;
}
- if (secret_size > TPM2_MAX_SEALED_DATA)
+ if (secret->iov_len > TPM2_MAX_SEALED_DATA)
return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW),
"Secret size %zu too large, limit is %d bytes.",
- secret_size, TPM2_MAX_SEALED_DATA);
+ secret->iov_len, TPM2_MAX_SEALED_DATA);
TPM2B_DIGEST random_seed;
TPM2B_ENCRYPTED_SECRET seed;
return r;
TPM2B_PUBLIC public;
- r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public);
+ r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret->iov_base, secret->iov_len, &public);
if (r < 0)
return r;
return r;
TPM2B_PRIVATE private;
- r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private);
+ r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret->iov_base, secret->iov_len, &private);
if (r < 0)
return r;
- _cleanup_free_ void *blob = NULL;
- size_t blob_size;
- r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size);
+ _cleanup_(iovec_done) struct iovec blob = {};
+ r = tpm2_marshal_blob(&public, &private, &seed, &blob.iov_base, &blob.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
if (r < 0)
return r;
- _cleanup_free_ void *serialized_parent = NULL;
- size_t serialized_parent_size;
+ _cleanup_(iovec_done) struct iovec serialized_parent = {};
r = tpm2_calculate_serialize(
parent_handle,
&parent_name,
parent_public,
- &serialized_parent,
- &serialized_parent_size);
+ &serialized_parent.iov_base,
+ &serialized_parent.iov_len);
if (r < 0)
return r;
if (ret_secret)
- *ret_secret = TAKE_PTR(generated_secret);
- if (ret_secret_size)
- *ret_secret_size = secret_size;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
- *ret_serialized_parent = TAKE_PTR(serialized_parent);
- *ret_serialized_parent_size = serialized_parent_size;
+ *ret_secret = TAKE_STRUCT(generated_secret);
+ *ret_blob = TAKE_STRUCT(blob);
+ *ret_serialized_parent = TAKE_STRUCT(serialized_parent);
return 0;
#else /* HAVE_OPENSSL */
uint32_t seal_key_handle,
const TPM2B_DIGEST *policy,
const char *pin,
- void **ret_secret,
- size_t *ret_secret_size,
- void **ret_blob,
- size_t *ret_blob_size,
+ struct iovec *ret_secret,
+ struct iovec *ret_blob,
uint16_t *ret_primary_alg,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size) {
+ struct iovec *ret_srk) {
uint16_t primary_alg = 0;
int r;
assert(ret_secret);
- assert(ret_secret_size);
assert(ret_blob);
- assert(ret_blob_size);
/* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
* is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
usec_t start = now(CLOCK_MONOTONIC);
+ TPMA_OBJECT hmac_attributes =
+ TPMA_OBJECT_FIXEDTPM |
+ TPMA_OBJECT_FIXEDPARENT;
+
+ /* If protected by PIN, a user-selected low-entropy password, enable DA protection.
+ Without a PIN, the key's left protected only by a PCR policy, which does not benefit
+ from DA protection. */
+ hmac_attributes |= pin ? 0 : TPMA_OBJECT_NODA;
+
/* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the
* LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it
* because it's a key type that is universally supported and suitable for symmetric binary blobs. */
TPMT_PUBLIC hmac_template = {
.type = TPM2_ALG_KEYEDHASH,
.nameAlg = TPM2_ALG_SHA256,
- .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT,
+ .objectAttributes = hmac_attributes,
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
.unique.keyedHash.size = SHA256_DIGEST_SIZE,
.authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
return log_debug_errno(r, "Failed to generate secret key: %m");
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
- if (ret_srk_buf) {
+ if (ret_srk) {
_cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL;
if (IN_SET(seal_key_handle, 0, TPM2_SRK_HANDLE)) {
if (seal_key_handle != 0)
log_debug("Using primary alg sealing, but seal key handle also provided; ignoring seal key handle.");
- /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */
+ /* TODO: force all callers to provide ret_srk, so we can stop sealing with the legacy templates. */
primary_alg = TPM2_ALG_ECC;
TPM2B_PUBLIC template = {
if (r < 0)
return r;
- _cleanup_(erase_and_freep) void *secret = NULL;
- secret = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
- if (!secret)
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
+ if (!secret.iov_base)
return log_oom_debug();
+ secret.iov_len = hmac_sensitive.data.size;
log_debug("Marshalling private and public part of HMAC key.");
- _cleanup_free_ void *blob = NULL;
- size_t blob_size = 0;
- r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size);
+ _cleanup_(iovec_done) struct iovec blob = {};
+ r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
- _cleanup_free_ void *srk_buf = NULL;
- size_t srk_buf_size = 0;
- if (ret_srk_buf) {
+ if (ret_srk) {
+ _cleanup_(iovec_done) struct iovec srk = {};
_cleanup_(Esys_Freep) void *tmp = NULL;
- r = tpm2_serialize(c, primary_handle, &tmp, &srk_buf_size);
+ size_t tmp_size;
+
+ r = tpm2_serialize(c, primary_handle, &tmp, &tmp_size);
if (r < 0)
return r;
/*
* make a copy since we don't want the caller to understand that
* ESYS allocated the pointer. It would make tracking what deallocator
- * to use for srk_buf in which context a PITA.
+ * to use for srk in which context a PITA.
*/
- srk_buf = memdup(tmp, srk_buf_size);
- if (!srk_buf)
+ srk.iov_base = memdup(tmp, tmp_size);
+ if (!srk.iov_base)
return log_oom_debug();
+ srk.iov_len = tmp_size;
- *ret_srk_buf = TAKE_PTR(srk_buf);
- *ret_srk_buf_size = srk_buf_size;
+ *ret_srk = TAKE_STRUCT(srk);
}
- *ret_secret = TAKE_PTR(secret);
- *ret_secret_size = hmac_sensitive.data.size;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
+ *ret_secret = TAKE_STRUCT(secret);
+ *ret_blob = TAKE_STRUCT(blob);
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
int tpm2_unseal(Tpm2Context *c,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
JsonVariant *signature,
const char *pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
uint16_t primary_alg,
- const void *blob,
- size_t blob_size,
- const void *known_policy_hash,
- size_t known_policy_hash_size,
- const void *srk_buf,
- size_t srk_buf_size,
- void **ret_secret,
- size_t *ret_secret_size) {
+ const struct iovec *blob,
+ const struct iovec *known_policy_hash,
+ const struct iovec *srk,
+ struct iovec *ret_secret) {
TSS2_RC rc;
int r;
- assert(blob);
- assert(blob_size > 0);
- assert(known_policy_hash_size == 0 || known_policy_hash);
- assert(pubkey_size == 0 || pubkey);
+ assert(iovec_is_set(blob));
+ assert(iovec_is_valid(known_policy_hash));
+ assert(iovec_is_valid(pubkey));
assert(ret_secret);
- assert(ret_secret_size);
assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
TPM2B_PUBLIC public;
TPM2B_PRIVATE private;
TPM2B_ENCRYPTED_SECRET seed = {};
- r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed);
+ r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed);
if (r < 0)
return log_debug_errno(r, "Could not extract parts from blob: %m");
}
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
- if (srk_buf) {
- r = tpm2_deserialize(c, srk_buf, srk_buf_size, &primary_handle);
+ if (iovec_is_set(srk)) {
+ r = tpm2_deserialize(c, srk->iov_base, srk->iov_len, &primary_handle);
if (r < 0)
return r;
} else if (primary_alg != 0) {
return r;
TPM2B_PUBLIC pubkey_tpm2b;
- _cleanup_free_ void *fp = NULL;
- size_t fp_size = 0;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b);
+ _cleanup_(iovec_done) struct iovec fp = {};
+ if (iovec_is_set(pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey->iov_base, pubkey->iov_len, &pubkey_tpm2b);
if (r < 0)
return log_debug_errno(r, "Could not create TPMT_PUBLIC: %m");
- r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size);
+ r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp.iov_base, &fp.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not get key fingerprint: %m");
}
policy_session,
hash_pcr_mask,
pcr_bank,
- pubkey ? &pubkey_tpm2b : NULL,
- fp, fp_size,
+ iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
+ fp.iov_base, fp.iov_len,
pubkey_pcr_mask,
signature,
!!pin,
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
* wait until the TPM2 tells us to go away. */
- if (known_policy_hash_size > 0 &&
- memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0)
+ if (iovec_is_set(known_policy_hash) &&
+ memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash->iov_base, known_policy_hash->iov_len) != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
"Current policy digest does not match stored policy digest, cancelling "
"TPM2 authentication attempt.");
log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i);
}
- _cleanup_(erase_and_freep) char *secret = NULL;
- secret = memdup(unsealed->buffer, unsealed->size);
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ secret.iov_base = memdup(unsealed->buffer, unsealed->size);
explicit_bzero_safe(unsealed->buffer, unsealed->size);
- if (!secret)
+ if (!secret.iov_base)
return log_oom_debug();
+ secret.iov_len = unsealed->size;
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
- *ret_secret = TAKE_PTR(secret);
- *ret_secret_size = unsealed->size;
+ *ret_secret = TAKE_STRUCT(secret);
return 0;
}
int keyslot,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
uint16_t primary_alg,
- const void *blob,
- size_t blob_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *blob,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
JsonVariant **ret) {
_cleanup_free_ char *keyslot_as_string = NULL;
int r;
- assert(blob || blob_size == 0);
- assert(policy_hash || policy_hash_size == 0);
- assert(pubkey || pubkey_size == 0);
+ assert(iovec_is_valid(pubkey));
+ assert(iovec_is_valid(blob));
+ assert(iovec_is_valid(policy_hash));
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return -ENOMEM;
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
- JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)),
+ JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_IOVEC_BASE64(blob)),
JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(hmj)),
JSON_BUILD_PAIR_CONDITION(!!tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))),
JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))),
- JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)),
+ JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)),
JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
JSON_BUILD_PAIR("tpm2_pcrlock", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PCRLOCK)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
- JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
- JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)),
- JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size))));
+ JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_IOVEC_BASE64(pubkey)),
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)),
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk))));
if (r < 0)
return r;
int *ret_keyslot,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {};
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field.");
- r = json_variant_unbase64(w, &blob, &blob_size);
+ r = json_variant_unbase64_iovec(w, &blob);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field.");
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field.");
- r = json_variant_unhex(w, &policy_hash, &policy_hash_size);
+ r = json_variant_unhex_iovec(w, &policy_hash);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field.");
w = json_variant_by_key(v, "tpm2_salt");
if (w) {
- r = json_variant_unbase64(w, &salt, &salt_size);
+ r = json_variant_unbase64_iovec(w, &salt);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2_salt' field.");
}
w = json_variant_by_key(v, "tpm2_pubkey");
if (w) {
- r = json_variant_unbase64(w, &pubkey, &pubkey_size);
+ r = json_variant_unbase64_iovec(w, &pubkey);
if (r < 0)
return log_debug_errno(r, "Failed to decode PCR public key.");
} else if (pubkey_pcr_mask != 0)
w = json_variant_by_key(v, "tpm2_srk");
if (w) {
- r = json_variant_unbase64(w, &srk_buf, &srk_buf_size);
+ r = json_variant_unbase64_iovec(w, &srk);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field.");
}
if (ret_pcr_bank)
*ret_pcr_bank = pcr_bank;
if (ret_pubkey)
- *ret_pubkey = TAKE_PTR(pubkey);
- if (ret_pubkey_size)
- *ret_pubkey_size = pubkey_size;
+ *ret_pubkey = TAKE_STRUCT(pubkey);
if (ret_pubkey_pcr_mask)
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
if (ret_blob)
- *ret_blob = TAKE_PTR(blob);
- if (ret_blob_size)
- *ret_blob_size = blob_size;
+ *ret_blob = TAKE_STRUCT(blob);
if (ret_policy_hash)
- *ret_policy_hash = TAKE_PTR(policy_hash);
- if (ret_policy_hash_size)
- *ret_policy_hash_size = policy_hash_size;
+ *ret_policy_hash = TAKE_STRUCT(policy_hash);
if (ret_salt)
- *ret_salt = TAKE_PTR(salt);
- if (ret_salt_size)
- *ret_salt_size = salt_size;
+ *ret_salt = TAKE_STRUCT(salt);
if (ret_flags)
*ret_flags = flags;
- if (ret_srk_buf)
- *ret_srk_buf = TAKE_PTR(srk_buf);
- if (ret_srk_buf_size)
- *ret_srk_buf_size = srk_buf_size;
+ if (ret_srk)
+ *ret_srk = TAKE_STRUCT(srk);
return 0;
}
int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
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 void *secret, size_t secret_size, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_serialized_parent, size_t *ret_serialized_parent_size);
+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_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template);
int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);
int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
-int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
-int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, uint16_t *ret_primary_alg, struct iovec *ret_srk);
+int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret);
#if HAVE_OPENSSL
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret);
-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags);
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, TPM2Flags flags, JsonVariant **ret);
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, TPM2Flags *ret_flags);
/* Default to PCR 7 only */
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)
#include "rlimit-util.h"
#include "string-table.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-record.h"
#include "user-util.h"
return strv_free_and_replace(*l, n);
}
-int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
- UserDisposition *disposition = userdata, k;
-
- if (json_variant_is_null(variant)) {
- *disposition = _USER_DISPOSITION_INVALID;
- return 0;
- }
-
- if (!json_variant_is_string(variant))
- return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
-
- k = user_disposition_from_string(json_variant_string(variant));
- if (k < 0)
- return json_log(variant, flags, k, "Disposition type '%s' not known.", json_variant_string(variant));
-
- *disposition = k;
- return 0;
-}
-
-static int json_dispatch_storage(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
- UserStorage *storage = userdata, k;
-
- if (json_variant_is_null(variant)) {
- *storage = _USER_STORAGE_INVALID;
- return 0;
- }
-
- if (!json_variant_is_string(variant))
- return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
-
- k = user_storage_from_string(json_variant_string(variant));
- if (k < 0)
- return json_log(variant, flags, k, "Storage type '%s' not known.", json_variant_string(variant));
-
- *storage = k;
- return 0;
-}
+JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
+static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint64_t *limit = userdata, k;
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
if (!array)
return log_oom();
- r = unbase64mem(json_variant_string(e), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(e), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
if (r < 0)
return r;
- r = json_dispatch(h->json, user_dispatch_table, json_flags, h);
+ r = json_dispatch(h->json, user_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h);
if (r < 0)
return r;
assert_se(!iterator->found_user);
- r = json_dispatch(parameters, dispatch_table, 0, &user_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &user_data);
if (r < 0)
goto finish;
assert_se(!iterator->found_group);
- r = json_dispatch(parameters, dispatch_table, 0, &group_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &group_data);
if (r < 0)
goto finish;
assert(!iterator->found_user_name);
assert(!iterator->found_group_name);
- r = json_dispatch(parameters, dispatch_table, 0, &membership_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &membership_data);
if (r < 0)
goto finish;
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.Credentials.h"
+
+static VARLINK_DEFINE_METHOD(
+ Describe,
+ VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(DefaultHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HostnameSource, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(IconName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Chassis, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Deployment, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Location, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(KernelName, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(KernelRelease, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(KernelVersion, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareModel, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareSerial, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareVersion, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareDate, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(MachineID, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(BootID, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(ProductUUID, VARLINK_STRING, VARLINK_NULLABLE));
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_Hostname,
+ "io.systemd.Hostname",
+ &vl_method_Describe);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_Hostname;
VARLINK_DEFINE_OUTPUT(ready, VARLINK_BOOL, VARLINK_NULLABLE),
/* Subsequent replies */
VARLINK_DEFINE_OUTPUT(state, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(result, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(rcode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(errno, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT_BY_TYPE(question, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(collectedQuestions, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, VARLINK_NULLABLE|VARLINK_ARRAY));
static VARLINK_DEFINE_ERROR(QueryAborted);
static VARLINK_DEFINE_ERROR(
DNSSECValidationFailed,
- VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0));
+ VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(NoTrustAnchor);
static VARLINK_DEFINE_ERROR(ResourceRecordTypeUnsupported);
static VARLINK_DEFINE_ERROR(NetworkDown);
static VARLINK_DEFINE_ERROR(StubLoop);
static VARLINK_DEFINE_ERROR(
DNSError,
- VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0));
+ VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(CNAMELoop);
static VARLINK_DEFINE_ERROR(BadAddressSize);
JsonVariant *current;
VarlinkSymbol *current_method;
+ int peer_pidfd;
struct ucred ucred;
bool ucred_acquired:1;
.timeout = VARLINK_DEFAULT_TIMEOUT_USEC,
.af = -1,
+
+ .peer_pidfd = -EBADF,
};
*ret = v;
if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+ r = fd_nonblock(pair[1], false);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m");
+
r = safe_fork_full(
"(sd-vlexec)",
/* stdio_fds= */ NULL,
return 0;
}
+static int varlink_connect_ssh(Varlink **ret, const char *where) {
+ _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
+ _cleanup_(sigkill_waitp) pid_t pid = 0;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(where, -EINVAL);
+
+ /* Connects to an SSH server via OpenSSH 9.4's -W switch to connect to a remote AF_UNIX socket. For
+ * now we do not expose this function directly, but only via varlink_connect_url(). */
+
+ const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh";
+ if (!path_is_valid(ssh))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh);
+
+ const char *e = strchr(where, ':');
+ if (!e)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH specification lacks a : separator between host and path, refusing: %s", where);
+
+ _cleanup_free_ char *h = strndup(where, e - where);
+ if (!h)
+ return log_oom_debug();
+
+ _cleanup_free_ char *c = strdup(e + 1);
+ if (!c)
+ return log_oom_debug();
+
+ if (!path_is_absolute(c))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Remote AF_UNIX socket path is not absolute, refusing: %s", c);
+
+ _cleanup_free_ char *p = NULL;
+ r = path_simplify_alloc(c, &p);
+ if (r < 0)
+ return r;
+
+ if (!path_is_normalized(p))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing: %s", p);
+
+ log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
+ return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+
+ r = safe_fork_full(
+ "(sd-vlssh)",
+ /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO },
+ /* except_fds= */ NULL,
+ /* n_except_fds= */ 0,
+ FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO,
+ &pid);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to spawn process: %m");
+ if (r == 0) {
+ /* Child */
+
+ execlp(ssh, "ssh", "-W", p, h, NULL);
+ log_debug_errno(errno, "Failed to invoke %s: %m", ssh);
+ _exit(EXIT_FAILURE);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ Varlink *v;
+ r = varlink_new(&v);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create varlink object: %m");
+
+ v->fd = TAKE_FD(pair[0]);
+ v->af = AF_UNIX;
+ v->exec_pid = TAKE_PID(pid);
+ varlink_set_state(v, VARLINK_IDLE_CLIENT);
+
+ *ret = v;
+ return 0;
+}
+
int varlink_connect_url(Varlink **ret, const char *url) {
_cleanup_free_ char *c = NULL;
const char *p;
- bool exec;
+ enum {
+ SCHEME_UNIX,
+ SCHEME_EXEC,
+ SCHEME_SSH,
+ } scheme;
int r;
assert_return(ret, -EINVAL);
assert_return(url, -EINVAL);
- // FIXME: Add support for vsock:, ssh-exec:, ssh-unix: URL schemes here. (The latter with OpenSSH
- // 9.4's -W switch for referencing remote AF_UNIX sockets.)
+ // FIXME: Maybe add support for vsock: and ssh-exec: URL schemes here.
- /* The Varlink URL scheme is a bit underdefined. We support only the unix: transport for now, plus an
- * exec: transport we made up ourselves. Strictly speaking this shouldn't even be called URL, since
- * it has nothing to do with Internet URLs by RFC. */
+ /* The Varlink URL scheme is a bit underdefined. We support only the spec-defined unix: transport for
+ * now, plus exec:, ssh: transports we made up ourselves. Strictly speaking this shouldn't even be
+ * called "URL", since it has nothing to do with Internet URLs by RFC. */
p = startswith(url, "unix:");
if (p)
- exec = false;
- else {
- p = startswith(url, "exec:");
- if (!p)
- return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported.");
-
- exec = true;
- }
+ scheme = SCHEME_UNIX;
+ else if ((p = startswith(url, "exec:")))
+ scheme = SCHEME_EXEC;
+ else if ((p = startswith(url, "ssh:")))
+ scheme = SCHEME_SSH;
+ else
+ return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported.");
/* The varlink.org reference C library supports more than just file system paths. We might want to
* support that one day too. For now simply refuse that. */
if (p[strcspn(p, ";?#")] != '\0')
return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported.");
- if (exec || p[0] != '@') { /* no validity checks for abstract namespace */
+ if (scheme == SCHEME_SSH)
+ return varlink_connect_ssh(ret, p);
+
+ if (scheme == SCHEME_EXEC || p[0] != '@') { /* no path validity checks for abstract namespace sockets */
if (!path_is_absolute(p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path not absolute, refusing.");
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing.");
}
- if (exec)
+ if (scheme == SCHEME_EXEC)
return varlink_connect_exec(ret, c, NULL);
return varlink_connect_address(ret, c ?: p);
sigterm_wait(v->exec_pid);
v->exec_pid = 0;
}
+
+ v->peer_pidfd = safe_close(v->peer_pidfd);
}
static Varlink* varlink_destroy(Varlink *v) {
sz = e - begin + 1;
- varlink_log(v, "New incoming message: %s", begin); /* FIXME: should we output the whole message here before validation?
- * This may produce a non-printable journal entry if the message
- * is invalid. We may also expose privileged information. */
-
r = json_parse(begin, 0, &v->current, NULL, NULL);
if (r < 0) {
/* If we encounter a parse failure flush all data. We cannot possibly recover from this,
return r;
}
+int varlink_dispatch_again(Varlink *v) {
+ int r;
+
+ assert_return(v, -EINVAL);
+
+ /* If a method call handler could not process the method call just yet (for example because it needed
+ * some Polkit authentication first), then it can leave the call unanswered, do its thing, and then
+ * ask to be dispatched a second time, via this call. It will then be called again, for the same
+ * message */
+
+ if (v->state == VARLINK_DISCONNECTED)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
+ if (v->state != VARLINK_PENDING_METHOD)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection has no pending method.");
+
+ varlink_set_state(v, VARLINK_IDLE_SERVER);
+
+ r = sd_event_source_set_enabled(v->defer_event_source, SD_EVENT_ON);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to enable deferred event source: %m");
+
+ return 0;
+}
+
+int varlink_get_current_parameters(Varlink *v, JsonVariant **ret) {
+ JsonVariant *p;
+
+ assert_return(v, -EINVAL);
+
+ if (!v->current)
+ return -ENODATA;
+
+ p = json_variant_by_key(v->current, "parameters");
+ if (!p)
+ return -ENODATA;
+
+ if (ret)
+ *ret = json_variant_ref(p);
+
+ return 0;
+}
+
static void handle_revents(Varlink *v, int revents) {
assert(v);
static int varlink_format_json(Varlink *v, JsonVariant *m) {
_cleanup_(erase_and_freep) char *text = NULL;
+ bool sensitive = false;
int r;
assert(v);
assert(m);
- r = json_variant_format(m, 0, &text);
+ r = json_variant_format(m, JSON_FORMAT_REFUSE_SENSITIVE, &text);
+ if (r == -EPERM) {
+ sensitive = true;
+ r = json_variant_format(m, /* flags= */ 0, &text);
+ }
if (r < 0)
return r;
assert(text[r] == '\0');
if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX)
return -ENOBUFS;
- varlink_log(v, "Sending message: %s", text);
+ varlink_log(v, "Sending message: %s", sensitive ? "<sensitive data>" : text);
if (v->output_buffer_size == 0) {
v->output_buffer_index = 0;
}
- if (json_variant_is_sensitive(m))
+ if (sensitive)
v->output_buffer_sensitive = true; /* Propagate sensitive flag */
else
text = mfree(text); /* No point in the erase_and_free() destructor declared above */
return 0;
}
+static int varlink_acquire_pidfd(Varlink *v) {
+ assert(v);
+
+ if (v->peer_pidfd >= 0)
+ return 0;
+
+ v->peer_pidfd = getpeerpidfd(v->fd);
+ if (v->peer_pidfd < 0)
+ return v->peer_pidfd;
+
+ return 0;
+}
+
+int varlink_get_peer_pidref(Varlink *v, PidRef *ret) {
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ /* Returns r > 0 if we acquired the pidref via SO_PEERPIDFD (i.e. if we can use it for
+ * authentication). Returns == 0 if we didn't, and the pidref should not be used for
+ * authentication. */
+
+ r = varlink_acquire_pidfd(v);
+ if (r < 0)
+ return r;
+
+ if (v->peer_pidfd < 0) {
+ pid_t pid;
+
+ r = varlink_get_peer_pid(v, &pid);
+ if (r < 0)
+ return r;
+
+ r = pidref_set_pid(ret, pid);
+ if (r < 0)
+ return r;
+
+ return 0; /* didn't get pidfd securely */
+ }
+
+ r = pidref_set_pidfd(ret, v->peer_pidfd);
+ if (r < 0)
+ return r;
+
+ return 1; /* got pidfd securely */
+}
+
int varlink_set_relative_timeout(Varlink *v, usec_t timeout) {
assert_return(v, -EINVAL);
assert_return(timeout > 0, -EINVAL);
#include "sd-event.h"
#include "json.h"
+#include "pidref.h"
#include "time-util.h"
#include "varlink-idl.h"
int varlink_notify(Varlink *v, JsonVariant *parameters);
int varlink_notifyb(Varlink *v, ...);
+/* Ask for the current message to be dispatched again */
+int varlink_dispatch_again(Varlink *v);
+
+/* Get the currently processed incoming message */
+int varlink_get_current_parameters(Varlink *v, JsonVariant **ret);
+
/* Parsing incoming data via json_dispatch() and generate a nice error on parse errors */
int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch table[], void *userdata);
int varlink_get_peer_uid(Varlink *v, uid_t *ret);
int varlink_get_peer_pid(Varlink *v, pid_t *ret);
+int varlink_get_peer_pidref(Varlink *v, PidRef *ret);
int varlink_set_relative_timeout(Varlink *v, usec_t usec);
/* This one we invented, and use for generically propagating system errors (errno) to clients */
#define VARLINK_ERROR_SYSTEM "io.systemd.System"
+/* This one we invented and is a weaker version of "org.varlink.service.PermissionDenied", and indicates that if user would allow interactive auth, we might allow access */
+#define VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED "io.systemd.InteractiveAuthenticationRequired"
+
/* These are errors defined in the Varlink spec */
#define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound"
#define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound"
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/stat.h>
+
+#include "architecture.h"
+#include "chase.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "recurse-dir.h"
+#include "vpick.h"
+
+void pick_result_done(PickResult *p) {
+ assert(p);
+
+ free(p->path);
+ safe_close(p->fd);
+ free(p->version);
+
+ *p = PICK_RESULT_NULL;
+}
+
+static int format_fname(
+ const PickFilter *filter,
+ PickFlags flags,
+ char **ret) {
+
+ _cleanup_free_ char *fn = NULL;
+ int r;
+
+ assert(filter);
+ assert(ret);
+
+ if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */
+ return -ENOEXEC;
+
+ /* The format for names we match goes like this:
+ *
+ * <basename><suffix>
+ * or:
+ * <basename>_<version><suffix>
+ * or:
+ * <basename>_<version>_<architecture><suffix>
+ * or:
+ * <basename>_<architecture><suffix>
+ *
+ * (Note that basename can be empty, in which case the leading "_" is suppressed)
+ *
+ * Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw
+ *
+ * Why use "_" as separator here? Primarily because it is not used by Semver 2.0. In RPM it is used
+ * for "unsortable" versions, i.e. doesn't show up in "sortable" versions, which we matter for this
+ * usecase here. In Debian the underscore is not allowed (and it uses it itself for separating
+ * fields).
+ *
+ * This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the
+ * architecture field redundant.
+ *
+ * Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix).
+ */
+
+ if (filter->basename) {
+ fn = strdup(filter->basename);
+ if (!fn)
+ return -ENOMEM;
+ }
+
+ if (filter->version) {
+ if (isempty(fn)) {
+ r = free_and_strdup(&fn, filter->version);
+ if (r < 0)
+ return r;
+ } else if (!strextend(&fn, "_", filter->version))
+ return -ENOMEM;
+ }
+
+ if (FLAGS_SET(flags, PICK_ARCHITECTURE) && filter->architecture >= 0) {
+ const char *as = ASSERT_PTR(architecture_to_string(filter->architecture));
+ if (isempty(fn)) {
+ r = free_and_strdup(&fn, as);
+ if (r < 0)
+ return r;
+ } else if (!strextend(&fn, "_", as))
+ return -ENOMEM;
+ }
+
+ if (filter->suffix && !strextend(&fn, filter->suffix))
+ return -ENOMEM;
+
+ if (!filename_is_valid(fn))
+ return -EINVAL;
+
+ *ret = TAKE_PTR(fn);
+ return 0;
+}
+
+static int errno_from_mode(uint32_t type_mask, mode_t found) {
+ /* Returns the most appropriate error code if we are lookging for an inode of type of those in the
+ * 'type_mask' but found 'found' instead.
+ *
+ * type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, …
+ * mode value. */
+
+ if (type_mask == 0) /* type doesn't matter */
+ return 0;
+
+ if (FLAGS_SET(type_mask, UINT32_C(1) << IFTODT(found)))
+ return 0;
+
+ if (type_mask == (UINT32_C(1) << DT_BLK))
+ return -ENOTBLK;
+ if (type_mask == (UINT32_C(1) << DT_DIR))
+ return -ENOTDIR;
+ if (type_mask == (UINT32_C(1) << DT_SOCK))
+ return -ENOTSOCK;
+
+ if (S_ISLNK(found))
+ return -ELOOP;
+ if (S_ISDIR(found))
+ return -EISDIR;
+
+ return -EBADF;
+}
+
+static int pin_choice(
+ const char *toplevel_path,
+ int toplevel_fd,
+ const char *inode_path,
+ int _inode_fd, /* we always take ownership of the fd, even on failure */
+ unsigned tries_left,
+ unsigned tries_done,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret) {
+
+ _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd);
+ _cleanup_free_ char *resolved_path = NULL;
+ int r;
+
+ assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
+ assert(inode_path);
+ assert(filter);
+
+ toplevel_path = strempty(toplevel_path);
+
+ if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) {
+ r = chaseat(toplevel_fd,
+ inode_path,
+ CHASE_AT_RESOLVE_IN_ROOT,
+ FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : 0,
+ inode_fd < 0 ? &inode_fd : NULL);
+ if (r < 0)
+ return r;
+
+ if (resolved_path)
+ inode_path = resolved_path;
+ }
+
+ struct stat st;
+ if (fstat(inode_fd, &st) < 0)
+ return log_debug_errno(errno, "Failed to stat discovered inode '%s/%s': %m", toplevel_path, inode_path);
+
+ if (filter->type_mask != 0 &&
+ !FLAGS_SET(filter->type_mask, UINT32_C(1) << IFTODT(st.st_mode)))
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)),
+ "Inode '%s/%s' has wrong type, found '%s'.",
+ toplevel_path, inode_path,
+ inode_type_to_string(st.st_mode));
+
+ _cleanup_(pick_result_done) PickResult result = {
+ .fd = TAKE_FD(inode_fd),
+ .st = st,
+ .architecture = filter->architecture,
+ .tries_left = tries_left,
+ .tries_done = tries_done,
+ };
+
+ result.path = strdup(inode_path);
+ if (!result.path)
+ return log_oom_debug();
+
+ if (filter->version) {
+ result.version = strdup(filter->version);
+ if (!result.version)
+ return log_oom_debug();
+ }
+
+ *ret = TAKE_PICK_RESULT(result);
+ return 1;
+}
+
+static int parse_tries(const char *s, unsigned *ret_tries_left, unsigned *ret_tries_done) {
+ unsigned left, done;
+ size_t n;
+
+ assert(s);
+ assert(ret_tries_left);
+ assert(ret_tries_done);
+
+ if (s[0] != '+')
+ goto nomatch;
+
+ s++;
+
+ n = strspn(s, DIGITS);
+ if (n == 0)
+ goto nomatch;
+
+ if (s[n] == 0) {
+ if (safe_atou(s, &left) < 0)
+ goto nomatch;
+
+ done = 0;
+ } else if (s[n] == '-') {
+ _cleanup_free_ char *c = NULL;
+
+ c = strndup(s, n);
+ if (!c)
+ return -ENOMEM;
+
+ if (safe_atou(c, &left) < 0)
+ goto nomatch;
+
+ s += n + 1;
+
+ if (!in_charset(s, DIGITS))
+ goto nomatch;
+
+ if (safe_atou(s, &done) < 0)
+ goto nomatch;
+ } else
+ goto nomatch;
+
+ *ret_tries_left = left;
+ *ret_tries_done = done;
+ return 1;
+
+nomatch:
+ *ret_tries_left = *ret_tries_done = UINT_MAX;
+ return 0;
+}
+
+static int make_choice(
+ const char *toplevel_path,
+ int toplevel_fd,
+ const char *inode_path,
+ int _inode_fd, /* we always take ownership of the fd, even on failure */
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret) {
+
+ static const Architecture local_architectures[] = {
+ /* In order of preference */
+ native_architecture(),
+#ifdef ARCHITECTURE_SECONDARY
+ ARCHITECTURE_SECONDARY,
+#endif
+ _ARCHITECTURE_INVALID, /* accept any arch, as last resort */
+ };
+
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ _cleanup_free_ char *best_version = NULL, *best_filename = NULL, *p = NULL, *j = NULL;
+ _cleanup_close_ int dir_fd = -EBADF, object_fd = -EBADF, inode_fd = TAKE_FD(_inode_fd);
+ const Architecture *architectures;
+ unsigned best_tries_left = UINT_MAX, best_tries_done = UINT_MAX;
+ size_t n_architectures, best_architecture_index = SIZE_MAX;
+ int r;
+
+ assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
+ assert(inode_path);
+ assert(filter);
+
+ toplevel_path = strempty(toplevel_path);
+
+ if (inode_fd < 0) {
+ r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd);
+ if (r < 0)
+ return r;
+ }
+
+ /* Maybe the filter is fully specified? Then we can generate the file name directly */
+ r = format_fname(filter, flags, &j);
+ if (r >= 0) {
+ _cleanup_free_ char *object_path = NULL;
+
+ /* Yay! This worked! */
+ p = path_join(inode_path, j);
+ if (!p)
+ 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/%s': %m", toplevel_path, p);
+
+ *ret = PICK_RESULT_NULL;
+ return 0;
+ }
+
+ return pin_choice(
+ toplevel_path,
+ toplevel_fd,
+ FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p,
+ TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */
+ /* tries_left= */ UINT_MAX,
+ /* tries_done= */ UINT_MAX,
+ filter,
+ flags & ~PICK_RESOLVE,
+ ret);
+
+ } else if (r != -ENOEXEC)
+ return log_debug_errno(r, "Failed to format file name: %m");
+
+ /* Underspecified, so we do our enumeration dance */
+
+ /* Convert O_PATH to a regular directory fd */
+ dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+ if (dir_fd < 0)
+ return log_debug_errno(dir_fd, "Failed to reopen '%s/%s' as directory: %m", toplevel_path, inode_path);
+
+ r = readdir_all(dir_fd, 0, &de);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read directory '%s/%s': %m", toplevel_path, inode_path);
+
+ if (filter->architecture < 0) {
+ architectures = local_architectures;
+ n_architectures = ELEMENTSOF(local_architectures);
+ } else {
+ architectures = &filter->architecture;
+ n_architectures = 1;
+ }
+
+ FOREACH_ARRAY(entry, de->entries, de->n_entries) {
+ unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX;
+ _cleanup_free_ char *chopped = NULL;
+ size_t found_architecture_index = SIZE_MAX;
+ const char *e;
+
+ if (!isempty(filter->basename)) {
+ e = startswith((*entry)->d_name, filter->basename);
+ if (!e)
+ continue;
+
+ if (e[0] != '_')
+ continue;
+
+ e++;
+ } else
+ e = (*entry)->d_name;
+
+ if (!isempty(filter->suffix)) {
+ const char *sfx;
+
+ sfx = endswith(e, filter->suffix);
+ if (!sfx)
+ continue;
+
+ chopped = strndup(e, sfx - e);
+ if (!chopped)
+ return log_oom_debug();
+
+ e = chopped;
+ }
+
+ if (FLAGS_SET(flags, PICK_TRIES)) {
+ char *plus = strrchr(e, '+');
+ if (plus) {
+ r = parse_tries(plus, &found_tries_left, &found_tries_done);
+ if (r < 0)
+ return r;
+ if (r > 0) /* Found and parsed, now chop off */
+ *plus = 0;
+ }
+ }
+
+ if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
+ char *underscore = strrchr(e, '_');
+ Architecture a;
+
+ a = underscore ? architecture_from_string(underscore + 1) : _ARCHITECTURE_INVALID;
+
+ for (size_t i = 0; i < n_architectures; i++)
+ if (architectures[i] == a) {
+ found_architecture_index = i;
+ break;
+ }
+
+ if (found_architecture_index == SIZE_MAX) { /* No matching arch found */
+ log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a < 0 ? "any" : architecture_to_string(a));
+ continue;
+ }
+
+ /* Chop off architecture from string */
+ if (underscore)
+ *underscore = 0;
+ }
+
+ if (!version_is_valid(e)) {
+ log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e, (*entry)->d_name);
+ continue;
+ }
+
+ if (filter->version && !streq(filter->version, e)) {
+ log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e, filter->version);
+ continue;
+ }
+
+ if (best_filename) { /* Already found one matching entry? Then figure out the better one */
+ int d = 0;
+
+ /* First, prefer entries with tries left over those without */
+ if (FLAGS_SET(flags, PICK_TRIES))
+ d = CMP(found_tries_left != 0, best_tries_left != 0);
+
+ /* Second, prefer newer versions */
+ if (d == 0)
+ d = strverscmp_improved(e, best_version);
+
+ /* Third, prefer native architectures over secondary architectures */
+ if (d == 0 &&
+ FLAGS_SET(flags, PICK_ARCHITECTURE) &&
+ found_architecture_index != SIZE_MAX && best_architecture_index != SIZE_MAX)
+ d = -CMP(found_architecture_index, best_architecture_index);
+
+ /* Fourth, prefer entries with more tries left */
+ if (FLAGS_SET(flags, PICK_TRIES)) {
+ if (d == 0)
+ d = CMP(found_tries_left, best_tries_left);
+
+ /* Fifth, prefer entries with fewer attempts done so far */
+ if (d == 0)
+ d = -CMP(found_tries_done, best_tries_done);
+ }
+
+ /* Last, just compare the filenames as strings */
+ if (d == 0)
+ d = strcmp((*entry)->d_name, best_filename);
+
+ if (d < 0) {
+ log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry)->d_name, best_filename);
+ continue;
+ }
+ }
+
+ r = free_and_strdup_warn(&best_version, e);
+ if (r < 0)
+ return r;
+
+ r = free_and_strdup_warn(&best_filename, (*entry)->d_name);
+ if (r < 0)
+ return r;
+
+ best_architecture_index = found_architecture_index;
+ best_tries_left = found_tries_left;
+ best_tries_done = found_tries_done;
+ }
+
+ if (!best_filename) { /* Everything was good, but we didn't find any suitable entry */
+ *ret = PICK_RESULT_NULL;
+ return 0;
+ }
+
+ p = path_join(inode_path, best_filename);
+ if (!p)
+ return log_oom_debug();
+
+ object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH);
+ if (object_fd < 0)
+ return log_debug_errno(errno, "Failed to open '%s/%s': %m", toplevel_path, p);
+
+ return pin_choice(
+ toplevel_path,
+ toplevel_fd,
+ p,
+ TAKE_FD(object_fd),
+ best_tries_left,
+ best_tries_done,
+ &(const PickFilter) {
+ .type_mask = filter->type_mask,
+ .basename = filter->basename,
+ .version = empty_to_null(best_version),
+ .architecture = best_architecture_index != SIZE_MAX ? architectures[best_architecture_index] : _ARCHITECTURE_INVALID,
+ .suffix = filter->suffix,
+ },
+ flags,
+ ret);
+}
+
+int path_pick(const char *toplevel_path,
+ int toplevel_fd,
+ const char *path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret) {
+
+ _cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL;
+ const char *filter_suffix, *enumeration_path;
+ uint32_t filter_type_mask;
+ int r;
+
+ assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
+ assert(path);
+
+ toplevel_path = strempty(toplevel_path);
+
+ /* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports
+ * three ways to be called:
+ *
+ * • with a path referring a directory of any name, and filter→basename *explicitly* specified, in
+ * which case we'll use a pattern "<filter→basename>_*<filter→suffix>" on the directory's files.
+ *
+ * • with no filter→basename explicitly specified and a path referring to a directory named in format
+ * "<somestring><filter→suffix>.v" . In this case the filter basename to search for inside the dir
+ * is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for
+ * "/foo/bar/baz.suffix.v/baz_*.suffix".
+ *
+ * • with a path whose penultimate component ends in ".v/". In this case the final component of the
+ * path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for
+ * "/foo/bar/baz.v/waldo_*.suffix".
+ */
+
+ /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */
+ if (filter->basename)
+ return make_choice(
+ toplevel_path,
+ toplevel_fd,
+ path,
+ /* inode_fd= */ -EBADF,
+ filter,
+ flags,
+ ret);
+
+ r = path_extract_filename(path, &fname);
+ if (r < 0) {
+ if (r != -EADDRNOTAVAIL) /* root dir or "." */
+ return r;
+
+ /* If there's no path element we can derive a pattern off, the don't */
+ goto bypass;
+ }
+
+ /* Remember if the path ends in a dash suffix */
+ bool slash_suffix = r == O_DIRECTORY;
+
+ const char *e = endswith(fname, ".v");
+ if (e) {
+ /* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is
+ * "baz", possibly with a suffix chopped off if there's one specified. */
+ filter_bname = strndup(fname, e - fname);
+ if (!filter_bname)
+ return -ENOMEM;
+
+ if (filter->suffix) {
+ /* Chop off suffix, if specified */
+ char *f = endswith(filter_bname, filter->suffix);
+ if (f)
+ *f = 0;
+ }
+
+ filter_suffix = filter->suffix;
+ filter_type_mask = filter->type_mask;
+
+ enumeration_path = path;
+ } else {
+ /* The path does not end in '.v', hence see if the last element is a pattern. */
+
+ char *wildcard = strrstr(fname, "___");
+ if (!wildcard)
+ goto bypass; /* Not a pattern, then bypass */
+
+ /* We found the '___' wildcard, hence everything after it is our filter suffix, and
+ * everything before is our filter basename */
+ *wildcard = 0;
+ filter_suffix = empty_to_null(wildcard + 3);
+
+ filter_bname = TAKE_PTR(fname);
+
+ r = path_extract_directory(path, &dir);
+ if (r < 0) {
+ if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */
+ return r;
+
+ goto bypass; /* No dir extractable, can't check if parent ends in ".v" */
+ }
+
+ r = path_extract_filename(dir, &parent);
+ if (r < 0) {
+ if (r != -EADDRNOTAVAIL) /* root dir or "." */
+ return r;
+
+ goto bypass; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */
+ }
+
+ e = endswith(parent, ".v");
+ if (!e)
+ goto bypass; /* Doesn't end in ".v", shortcut */
+
+ filter_type_mask = filter->type_mask;
+ if (slash_suffix) {
+ /* If the pattern is suffixed by a / then we are looking for directories apparently. */
+ if (filter_type_mask != 0 && !FLAGS_SET(filter_type_mask, UINT32_C(1) << DT_DIR))
+ return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask, S_IFDIR)),
+ "Specified pattern ends in '/', but not looking for directories, refusing.");
+ filter_type_mask = UINT32_C(1) << DT_DIR;
+ }
+
+ enumeration_path = dir;
+ }
+
+ return make_choice(
+ toplevel_path,
+ toplevel_fd,
+ enumeration_path,
+ /* inode_fd= */ -EBADF,
+ &(const PickFilter) {
+ .type_mask = filter_type_mask,
+ .basename = filter_bname,
+ .version = filter->version,
+ .architecture = filter->architecture,
+ .suffix = filter_suffix,
+ },
+ flags,
+ ret);
+
+bypass:
+ /* Don't make any choice, but just use the passed path literally */
+ return pin_choice(
+ toplevel_path,
+ toplevel_fd,
+ path,
+ /* inode_fd= */ -EBADF,
+ /* tries_left= */ UINT_MAX,
+ /* tries_done= */ UINT_MAX,
+ filter,
+ flags,
+ ret);
+}
+
+int path_pick_update_warn(
+ char **path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret_result) {
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ int r;
+
+ assert(path);
+ assert(*path);
+
+ /* This updates the first argument if needed! */
+
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ *path,
+ filter,
+ flags,
+ &result);
+ if (r == -ENOENT) {
+ log_debug("Path '%s' doesn't exist, leaving as is.", *path);
+ *ret_result = PICK_RESULT_NULL;
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to pick version on path '%s': %m", *path);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching entries in versioned directory '%s' found.", *path);
+
+ log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result.path, *path, strna(result.version));
+
+ if (ret_result) {
+ r = free_and_strdup_warn(path, result.path);
+ if (r < 0)
+ return r;
+
+ *ret_result = TAKE_PICK_RESULT(result);
+ } else
+ free_and_replace(*path, result.path);
+
+ return 1;
+}
+
+const PickFilter pick_filter_image_raw = {
+ .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
+ .architecture = _ARCHITECTURE_INVALID,
+ .suffix = ".raw",
+};
+
+const PickFilter pick_filter_image_dir = {
+ .type_mask = UINT32_C(1) << DT_DIR,
+ .architecture = _ARCHITECTURE_INVALID,
+};
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "architecture.h"
+
+typedef enum PickFlags {
+ PICK_ARCHITECTURE = 1 << 0, /* Look for an architecture suffix */
+ PICK_TRIES = 1 << 1, /* Look for tries left/tries done counters */
+ PICK_RESOLVE = 1 << 2, /* Return the fully resolved (chased) path, rather than the path to the entry */
+} PickFlags;
+
+typedef struct PickFilter {
+ uint32_t type_mask; /* A mask of 1U << DT_REG, 1U << DT_DIR, … */
+ const char *basename; /* Can be overridden by search pattern */
+ const char *version;
+ Architecture architecture;
+ const char *suffix; /* Can be overridden by search pattern */
+} PickFilter;
+
+typedef struct PickResult {
+ char *path;
+ int fd; /* O_PATH */
+ struct stat st;
+ char *version;
+ Architecture architecture;
+ unsigned tries_left;
+ unsigned tries_done;
+} PickResult;
+
+#define PICK_RESULT_NULL \
+ (const PickResult) { \
+ .fd = -EBADF, \
+ .st.st_mode = MODE_INVALID, \
+ .architecture = _ARCHITECTURE_INVALID, \
+ .tries_left = UINT_MAX, \
+ .tries_done = UINT_MAX, \
+ }
+
+#define TAKE_PICK_RESULT(pick) TAKE_GENERIC(pick, PickResult, PICK_RESULT_NULL)
+
+void pick_result_done(PickResult *p);
+
+int path_pick(const char *toplevel_path,
+ int toplevel_fd,
+ const char *path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret);
+
+int path_pick_update_warn(
+ char **path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret);
+
+extern const PickFilter pick_filter_image_raw;
+extern const PickFilter pick_filter_image_dir;
static int update_timeout(void) {
int r;
+ usec_t previous_timeout;
assert(watchdog_timeout > 0);
if (watchdog_fd < 0)
return 0;
+ previous_timeout = watchdog_timeout;
+
if (watchdog_timeout != USEC_INFINITY) {
r = watchdog_set_timeout();
if (r < 0) {
if (watchdog_timeout == USEC_INFINITY) {
r = watchdog_read_timeout();
- if (r < 0)
- return log_error_errno(r, "Failed to query watchdog HW timeout: %m");
+ if (r < 0) {
+ if (!ERRNO_IS_NOT_SUPPORTED(r))
+ return log_error_errno(r, "Failed to query watchdog HW timeout: %m");
+ log_info("Reading watchdog timeout is not supported, reusing the configured timeout.");
+ watchdog_timeout = previous_timeout;
+ }
}
/* If the watchdog timeout was changed, the pretimeout could have been
opterr = 0; /* do not print errors */
}
+ /* We need to reset some global state manually here since libfuzzer feeds a single process with
+ * multiple inputs, so we might carry over state from previous invocations that can trigger
+ * certain asserts. */
optind = 0; /* this tells the getopt machinery to reinitialize */
+ arg_transport = BUS_TRANSPORT_LOCAL;
r = systemctl_dispatch_parse_argv(strv_length(argv), argv);
if (r < 0)
if (strlen(t) != sizeof(found.sha256sum) * 2)
goto nope;
- r = unhexmem(t, sizeof(found.sha256sum) * 2, &d, &l);
+ r = unhexmem_full(t, sizeof(found.sha256sum) * 2, /* secure = */ false, &d, &l);
if (r == -ENOMEM)
return r;
if (r < 0)
if (p[0] == '\\')
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr);
- r = unhexmem(p, 64, &h, &hlen);
+ r = unhexmem_full(p, 64, /* secure = */ false, &h, &hlen);
if (r < 0)
return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr);
#include "strv.h"
#include "sync-util.h"
#include "tmpfile-util-label.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "uid-range.h"
#include "user-util.h"
#include "utf8.h"
Set *names;
uid_t search_uid;
- UidRange *uid_range;
+ UIDRange *uid_range;
UGIDAllocationRange login_defs;
bool login_defs_need_warning;
'test-install-file.c',
'test-install-root.c',
'test-io-util.c',
+ 'test-iovec-util.c',
'test-journal-importer.c',
'test-kbd-util.c',
'test-limits-util.c',
'test-terminal-util.c',
'test-tmpfile-util.c',
'test-udev-util.c',
- 'test-uid-alloc-range.c',
+ 'test-uid-classification.c',
'test-uid-range.c',
'test-umask-util.c',
'test-unaligned.c',
'test-user-util.c',
'test-utf8.c',
'test-verbs.c',
+ 'test-vpick.c',
'test-web-util.c',
'test-xattr-util.c',
'test-xml.c',
#include "tests.h"
#include "tmpfile-util.h"
#include "tomoyo-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "virt.h"
#include "creds-util.h"
#include "fileio.h"
+#include "id128-util.h"
+#include "iovec-util.h"
#include "path-util.h"
#include "rm-rf.h"
#include "tests.h"
#include "tmpfile-util.h"
+#include "tpm2-util.h"
TEST(read_credential_strings) {
_cleanup_free_ char *x = NULL, *y = NULL, *saved = NULL, *p = NULL;
assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0);
assert_se(x == NULL);
- assert_se(streq(y, "piff"));
+ assert_se(streq(y, "paff"));
p = mfree(p);
assert_se(p = path_join(tmp, "foo"));
assert_se(write_string_file(p, "knurz", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
- assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0);
- assert_se(streq(x, "knurz"));
- assert_se(streq(y, "piff"));
-
- y = mfree(y);
-
assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0);
assert_se(streq(x, "knurz"));
assert_se(streq(y, "paff"));
assert_se(fwrite("x\0y", 1, 3, f) == 3); /* embedded NUL byte should result in EBADMSG when reading back with read_credential_strings_many() */
f = safe_fclose(f);
- assert_se(read_credential_strings_many("bazz", &x, "foo", &y) == -EBADMSG);
+ y = mfree(y);
+
+ assert_se(read_credential_strings_many("bazz", &x, "bar", &y) == -EBADMSG);
assert_se(streq(x, "knurz"));
assert_se(streq(y, "paff"));
assert_se(credential_glob_valid(buf));
}
+static void test_encrypt_decrypt_with(sd_id128_t mode) {
+ static const struct iovec plaintext = CONST_IOVEC_MAKE_STRING("this is a super secret string");
+ int r;
+
+ log_notice("Running encryption/decryption test with mode " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(mode));
+
+ _cleanup_(iovec_done) struct iovec encrypted = {};
+ r = encrypt_credential_and_warn(
+ mode,
+ "foo",
+ /* timestamp= */ USEC_INFINITY,
+ /* not_after=*/ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_hash_pcr_mask= */ 0,
+ /* tpm2_pubkey_path= */ NULL,
+ /* tpm2_pubkey_pcr_mask= */ 0,
+ &plaintext,
+ CREDENTIAL_ALLOW_NULL,
+ &encrypted);
+ if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) {
+ log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because /etc/machine-id is not initialized.", SD_ID128_FORMAT_VAL(mode));
+ return;
+ }
+ if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
+ log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because encrypted credentials are not supported.", SD_ID128_FORMAT_VAL(mode));
+ return;
+ }
+
+ assert_se(r >= 0);
+
+ _cleanup_(iovec_done) struct iovec decrypted = {};
+ r = decrypt_credential_and_warn(
+ "bar",
+ /* validate_timestamp= */ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &encrypted,
+ CREDENTIAL_ALLOW_NULL,
+ &decrypted);
+ assert_se(r == -EREMOTE); /* name didn't match */
+
+ r = decrypt_credential_and_warn(
+ "foo",
+ /* validate_timestamp= */ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &encrypted,
+ CREDENTIAL_ALLOW_NULL,
+ &decrypted);
+ assert_se(r >= 0);
+
+ assert_se(iovec_memcmp(&plaintext, &decrypted) == 0);
+}
+
+static bool try_tpm2(void) {
+#if HAVE_TPM2
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
+ int r;
+
+ r = tpm2_context_new(/* device= */ NULL, &tpm2_context);
+ if (r < 0)
+ log_notice_errno(r, "Failed to create TPM2 context, assuming no TPM2 support or privileges: %m");
+
+ return r >= 0;
+#else
+ return false;
+#endif
+}
+
+TEST(credential_encrypt_decrypt) {
+ _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+ _cleanup_free_ char *j = NULL;
+
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_NULL);
+
+ assert_se(mkdtemp_malloc(NULL, &d) >= 0);
+ j = path_join(d, "secret");
+ assert_se(j);
+
+ const char *e = getenv("SYSTEMD_CREDENTIAL_SECRET");
+ _cleanup_free_ char *ec = NULL;
+
+ if (e)
+ assert_se(ec = strdup(e));
+
+ assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", j, true) >= 0);
+
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST);
+
+ if (try_tpm2()) {
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_TPM2_HMAC);
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
+ }
+
+ if (ec)
+ assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true) >= 0);
+
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
assert_se(mkdir_p(f, 0755) >= 0);
assert_se(make_inaccessible_nodes(f, 1, 1) >= 0);
+ assert_se(make_inaccessible_nodes(f, 1, 1) >= 0); /* 2nd call should be a clean NOP */
f = prefix_roota(p, "/run/systemd/inaccessible/reg");
assert_se(stat(f, &st) >= 0);
assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-failure.target"), UNIT_ATOM_ON_FAILURE_OF, a));
assert_se( unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "non-existing-on-success.target")));
assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-success.target"), UNIT_ATOM_ON_SUCCESS_OF, a));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, "basic.target")));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "basic.target")));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE_OF, manager_get_unit(m, "basic.target")));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS_OF, manager_get_unit(m, "basic.target")));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE_OF, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS_OF, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
assert_se(!unit_has_dependency(a, UNIT_ATOM_PROPAGATES_RELOAD_TO, manager_get_unit(m, "non-existing-on-failure.target")));
assert_se(unit_has_name(a, "a.service"));
static char *user_runtime_unit_dir = NULL;
static bool can_unshare;
static bool have_net_dummy;
+static bool have_netns;
static unsigned n_ran_tests = 0;
STATIC_DESTRUCTOR_REGISTER(user_runtime_unit_dir, freep);
if (!have_net_dummy)
return (void)log_notice("Skipping %s, dummy network interface not available", __func__);
+ if (!have_netns)
+ return (void)log_notice("Skipping %s, network namespace not available", __func__);
+
r = find_executable("ip", NULL);
if (r < 0) {
log_notice_errno(r, "Skipping %s, could not find ip binary: %m", __func__);
if (have_net_dummy) {
/* Create a network namespace and a dummy interface in it for NetworkNamespacePath= */
- (void) system("ip netns add test-execute-netns");
- (void) system("ip netns exec test-execute-netns ip link add dummy-test-ns type dummy");
+ have_netns = system("ip netns add test-execute-netns") == 0;
+ have_netns = have_netns && system("ip netns exec test-execute-netns ip link add dummy-test-ns type dummy") == 0;
}
return EXIT_SUCCESS;
log_debug("hexmem(\"%s\") → \"%s\" (expected: \"%s\")", strnull(in), result, expected);
assert_se(streq(result, expected));
- assert_se(unhexmem(result, SIZE_MAX, &mem, &len) >= 0);
+ assert_se(unhexmem(result, &mem, &len) >= 0);
assert_se(memcmp_safe(mem, in, len) == 0);
}
_cleanup_free_ void *mem = NULL;
size_t len;
- assert_se(unhexmem(s, l, &mem, &len) == retval);
+ assert_se(unhexmem_full(s, l, /* secure = */ false, &mem, &len) == retval);
if (retval == 0) {
char *answer;
assert_se(encoded);
assert_se((size_t) l == strlen(encoded));
- assert_se(unbase64mem(encoded, SIZE_MAX, &decoded, &decoded_size) >= 0);
+ assert_se(unbase64mem(encoded, &decoded, &decoded_size) >= 0);
assert_se(decoded_size == n);
assert_se(memcmp(data, decoded, n) == 0);
_cleanup_free_ void *buffer = NULL;
size_t size = 0;
- assert_se(unbase64mem(input, SIZE_MAX, &buffer, &size) == ret);
+ assert_se(unbase64mem(input, &buffer, &size) == ret);
if (ret >= 0) {
assert_se(size == strlen(output));
assert_se(memcmp(buffer, output, size) == 0);
size_t size;
/* This is regular base64 */
- assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g/xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5+p8e6itqrIwzecu98+rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0);
+ assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g/xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5+p8e6itqrIwzecu98+rNyUVDhWBzS0PMwxEw==", &buffer, &size) >= 0);
assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0);
buffer = mfree(buffer);
/* This is the same but in base64url */
- assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g_xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5-p8e6itqrIwzecu98-rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0);
+ assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g_xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5-p8e6itqrIwzecu98-rNyUVDhWBzS0PMwxEw==", &buffer, &size) >= 0);
assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0);
/* Hint: use xxd -i to generate the static C array from some data, and basenc --base64 + basenc
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "iovec-util.h"
+#include "tests.h"
+
+TEST(iovec_memcmp) {
+ struct iovec iov1 = CONST_IOVEC_MAKE_STRING("abcdef"), iov2 = IOVEC_MAKE_STRING("bcdefg"), empty = {};
+
+ struct iovec iov1_truncated = iov1;
+ iov1_truncated.iov_len /= 2;
+
+ assert_se(iovec_memcmp(NULL, NULL) == 0);
+ assert_se(iovec_memcmp(&iov1, &iov1) == 0);
+ assert_se(iovec_memcmp(&iov2, &iov2) == 0);
+ assert_se(iovec_memcmp(&empty, &empty) == 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &iov1_truncated) == 0);
+ assert_se(iovec_memcmp(&empty, NULL) == 0);
+ assert_se(iovec_memcmp(NULL, &empty) == 0);
+ assert_se(iovec_memcmp(&iov1, &iov2) < 0);
+ assert_se(iovec_memcmp(&iov2, &iov1) > 0);
+ assert_se(iovec_memcmp(&iov1, &empty) > 0);
+ assert_se(iovec_memcmp(&empty, &iov1) < 0);
+ assert_se(iovec_memcmp(&iov2, &empty) > 0);
+ assert_se(iovec_memcmp(&empty, &iov2) < 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &empty) > 0);
+ assert_se(iovec_memcmp(&empty, &iov1_truncated) < 0);
+ assert_se(iovec_memcmp(&iov1, &iov1_truncated) > 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &iov1) < 0);
+ assert_se(iovec_memcmp(&iov2, &iov1_truncated) > 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &iov2) < 0);
+
+ _cleanup_(iovec_done) struct iovec copy = {};
+
+ assert_se(iovec_memdup(&iov1, ©));
+ assert_se(iovec_memcmp(&iov1, ©) == 0);
+}
+
+TEST(iovec_set_and_valid) {
+ struct iovec empty = {},
+ filled = CONST_IOVEC_MAKE_STRING("waldo"),
+ half = { .iov_base = (char*) "piff", .iov_len = 0 },
+ invalid = { .iov_base = NULL, .iov_len = 47 };
+
+ assert_se(!iovec_is_set(NULL));
+ assert_se(!iovec_is_set(&empty));
+ assert_se(iovec_is_set(&filled));
+ assert_se(!iovec_is_set(&half));
+ assert_se(!iovec_is_set(&invalid));
+
+ assert_se(iovec_is_valid(NULL));
+ assert_se(iovec_is_valid(&empty));
+ assert_se(iovec_is_valid(&filled));
+ assert_se(iovec_is_valid(&half));
+ assert_se(!iovec_is_valid(&invalid));
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
+#include "iovec-util.h"
#include "json-internal.h"
#include "json.h"
#include "math-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
assert_se(json_variant_has_type(w, json_variant_type(v)));
assert_se(json_variant_equal(v, w));
+ s = mfree(s);
+ r = json_variant_format(w, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r == -EPERM);
+ assert_se(!s);
+
+ s = mfree(s);
+ r = json_variant_format(w, JSON_FORMAT_PRETTY, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+
s = mfree(s);
w = json_variant_unref(w);
assert_se(foobar.l == INT16_MIN);
}
+typedef enum mytestenum {
+ myfoo, mybar, mybaz, _mymax, _myinvalid = -EINVAL,
+} mytestenum;
+
+static const char *mytestenum_table[_mymax] = {
+ [myfoo] = "myfoo",
+ [mybar] = "mybar",
+ [mybaz] = "mybaz",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(mytestenum, mytestenum);
+
+static JSON_DISPATCH_ENUM_DEFINE(dispatch_mytestenum, mytestenum, mytestenum_from_string);
+
+TEST(json_dispatch_enum_define) {
+
+ struct data {
+ mytestenum a, b, c, d;
+ } data = {
+ .a = _myinvalid,
+ .b = _myinvalid,
+ .c = _myinvalid,
+ .d = mybar,
+ };
+
+ _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
+
+ assert_se(json_build(&j, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("a", JSON_BUILD_STRING("mybaz")),
+ JSON_BUILD_PAIR("b", JSON_BUILD_STRING("mybar")),
+ JSON_BUILD_PAIR("c", JSON_BUILD_STRING("myfoo")),
+ JSON_BUILD_PAIR("d", JSON_BUILD_NULL))) >= 0);
+
+ assert_se(json_dispatch(j,
+ (const JsonDispatch[]) {
+ { "a", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, a), 0 },
+ { "b", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, b), 0 },
+ { "c", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, c), 0 },
+ { "d", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, d), 0 },
+ {},
+ },
+ /* flags= */ 0,
+ &data) >= 0);
+
+ assert(data.a == mybaz);
+ assert(data.b == mybar);
+ assert(data.c == myfoo);
+ assert(data.d < 0);
+}
+
+TEST(json_sensitive) {
+ _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL, *v = NULL;
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert_se(json_build(&a, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "bar", "baz", "foo", "qux", "baz"))) >= 0);
+ assert_se(json_build(&b, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "qux"))) >= 0);
+
+ json_variant_sensitive(a);
+
+ assert_se(json_variant_format(a, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+
+ r = json_variant_format(b, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+ s = mfree(s);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+ s = mfree(s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+ s = mfree(s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR_VARIANT("a", a),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR_VARIANT("a", a),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR_VARIANT("a", a),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT),
+ JSON_BUILD_PAIR_VARIANT("a", a))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+}
+
+TEST(json_iovec) {
+ struct iovec iov1 = CONST_IOVEC_MAKE_STRING("üxknürz"), iov2 = CONST_IOVEC_MAKE_STRING("wuffwuffmiau");
+
+ _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
+ assert_se(json_build(&j, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("nr1", JSON_BUILD_IOVEC_BASE64(&iov1)),
+ JSON_BUILD_PAIR("nr2", JSON_BUILD_IOVEC_HEX(&iov2)))) >= 0);
+
+ json_variant_dump(j, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, /* f= */ NULL, /* prefix= */ NULL);
+
+ _cleanup_(iovec_done) struct iovec a = {}, b = {};
+ assert_se(json_variant_unbase64_iovec(json_variant_by_key(j, "nr1"), &a) >= 0);
+ assert_se(json_variant_unhex_iovec(json_variant_by_key(j, "nr2"), &b) >= 0);
+
+ assert_se(iovec_memcmp(&iov1, &a) == 0);
+ assert_se(iovec_memcmp(&iov2, &b) == 0);
+ assert_se(iovec_memcmp(&iov2, &a) < 0);
+ assert_se(iovec_memcmp(&iov1, &b) > 0);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
TEST(valid_items) {
test_valid_item("any", AF_UNSPEC, 0, 0, 0);
+ test_valid_item("0-65535", AF_UNSPEC, 0, 0, 0);
test_valid_item("ipv4", AF_INET, 0, 0, 0);
test_valid_item("ipv6", AF_INET6, 0, 0, 0);
test_valid_item("ipv4:any", AF_INET, 0, 0, 0);
test_valid_item("udp", AF_UNSPEC, IPPROTO_UDP, 0, 0);
test_valid_item("tcp:any", AF_UNSPEC, IPPROTO_TCP, 0, 0);
test_valid_item("udp:any", AF_UNSPEC, IPPROTO_UDP, 0, 0);
+ test_valid_item("0", AF_UNSPEC, 0, 1, 0);
test_valid_item("6666", AF_UNSPEC, 0, 1, 6666);
test_valid_item("6666-6667", AF_UNSPEC, 0, 2, 6666);
test_valid_item("65535", AF_UNSPEC, 0, 1, 65535);
test_valid_item("ipv6:tcp:6666", AF_INET6, IPPROTO_TCP, 1, 6666);
test_valid_item("ipv6:udp:6666-6667", AF_INET6, IPPROTO_UDP, 2, 6666);
test_valid_item("ipv6:tcp:any", AF_INET6, IPPROTO_TCP, 0, 0);
+ test_valid_item("ipv6:tcp:0", AF_INET6, IPPROTO_TCP, 1, 0);
}
TEST(invalid_items) {
test_invalid_item("ipv6::");
test_invalid_item("ipv6:ipv6");
test_invalid_item("ipv6:icmp");
- test_invalid_item("ipv6:tcp:0");
test_invalid_item("65536");
- test_invalid_item("0-65535");
test_invalid_item("ipv6:tcp:6666-6665");
test_invalid_item("ipv6:tcp:6666-100000");
test_invalid_item("ipv6::6666");
}
}
+TEST(pid_get_start_time) {
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+
+ assert_se(pidref_set_self(&pidref) >= 0);
+
+ uint64_t start_time;
+ assert_se(pidref_get_start_time(&pidref, &start_time) >= 0);
+ log_info("our starttime: %" PRIu64, start_time);
+
+ _cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
+
+ assert_se(pidref_safe_fork("(stub)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS, &child) >= 0);
+
+ uint64_t start_time2;
+ assert_se(pidref_get_start_time(&child, &start_time2) >= 0);
+
+ log_info("child starttime: %" PRIu64, start_time2);
+
+ assert_se(start_time2 >= start_time);
+}
+
static int intro(void) {
log_show_color(true);
return EXIT_SUCCESS;
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) > 0);
}
+TEST(inode_type_from_string) {
+ static const mode_t types[] = {
+ S_IFREG,
+ S_IFDIR,
+ S_IFLNK,
+ S_IFCHR,
+ S_IFBLK,
+ S_IFIFO,
+ S_IFSOCK,
+ };
+
+ FOREACH_ARRAY(m, types, ELEMENTSOF(types))
+ assert_se(inode_type_from_string(inode_type_to_string(*m)) == *m);
+}
+
static int intro(void) {
log_show_color(true);
return EXIT_SUCCESS;
assert_se(strlevenshtein("sunday", "saturday") == 3);
}
+TEST(strrstr) {
+ assert_se(!strrstr(NULL, NULL));
+ assert_se(!strrstr("foo", NULL));
+ assert_se(!strrstr(NULL, "foo"));
+
+ const char *p = "foo";
+ assert_se(strrstr(p, "foo") == p);
+ assert_se(strrstr(p, "fo") == p);
+ assert_se(strrstr(p, "f") == p);
+ assert_se(strrstr(p, "oo") == p + 1);
+ assert_se(strrstr(p, "o") == p + 2);
+ assert_se(strrstr(p, "") == p + strlen(p));
+ assert_se(!strrstr(p, "bar"));
+
+ p = "xoxoxox";
+ assert_se(strrstr(p, "") == p + strlen(p));
+ assert_se(strrstr(p, "x") == p + 6);
+ assert_se(strrstr(p, "ox") == p + 5);
+ assert_se(strrstr(p, "xo") == p + 4);
+ assert_se(strrstr(p, "xox") == p + 4);
+ assert_se(!strrstr(p, "xx"));
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j"));
}
+TEST(endswith_strv) {
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("xxx", "yyy", "ldo", "zzz")), "ldo"));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("xxx", "yyy", "zzz")), NULL));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("waldo")), "waldo"));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("w", "o", "ldo")), "o"));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("knurz", "", "waldo")), ""));
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
assert_se(asprintf(&secret_string, "The classified documents are in room %x", parent_index) > 0);
size_t secret_size = strlen(secret_string) + 1;
- _cleanup_free_ void *blob = NULL;
- size_t blob_size = 0;
- _cleanup_free_ void *serialized_parent = NULL;
- size_t serialized_parent_size;
+ _cleanup_(iovec_done) struct iovec blob = {}, serialized_parent = {};
assert_se(tpm2_calculate_seal(
parent_index,
parent_public,
/* attributes= */ NULL,
- secret_string, secret_size,
+ &IOVEC_MAKE(secret_string, secret_size),
/* policy= */ NULL,
/* pin= */ NULL,
- /* ret_secret= */ NULL, /* ret_secret_size= */ 0,
- &blob, &blob_size,
- &serialized_parent, &serialized_parent_size) >= 0);
+ /* ret_secret= */ NULL,
+ &blob,
+ &serialized_parent) >= 0);
- _cleanup_free_ void *unsealed_secret = NULL;
- size_t unsealed_secret_size;
+ _cleanup_(iovec_done) struct iovec unsealed_secret = {};
assert_se(tpm2_unseal(
c,
/* hash_pcr_mask= */ 0,
/* pcr_bank= */ 0,
- /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature= */ NULL,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0,
- blob, blob_size,
- /* known_policy_hash= */ NULL, /* known_policy_hash_size= */ 0,
- serialized_parent, serialized_parent_size,
- &unsealed_secret, &unsealed_secret_size) >= 0);
+ &blob,
+ /* known_policy_hash= */ NULL,
+ &serialized_parent,
+ &unsealed_secret) >= 0);
- assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret, unsealed_secret_size) == 0);
+ assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret.iov_base, unsealed_secret.iov_len) == 0);
- char unsealed_string[unsealed_secret_size];
- assert_se(snprintf(unsealed_string, unsealed_secret_size, "%s", (char*) unsealed_secret) == (int) unsealed_secret_size - 1);
+ char unsealed_string[unsealed_secret.iov_len];
+ assert_se(snprintf(unsealed_string, unsealed_secret.iov_len, "%s", (char*) unsealed_secret.iov_base) == (int) unsealed_secret.iov_len - 1);
log_debug("Unsealed secret is: %s", unsealed_string);
}
log_debug("Check seal/unseal for handle 0x%" PRIx32, handle);
- _cleanup_free_ void *secret = NULL, *blob = NULL, *srk = NULL, *unsealed_secret = NULL;
- size_t secret_size, blob_size, srk_size, unsealed_secret_size;
+ _cleanup_(iovec_done) struct iovec secret = {}, blob = {}, srk = {}, unsealed_secret = {};
assert_se(tpm2_seal(
c,
handle,
&policy,
/* pin= */ NULL,
- &secret, &secret_size,
- &blob, &blob_size,
+ &secret,
+ &blob,
/* ret_primary_alg= */ NULL,
- &srk, &srk_size) >= 0);
+ &srk) >= 0);
assert_se(tpm2_unseal(
c,
/* hash_pcr_mask= */ 0,
/* pcr_bank= */ 0,
- /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature= */ NULL,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0,
- blob, blob_size,
- /* policy_hash= */ NULL, /* policy_hash_size= */ 0,
- srk, srk_size,
- &unsealed_secret, &unsealed_secret_size) >= 0);
+ &blob,
+ /* policy_hash= */ NULL,
+ &srk,
+ &unsealed_secret) >= 0);
- assert_se(memcmp_nn(secret, secret_size, unsealed_secret, unsealed_secret_size) == 0);
+ assert_se(iovec_memcmp(&secret, &unsealed_secret) == 0);
}
static void check_seal_unseal(Tpm2Context *c) {
#include "fs-util.h"
#include "tests.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
static void test_read_login_defs_one(const char *path) {
log_info("/* %s(\"%s\") */", __func__, path ?: "<custom>");
#include "virt.h"
TEST(uid_range) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
uid_t search;
assert_se(uid_range_covers(p, 0, 0));
}
TEST(load_userns) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
_cleanup_(unlink_and_freep) char *fn = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
}
TEST(uid_range_coalesce) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
for (size_t i = 0; i < 10; i++) {
assert_se(uid_range_add_internal(&p, i * 10, 10, /* coalesce = */ false) >= 0);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "tests.h"
+#include "tmpfile-util.h"
+#include "vpick.h"
+
+TEST(path_pick) {
+ _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
+ _cleanup_close_ int dfd = -EBADF, sub_dfd = -EBADF;
+
+ dfd = mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p);
+ assert(dfd >= 0);
+
+ sub_dfd = open_mkdir_at(dfd, "foo.v", O_CLOEXEC, 0777);
+ assert(sub_dfd >= 0);
+
+ assert_se(write_string_file_at(sub_dfd, "foo_5.5.raw", "5.5", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_55.raw", "55", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_5.raw", "5", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_5_ia64.raw", "5", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_7.raw", "7", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_7_x86-64.raw", "7 64bit", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_55_x86-64.raw", "55 64bit", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_55_x86.raw", "55 32bit", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "foo_99_x86.raw", "99 32bit", WRITE_STRING_FILE_CREATE) >= 0);
+
+ /* Let's add an entry for sparc (which is a valid arch, but almost certainly not what we test
+ * on). This entry should hence always be ignored */
+ if (native_architecture() != ARCHITECTURE_SPARC)
+ assert_se(write_string_file_at(sub_dfd, "foo_100_sparc.raw", "100 sparc", WRITE_STRING_FILE_CREATE) >= 0);
+
+ assert_se(write_string_file_at(sub_dfd, "quux_1_s390.raw", "waldo1", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "quux_2_s390+4-6.raw", "waldo2", WRITE_STRING_FILE_CREATE) >= 0);
+ assert_se(write_string_file_at(sub_dfd, "quux_3_s390+0-10.raw", "waldo3", WRITE_STRING_FILE_CREATE) >= 0);
+
+ _cleanup_free_ char *pp = NULL;
+ pp = path_join(p, "foo.v");
+ assert_se(pp);
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+
+ PickFilter filter = {
+ .architecture = _ARCHITECTURE_INVALID,
+ .suffix = ".raw",
+ };
+
+ if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "99"));
+ assert_se(result.architecture == ARCHITECTURE_X86);
+ assert_se(endswith(result.path, "/foo_99_x86.raw"));
+
+ pick_result_done(&result);
+ }
+
+ filter.architecture = ARCHITECTURE_X86_64;
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "55"));
+ assert_se(result.architecture == ARCHITECTURE_X86_64);
+ assert_se(endswith(result.path, "/foo_55_x86-64.raw"));
+ pick_result_done(&result);
+
+ filter.architecture = ARCHITECTURE_IA64;
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "5"));
+ assert_se(result.architecture == ARCHITECTURE_IA64);
+ assert_se(endswith(result.path, "/foo_5_ia64.raw"));
+ pick_result_done(&result);
+
+ filter.architecture = _ARCHITECTURE_INVALID;
+ filter.version = "5";
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "5"));
+ if (native_architecture() != ARCHITECTURE_IA64) {
+ assert_se(result.architecture == _ARCHITECTURE_INVALID);
+ assert_se(endswith(result.path, "/foo_5.raw"));
+ }
+ pick_result_done(&result);
+
+ filter.architecture = ARCHITECTURE_IA64;
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "5"));
+ assert_se(result.architecture == ARCHITECTURE_IA64);
+ assert_se(endswith(result.path, "/foo_5_ia64.raw"));
+ pick_result_done(&result);
+
+ filter.architecture = ARCHITECTURE_CRIS;
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == 0);
+ assert_se(result.st.st_mode == MODE_INVALID);
+ assert_se(!result.version);
+ assert_se(result.architecture < 0);
+ assert_se(!result.path);
+
+ assert_se(unlinkat(sub_dfd, "foo_99_x86.raw", 0) >= 0);
+
+ filter.architecture = _ARCHITECTURE_INVALID;
+ filter.version = NULL;
+ if (IN_SET(native_architecture(), ARCHITECTURE_X86_64, ARCHITECTURE_X86)) {
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "55"));
+
+ if (native_architecture() == ARCHITECTURE_X86_64) {
+ assert_se(result.architecture == ARCHITECTURE_X86_64);
+ assert_se(endswith(result.path, "/foo_55_x86-64.raw"));
+ } else {
+ assert_se(result.architecture == ARCHITECTURE_X86);
+ assert_se(endswith(result.path, "/foo_55_x86.raw"));
+ }
+ pick_result_done(&result);
+ }
+
+ /* Test explicit patterns in last component of path not being .v */
+ free(pp);
+ pp = path_join(p, "foo.v/foo___.raw");
+ assert_se(pp);
+
+ if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "55"));
+ assert_se(result.architecture == native_architecture());
+ assert_se(endswith(result.path, ".raw"));
+ assert_se(strrstr(result.path, "/foo_55_x86"));
+ pick_result_done(&result);
+ }
+
+ /* Specify an explicit path */
+ free(pp);
+ pp = path_join(p, "foo.v/foo_5.raw");
+ assert_se(pp);
+
+ filter.type_mask = UINT32_C(1) << DT_DIR;
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == -ENOTDIR);
+
+ filter.type_mask = UINT32_C(1) << DT_REG;
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(!result.version);
+ assert_se(result.architecture == _ARCHITECTURE_INVALID);
+ assert_se(path_equal(result.path, pp));
+ pick_result_done(&result);
+
+ free(pp);
+ pp = path_join(p, "foo.v");
+ assert_se(pp);
+
+ filter.architecture = ARCHITECTURE_S390;
+ filter.basename = "quux";
+
+ assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
+ assert_se(S_ISREG(result.st.st_mode));
+ assert_se(streq_ptr(result.version, "2"));
+ assert_se(result.tries_left == 4);
+ assert_se(result.tries_done == 6);
+ assert_se(endswith(result.path, "quux_2_s390+4-6.raw"));
+ assert_se(result.architecture == ARCHITECTURE_S390);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
#include "clock-util.h"
#include "conf-files.h"
#include "constants.h"
+#include "daemon-util.h"
#include "fd-util.h"
#include "fileio-label.h"
#include "fileio.h"
assert(c);
free(c->zone);
- bus_verify_polkit_async_registry_free(c->polkit_registry);
+ hashmap_free(c->polkit_registry);
sd_bus_message_unref(c->cache);
sd_bus_slot_unref(c->slot_job_removed);
umask(0022);
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
-
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
(void) sd_event_set_watchdog(event, true);
- r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to install SIGINT handler: %m");
-
- r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+ r = sd_event_set_signal_exit(event, true);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGTERM handler: %m");
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
r = connect_bus(&context, event, &bus);
if (r < 0)
if (r < 0)
return r;
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
if (r < 0)
return r;
- if (addr.sa.sa_family == AF_INET)
- (void) setsockopt_int(m->server_socket, IPPROTO_IP, IP_TOS, IPTOS_LOWDELAY);
+ (void) socket_set_option(m->server_socket, addr.sa.sa_family, IP_TOS, IPV6_TCLASS, IPTOS_DSCP_EF);
return sd_event_add_io(m->event, &m->event_receive, m->server_socket, EPOLLIN, manager_receive_response, m);
}
sd_bus_flush_close_unref(m->bus);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
return mfree(m);
}
#include "log.h"
#include "macro.h"
#include "main-func.h"
-#include "missing_stat.h"
#include "missing_syscall.h"
#include "mkdir-label.h"
#include "mount-util.h"
#include "set.h"
#include "sort-util.h"
#include "specifier.h"
+#include "stat-util.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
OPERATION_CREATE = 1 << 0,
OPERATION_REMOVE = 1 << 1,
OPERATION_CLEAN = 1 << 2,
+ OPERATION_PURGE = 1 << 3,
} OperationMask;
typedef enum ItemType {
Set *unix_sockets;
} Context;
-STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep);
-STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
return 0;
}
+static bool needs_purge(ItemType t) {
+ return IN_SET(t,
+ COPY_FILES,
+ TRUNCATE_FILE,
+ CREATE_FILE,
+ WRITE_FILE,
+ EMPTY_DIRECTORY,
+ CREATE_SUBVOLUME,
+ CREATE_SUBVOLUME_INHERIT_QUOTA,
+ CREATE_SUBVOLUME_NEW_QUOTA,
+ CREATE_CHAR_DEVICE,
+ CREATE_BLOCK_DEVICE,
+ CREATE_SYMLINK,
+ CREATE_FIFO,
+ CREATE_DIRECTORY,
+ TRUNCATE_DIRECTORY);
+}
+
static bool needs_glob(ItemType t) {
return IN_SET(t,
WRITE_FILE,
- IGNORE_PATH,
- IGNORE_DIRECTORY_PATH,
- REMOVE_PATH,
- RECURSIVE_REMOVE_PATH,
EMPTY_DIRECTORY,
- ADJUST_MODE,
- RELABEL_PATH,
- RECURSIVE_RELABEL_PATH,
SET_XATTR,
RECURSIVE_SET_XATTR,
SET_ACL,
RECURSIVE_SET_ACL,
SET_ATTRIBUTE,
- RECURSIVE_SET_ATTRIBUTE);
+ RECURSIVE_SET_ATTRIBUTE,
+ IGNORE_PATH,
+ IGNORE_DIRECTORY_PATH,
+ REMOVE_PATH,
+ RECURSIVE_REMOVE_PATH,
+ RELABEL_PATH,
+ RECURSIVE_RELABEL_PATH,
+ ADJUST_MODE);
}
static bool takes_ownership(ItemType t) {
CREATE_FILE,
TRUNCATE_FILE,
CREATE_DIRECTORY,
- EMPTY_DIRECTORY,
TRUNCATE_DIRECTORY,
CREATE_SUBVOLUME,
CREATE_SUBVOLUME_INHERIT_QUOTA,
CREATE_BLOCK_DEVICE,
COPY_FILES,
WRITE_FILE,
+ EMPTY_DIRECTORY,
IGNORE_PATH,
IGNORE_DIRECTORY_PATH,
REMOVE_PATH,
return xopendirat_nomod(AT_FDCWD, path);
}
-static nsec_t load_statx_timestamp_nsec(const struct statx_timestamp *ts) {
- assert(ts);
-
- if (ts->tv_sec < 0)
- return NSEC_INFINITY;
-
- if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
- return NSEC_INFINITY;
-
- return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
-}
-
static bool needs_cleanup(
nsec_t atime,
nsec_t btime,
}
}
- atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? load_statx_timestamp_nsec(&sx.stx_atime) : 0;
- mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? load_statx_timestamp_nsec(&sx.stx_mtime) : 0;
- ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? load_statx_timestamp_nsec(&sx.stx_ctime) : 0;
- btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? load_statx_timestamp_nsec(&sx.stx_btime) : 0;
+ atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0;
+ mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0;
+ ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0;
+ btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0;
sub_path = path_join(p, de->d_name);
if (!sub_path) {
return 0;
}
+static int purge_item_instance(Context *c, Item *i, const char *instance, CreationMode creation) {
+ int r;
+
+ /* FIXME: we probably should use dir_cleanup() here instead of rm_rf() so that 'x' is honoured. */
+ log_debug("rm -rf \"%s\"", instance);
+ r = rm_rf(instance, REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "rm_rf(%s): %m", instance);
+
+ return 0;
+}
+
+static int purge_item(Context *c, Item *i) {
+
+ assert(i);
+
+ if (!needs_purge(i->type))
+ return 0;
+
+ log_debug("Running purge owned action for entry %c %s", (char) i->type, i->path);
+
+ if (needs_glob(i->type))
+ return glob_item(c, i, purge_item_instance);
+
+ return purge_item_instance(c, i, i->path, CREATION_EXISTING);
+}
+
static int remove_item_instance(
Context *c,
Item *i,
}
return dir_cleanup(c, i, instance, d,
- load_statx_timestamp_nsec(&sx.stx_atime),
- load_statx_timestamp_nsec(&sx.stx_mtime),
+ statx_timestamp_load_nsec(&sx.stx_atime),
+ statx_timestamp_load_nsec(&sx.stx_mtime),
cutoff * NSEC_PER_USEC,
sx.stx_dev_major, sx.stx_dev_minor, mountpoint,
MAX_DEPTH, i->keep_first_level,
switch (i->type) {
case CREATE_DIRECTORY:
+ case TRUNCATE_DIRECTORY:
case CREATE_SUBVOLUME:
case CREATE_SUBVOLUME_INHERIT_QUOTA:
case CREATE_SUBVOLUME_NEW_QUOTA:
- case TRUNCATE_DIRECTORY:
- case IGNORE_PATH:
case COPY_FILES:
clean_item_instance(c, i, i->path, CREATION_EXISTING);
return 0;
case EMPTY_DIRECTORY:
+ case IGNORE_PATH:
case IGNORE_DIRECTORY_PATH:
return glob_item(c, i, clean_item_instance);
OperationMask todo;
_cleanup_free_ char *_path = NULL;
const char *path;
- int r, q, p;
+ int r;
assert(c);
assert(i);
if (i->allow_failure)
r = 0;
- q = FLAGS_SET(operation, OPERATION_REMOVE) ? remove_item(c, i) : 0;
- p = FLAGS_SET(operation, OPERATION_CLEAN) ? clean_item(c, i) : 0;
+ RET_GATHER(r, FLAGS_SET(operation, OPERATION_REMOVE) ? remove_item(c, i) : 0);
+ RET_GATHER(r, FLAGS_SET(operation, OPERATION_CLEAN) ? clean_item(c, i) : 0);
+ RET_GATHER(r, FLAGS_SET(operation, OPERATION_PURGE) ? purge_item(c, i) : 0);
- return r < 0 ? r :
- q < 0 ? q :
- p;
+ return r;
}
static int process_item_array(
r = process_item_array(c, array->parent, operation & OPERATION_CREATE);
/* Clean up all children first */
- if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN)) && !set_isempty(array->children)) {
+ if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE)) && !set_isempty(array->children)) {
ItemArray *cc;
SET_FOREACH(cc, array->children) {
int k;
- k = process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN));
+ k = process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE));
if (k < 0 && r == 0)
r = k;
}
_cleanup_free_ void *data = NULL;
size_t data_size = 0;
- r = unbase64mem(item_binary_argument(&i), item_binary_argument_size(&i), &data, &data_size);
+ r = unbase64mem_full(item_binary_argument(&i), item_binary_argument_size(&i), /* secure = */ false,
+ &data, &data_size);
if (r < 0)
return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument);
" --remove Remove marked files/directories\n"
" --boot Execute actions only safe at boot\n"
" --graceful Quietly ignore unknown users or groups\n"
+ " --purge Delete all files owned by the configuration files\n"
" --prefix=PATH Only apply rules with the specified prefix\n"
" --exclude-prefix=PATH Ignore rules with the specified prefix\n"
" -E Ignore rules prefixed with /dev, /proc, /run, /sys\n"
ARG_CREATE,
ARG_CLEAN,
ARG_REMOVE,
+ ARG_PURGE,
ARG_BOOT,
ARG_GRACEFUL,
ARG_PREFIX,
{ "create", no_argument, NULL, ARG_CREATE },
{ "clean", no_argument, NULL, ARG_CLEAN },
{ "remove", no_argument, NULL, ARG_REMOVE },
+ { "purge", no_argument, NULL, ARG_PURGE },
{ "boot", no_argument, NULL, ARG_BOOT },
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
{ "prefix", required_argument, NULL, ARG_PREFIX },
arg_boot = true;
break;
+ case ARG_PURGE:
+ arg_operation |= OPERATION_PURGE;
+ break;
+
case ARG_GRACEFUL:
arg_graceful = true;
break;
case ARG_PREFIX:
- if (strv_push(&arg_include_prefixes, optarg) < 0)
+ if (strv_extend(&arg_include_prefixes, optarg) < 0)
return log_oom();
break;
case ARG_EXCLUDE_PREFIX:
- if (strv_push(&arg_exclude_prefixes, optarg) < 0)
+ if (strv_extend(&arg_exclude_prefixes, optarg) < 0)
return log_oom();
break;
if (arg_operation == 0 && arg_cat_flags == CAT_CONFIG_OFF)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "You need to specify at least one of --clean, --create, or --remove.");
+ "You need to specify at least one of --clean, --create, --remove, or --purge.");
if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
bool invalid_config = false;
ItemArray *a;
enum {
+ PHASE_PURGE,
PHASE_REMOVE_AND_CLEAN,
PHASE_CREATE,
_PHASE_MAX
for (phase = 0; phase < _PHASE_MAX; phase++) {
OperationMask op;
- if (phase == PHASE_REMOVE_AND_CLEAN)
+ if (phase == PHASE_PURGE)
+ op = arg_operation & OPERATION_PURGE;
+ else if (phase == PHASE_REMOVE_AND_CLEAN)
op = arg_operation & (OPERATION_REMOVE|OPERATION_CLEAN);
else if (phase == PHASE_CREATE)
op = arg_operation & OPERATION_CREATE;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "generator.h"
+#include "parse-util.h"
#include "proc-cmdline.h"
#include "special.h"
#include "tpm2-util.h"
-#include "parse-util.h"
/* A small generator that enqueues tpm2.target as synchronization point if the TPM2 device hasn't shown up
* yet, but the firmware reports it to exist. This is supposed to deal with systems where the TPM2 driver
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(LinkConfig, conditions)
Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(LinkConfig, conditions)
Link.Description, config_parse_string, 0, offsetof(LinkConfig, description)
+Link.Property, config_parse_udev_property, 0, offsetof(LinkConfig, properties)
+Link.ImportProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, import_properties)
+Link.UnsetProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, unset_properties)
Link.MACAddressPolicy, config_parse_mac_address_policy, 0, offsetof(LinkConfig, mac_address_policy)
Link.MACAddress, config_parse_hw_addr, 0, offsetof(LinkConfig, hw_addr)
Link.NamePolicy, config_parse_name_policy, 0, offsetof(LinkConfig, name_policy)
#include "creds-util.h"
#include "device-private.h"
#include "device-util.h"
+#include "env-util.h"
+#include "escape.h"
#include "ethtool-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "random-util.h"
+#include "specifier.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
+#include "udev-builtin.h"
#include "utf8.h"
+static const Specifier link_specifier_table[] = {
+ COMMON_SYSTEM_SPECIFIERS,
+ COMMON_TMP_SPECIFIERS,
+ {}
+};
+
struct LinkConfigContext {
LIST_HEAD(LinkConfig, configs);
int ethtool_fd;
condition_free_list(config->conditions);
free(config->description);
+ strv_free(config->properties);
+ strv_free(config->import_properties);
+ strv_free(config->unset_properties);
free(config->name_policy);
free(config->name);
strv_free(config->alternative_names);
return NULL;
sd_device_unref(link->device);
+ sd_device_unref(link->device_db_clone);
free(link->kind);
strv_free(link->altnames);
return mfree(link);
}
-int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret) {
+int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret) {
_cleanup_(link_freep) Link *link = NULL;
int r;
assert(ctx);
assert(rtnl);
assert(device);
+ assert(device_db_clone);
assert(ret);
link = new(Link, 1);
*link = (Link) {
.device = sd_device_ref(device),
+ .device_db_clone = sd_device_ref(device_db_clone),
};
r = sd_device_get_sysname(device, &link->ifname);
return 0;
}
-int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) {
- int r;
+static int link_apply_udev_properties(Link *link, bool test) {
+ LinkConfig *config;
+ sd_device *device;
- assert(ctx);
- assert(rtnl);
assert(link);
- if (!IN_SET(link->action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) {
- log_link_debug(link, "Skipping to apply .link settings on '%s' uevent.",
- device_action_to_string(link->action));
+ config = ASSERT_PTR(link->config);
+ device = ASSERT_PTR(link->device);
- link->new_name = link->ifname;
- return 0;
+ /* 1. apply ImportProperty=. */
+ STRV_FOREACH(p, config->import_properties)
+ (void) udev_builtin_import_property(device, link->device_db_clone, test, *p);
+
+ /* 2. apply Property=. */
+ STRV_FOREACH(p, config->properties) {
+ _cleanup_free_ char *key = NULL;
+ const char *eq;
+
+ eq = strchr(*p, '=');
+ if (!eq)
+ continue;
+
+ key = strndup(*p, eq - *p);
+ if (!key)
+ return log_oom();
+
+ (void) udev_builtin_add_property(device, test, key, eq + 1);
}
+ /* 3. apply UnsetProperty=. */
+ STRV_FOREACH(p, config->unset_properties)
+ (void) udev_builtin_add_property(device, test, *p, NULL);
+
+ /* 4. set the default properties. */
+ (void) udev_builtin_add_property(device, test, "ID_NET_LINK_FILE", config->filename);
+
+ _cleanup_free_ char *joined = NULL;
+ STRV_FOREACH(d, config->dropins) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = xescape(*d, ":");
+ if (!escaped)
+ return log_oom();
+
+ if (!strextend_with_separator(&joined, ":", escaped))
+ return log_oom();
+ }
+
+ (void) udev_builtin_add_property(device, test, "ID_NET_LINK_FILE_DROPINS", joined);
+
+ if (link->new_name)
+ (void) udev_builtin_add_property(device, test, "ID_NET_NAME", link->new_name);
+
+ return 0;
+}
+
+int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, bool test) {
+ int r;
+
+ assert(ctx);
+ assert(rtnl);
+ assert(link);
+
r = link_apply_ethtool_settings(link, &ctx->ethtool_fd);
if (r < 0)
return r;
if (r < 0)
return r;
+ r = link_apply_udev_properties(link, test);
+ if (r < 0)
+ return r;
+
return 0;
}
+int config_parse_udev_property(
+ 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 ***properties = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *properties = strv_free(*properties);
+ return 0;
+ }
+
+ for (const char *p = rvalue;; ) {
+ _cleanup_free_ char *word = NULL, *resolved = NULL, *key = NULL;
+ const char *eq;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve specifiers in %s, ignoring assignment: %m", word);
+ continue;
+ }
+
+ /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */
+ if (!env_assignment_is_valid(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property, ignoring assignment: %s", word);
+ continue;
+ }
+
+ assert_se(eq = strchr(resolved, '='));
+ key = strndup(resolved, eq - resolved);
+ if (!key)
+ return log_oom();
+
+ if (!device_property_can_set(key)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name '%s', ignoring assignment: %s", key, resolved);
+ continue;
+ }
+
+ r = strv_env_replace_consume(properties, TAKE_PTR(resolved));
+ if (r < 0)
+ return log_error_errno(r, "Failed to update properties: %m");
+ }
+}
+
+int config_parse_udev_property_name(
+ 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 ***properties = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *properties = strv_free(*properties);
+ return 0;
+ }
+
+ for (const char *p = rvalue;; ) {
+ _cleanup_free_ char *word = NULL, *resolved = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve specifiers in %s, ignoring assignment: %m", word);
+ continue;
+ }
+
+ /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */
+ if (!env_name_is_valid(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name, ignoring assignment: %s", resolved);
+ continue;
+ }
+
+ if (!device_property_can_set(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name, ignoring assignment: %s", resolved);
+ continue;
+ }
+
+ r = strv_consume(properties, TAKE_PTR(resolved));
+ if (r < 0)
+ return log_error_errno(r, "Failed to update properties: %m");
+ }
+}
+
int config_parse_ifalias(
const char *unit,
const char *filename,
LinkConfig *config;
sd_device *device;
+ sd_device *device_db_clone;
sd_device_action_t action;
char *kind;
LIST_HEAD(Condition, conditions);
char *description;
+ char **properties;
+ char **import_properties;
+ char **unset_properties;
struct hw_addr_data hw_addr;
MACAddressPolicy mac_address_policy;
NamePolicy *name_policy;
int link_config_load(LinkConfigContext *ctx);
bool link_config_should_reload(LinkConfigContext *ctx);
-int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret);
+int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret);
Link *link_free(Link *link);
DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free);
int link_get_config(LinkConfigContext *ctx, Link *link);
-int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link);
+int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, bool test);
const char *mac_address_policy_to_string(MACAddressPolicy p) _const_;
MACAddressPolicy mac_address_policy_from_string(const char *p) _pure_;
/* gperf lookup function */
const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+CONFIG_PARSER_PROTOTYPE(config_parse_udev_property);
+CONFIG_PARSER_PROTOTYPE(config_parse_udev_property_name);
CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
CONFIG_PARSER_PROTOTYPE(config_parse_rx_tx_queues);
CONFIG_PARSER_PROTOTYPE(config_parse_txqueuelen);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
+#include "device-private.h"
#include "device-util.h"
-#include "escape.h"
#include "errno-util.h"
#include "link-config.h"
#include "log.h"
static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool test) {
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
_cleanup_(link_freep) Link *link = NULL;
- _cleanup_free_ char *joined = NULL;
int r;
if (argc > 1)
return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
- r = link_new(ctx, &event->rtnl, dev, &link);
+ sd_device_action_t action;
+ r = sd_device_get_action(dev, &action);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to get action: %m");
+
+ if (!IN_SET(action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) {
+ log_device_debug(dev, "Skipping to apply .link settings on '%s' uevent.",
+ device_action_to_string(action));
+
+ /* Import previously assigned .link file name. */
+ (void) udev_builtin_import_property(dev, event->dev_db_clone, test, "ID_NET_LINK_FILE");
+ (void) udev_builtin_import_property(dev, event->dev_db_clone, test, "ID_NET_LINK_FILE_DROPINS");
+
+ /* Set ID_NET_NAME= with the current interface name. */
+ const char *value;
+ if (sd_device_get_sysname(dev, &value) >= 0)
+ (void) udev_builtin_add_property(dev, test, "ID_NET_NAME", value);
+
+ return 0;
+ }
+
+ r = link_new(ctx, &event->rtnl, dev, event->dev_db_clone, &link);
if (r == -ENODEV) {
log_device_debug_errno(dev, r, "Link vanished while getting information, ignoring.");
return 0;
return log_device_error_errno(dev, r, "Failed to get link config: %m");
}
- r = link_apply_config(ctx, &event->rtnl, link);
+ r = link_apply_config(ctx, &event->rtnl, link, test);
if (r == -ENODEV)
log_device_debug_errno(dev, r, "Link vanished while applying configuration, ignoring.");
else if (r < 0)
log_device_warning_errno(dev, r, "Could not apply link configuration, ignoring: %m");
- udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE", link->config->filename);
- if (link->new_name)
- udev_builtin_add_property(dev, test, "ID_NET_NAME", link->new_name);
-
event->altnames = TAKE_PTR(link->altnames);
- STRV_FOREACH(d, link->config->dropins) {
- _cleanup_free_ char *escaped = NULL;
-
- escaped = xescape(*d, ":");
- if (!escaped)
- return log_oom();
-
- if (!strextend_with_separator(&joined, ":", escaped))
- return log_oom();
- }
-
- udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE_DROPINS", joined);
-
return 0;
}
static void add_id_with_usb_revision(sd_device *dev, bool test, char *path) {
char *p;
- int r;
assert(dev);
assert(path);
if (p[1] != '-')
return;
- r = udev_builtin_add_property(dev, test, "ID_PATH_WITH_USB_REVISION", path);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH_WITH_USB_REVISION property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_WITH_USB_REVISION", path);
/* Drop the USB revision specifier for backward compatibility. */
memmove(p - 1, p + 1, strlen(p + 1) + 1);
static void add_id_tag(sd_device *dev, bool test, const char *path) {
char tag[UDEV_NAME_SIZE];
size_t i = 0;
- int r;
/* compose valid udev tag name */
for (const char *p = path; *p; p++) {
i--;
tag[i] = '\0';
- r = udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH_TAG property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
}
static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) {
add_id_with_usb_revision(dev, test, path);
- r = udev_builtin_add_property(dev, test, "ID_PATH", path);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH", path);
add_id_tag(dev, test, path);
* ID_PATH_ATA_COMPAT
*/
if (compat_path)
- udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path);
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path);
return 0;
}
return udev_builtin_add_property(dev, test, key, val);
}
+
+int udev_builtin_import_property(sd_device *dev, sd_device *src, bool test, const char *key) {
+ const char *val;
+ int r;
+
+ assert(dev);
+ assert(key);
+
+ if (!src)
+ return 0;
+
+ r = sd_device_get_property_value(src, key, &val);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_device_debug_errno(src, r, "Failed to get property \"%s\", ignoring: %m", key);
+
+ r = udev_builtin_add_property(dev, test, key, val);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
bool udev_builtin_should_reload(void);
int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val);
int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const char *valf, ...) _printf_(4, 5);
+int udev_builtin_import_property(sd_device *dev, sd_device *src, bool test, const char *key);
int udev_builtin_hwdb_lookup(sd_device *dev, const char *prefix, const char *modalias,
const char *filter, bool test);
return 1;
}
+static usec_t extra_timeout_usec(void) {
+ static usec_t saved = 10 * USEC_PER_SEC;
+ static bool parsed = false;
+ usec_t timeout;
+ const char *e;
+ int r;
+
+ if (parsed)
+ return saved;
+
+ parsed = true;
+
+ e = getenv("SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC");
+ if (!e)
+ return saved;
+
+ r = parse_sec(e, &timeout);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC=%s, ignoring: %m", e);
+
+ if (timeout > 5 * USEC_PER_HOUR) /* Add an arbitrary upper bound */
+ log_debug("Parsed $SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC=%s is too large, ignoring.", e);
+ else
+ saved = timeout;
+
+ return saved;
+}
+
static void worker_attach_event(Worker *worker, Event *event) {
Manager *manager = ASSERT_PTR(ASSERT_PTR(worker)->manager);
sd_event *e = ASSERT_PTR(manager->event);
udev_warn_timeout(manager->timeout_usec), USEC_PER_SEC,
on_event_timeout_warning, event);
+ /* Manager.timeout_usec is also used as the timeout for running programs specified in
+ * IMPORT{program}=, PROGRAM=, or RUN=. Here, let's add an extra time before the manager
+ * kills a worker, to make it possible that the worker detects timed out of spawned programs,
+ * kills them, and finalizes the event. */
(void) sd_event_add_time_relative(e, &event->timeout_event, CLOCK_MONOTONIC,
- manager->timeout_usec, USEC_PER_SEC,
+ usec_add(manager->timeout_usec, extra_timeout_usec()), USEC_PER_SEC,
on_event_timeout, event);
}
}
if (!is_match) {
- if (STR_IN_SET(attr,
- "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER",
- "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS"))
+ if (!device_property_can_set(attr))
return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
"Invalid ENV attribute. '%s' cannot be set.", attr);
usec_t timeout_usec;
int timeout_signal;
usec_t event_birth_usec;
+ usec_t cmd_birth_usec;
bool accept_failure;
int fd_stdout;
int fd_stderr;
if (r < 0)
return log_device_debug_errno(spawn->device, r, "Failed to allocate sd-event object: %m");
- if (spawn->timeout_usec > 0) {
- usec_t usec, age_usec;
-
- usec = now(CLOCK_MONOTONIC);
- age_usec = usec - spawn->event_birth_usec;
- if (age_usec < spawn->timeout_usec) {
- if (spawn->timeout_warn_usec > 0 &&
- spawn->timeout_warn_usec < spawn->timeout_usec &&
- spawn->timeout_warn_usec > age_usec) {
- spawn->timeout_warn_usec -= age_usec;
-
- r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC,
- usec + spawn->timeout_warn_usec, USEC_PER_SEC,
- on_spawn_timeout_warning, spawn);
- if (r < 0)
- return log_device_debug_errno(spawn->device, r, "Failed to create timeout warning event source: %m");
- }
-
- spawn->timeout_usec -= age_usec;
-
+ if (spawn->timeout_usec != USEC_INFINITY) {
+ if (spawn->timeout_warn_usec < spawn->timeout_usec) {
r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC,
- usec + spawn->timeout_usec, USEC_PER_SEC, on_spawn_timeout, spawn);
+ usec_add(spawn->cmd_birth_usec, spawn->timeout_warn_usec), USEC_PER_SEC,
+ on_spawn_timeout_warning, spawn);
if (r < 0)
- return log_device_debug_errno(spawn->device, r, "Failed to create timeout event source: %m");
+ return log_device_debug_errno(spawn->device, r, "Failed to create timeout warning event source: %m");
}
+
+ r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC,
+ usec_add(spawn->cmd_birth_usec, spawn->timeout_usec), USEC_PER_SEC,
+ on_spawn_timeout, spawn);
+ if (r < 0)
+ return log_device_debug_errno(spawn->device, r, "Failed to create timeout event source: %m");
}
if (spawn->fd_stdout >= 0) {
int timeout_signal = event->worker ? event->worker->timeout_signal : SIGKILL;
usec_t timeout_usec = event->worker ? event->worker->timeout_usec : DEFAULT_WORKER_TIMEOUT_USEC;
+ usec_t now_usec = now(CLOCK_MONOTONIC);
+ usec_t age_usec = usec_sub_unsigned(now_usec, event->birth_usec);
+ usec_t cmd_timeout_usec = usec_sub_unsigned(timeout_usec, age_usec);
+ if (cmd_timeout_usec <= 0)
+ return log_device_warning_errno(event->dev, SYNTHETIC_ERRNO(ETIME),
+ "The event already takes longer (%s) than the timeout (%s), skipping execution of '%s'.",
+ FORMAT_TIMESPAN(age_usec, 1), FORMAT_TIMESPAN(timeout_usec, 1), cmd);
/* pipes from child to parent */
if (result || log_get_max_level() >= LOG_INFO)
.cmd = cmd,
.pid = pid,
.accept_failure = accept_failure,
- .timeout_warn_usec = udev_warn_timeout(timeout_usec),
- .timeout_usec = timeout_usec,
+ .timeout_warn_usec = udev_warn_timeout(cmd_timeout_usec),
+ .timeout_usec = cmd_timeout_usec,
.timeout_signal = timeout_signal,
.event_birth_usec = event->birth_usec,
+ .cmd_birth_usec = now_usec,
.fd_stdout = outpipe[READ_END],
.fd_stderr = errpipe[READ_END],
.result = result,
log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command);
} else {
if (event->worker && event->worker->exec_delay_usec > 0) {
+ usec_t timeout_usec = event->worker ? event->worker->timeout_usec : DEFAULT_WORKER_TIMEOUT_USEC;
+ usec_t now_usec = now(CLOCK_MONOTONIC);
+ usec_t age_usec = usec_sub_unsigned(now_usec, event->birth_usec);
+
+ if (event->worker->exec_delay_usec >= usec_sub_unsigned(timeout_usec, age_usec)) {
+ log_device_warning(event->dev,
+ "Cannot delaying execution of \"%s\" for %s, skipping.",
+ command, FORMAT_TIMESPAN(event->worker->exec_delay_usec, USEC_PER_SEC));
+ continue;
+ }
+
log_device_debug(event->dev, "Delaying execution of \"%s\" for %s.",
command, FORMAT_TIMESPAN(event->worker->exec_delay_usec, USEC_PER_SEC));
(void) usleep_safe(event->worker->exec_delay_usec);
#include <stdio.h>
#include <stdlib.h>
+#include "device-private.h"
+#include "device-util.h"
#include "log.h"
#include "udev-builtin.h"
#include "udevadm.h"
int r;
log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
}
+ if (arg_action != SD_DEVICE_REMOVE) {
+ /* For net_setup_link */
+ r = device_clone_with_db(dev, &event->dev_db_clone);
+ if (r < 0) {
+ log_device_error_errno(dev, r, "Failed to clone device: %m");
+ goto finish;
+ }
+ }
+
r = udev_builtin_run(event, cmd, arg_command, true);
if (r < 0) {
log_debug_errno(r, "Builtin command '%s' fails: %m", arg_command);
int r;
log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
r = parse_argv(argc, argv);
if (r <= 0)
},
};
-static int table_add_uid_boundaries(Table *table, const UidRange *p) {
+static int table_add_uid_boundaries(Table *table, const UIDRange *p) {
int r;
assert(table);
static int table_add_uid_map(
Table *table,
- const UidRange *p,
+ const UIDRange *p,
int (*add_unavailable)(Table *t, uid_t start, uid_t end)) {
uid_t focus = 0;
assert(add_unavailable);
for (size_t i = 0; p && i < p->n_entries; i++) {
- UidRangeEntry *x = p->entries + i;
+ UIDRangeEntry *x = p->entries + i;
if (focus < x->start) {
r = add_unavailable(table, focus, x->start-1);
}
if (table) {
- _cleanup_(uid_range_freep) UidRange *uid_range = NULL;
+ _cleanup_(uid_range_freep) UIDRange *uid_range = NULL;
int boundary_lines, uid_map_lines;
r = uid_range_load_userns(&uid_range, "/proc/self/uid_map");
return 0;
}
-static int table_add_gid_boundaries(Table *table, const UidRange *p) {
+static int table_add_gid_boundaries(Table *table, const UIDRange *p) {
int r;
assert(table);
}
if (table) {
- _cleanup_(uid_range_freep) UidRange *gid_range = NULL;
+ _cleanup_(uid_range_freep) UIDRange *gid_range = NULL;
int boundary_lines, gid_map_lines;
r = uid_range_load_userns(&gid_range, "/proc/self/gid_map");
#include "sd-daemon.h"
#include "common-signal.h"
+#include "env-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "mkdir.h"
if (r < 0)
return log_error_errno(r, "Failed to fork new worker child: %m");
if (r == 0) {
- char pids[DECIMAL_STR_MAX(pid_t)];
/* Child */
if (m->listen_fd == 3) {
safe_close(m->listen_fd);
}
- xsprintf(pids, PID_FMT, pid);
- if (setenv("LISTEN_PID", pids, 1) < 0) {
- log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
+ r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
+ if (r < 0) {
+ log_error_errno(r, "Failed to set $LISTEN_PID: %m");
_exit(EXIT_FAILURE);
}
if (*data_what && *hash_what)
return 0;
- r = unhexmem(hash, strlen(hash), &m, &l);
+ r = unhexmem(hash, &m, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse hash: %s", hash);
if (l < sizeof(sd_id128_t)) {
size_t l;
void *m;
- r = unhexmem(val, strlen(val), &m, &l);
+ r = unhexmem(val, &m, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse salt '%s': %m", word);
if (!filename_is_valid(volume))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
- r = unhexmem(root_hash, SIZE_MAX, &m, &l);
+ r = unhexmem(root_hash, &m, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash: %m");
char *value;
if ((value = startswith(arg_root_hash_signature, "base64:"))) {
- r = unbase64mem(value, strlen(value), (void *)&hash_sig, &hash_sig_size);
+ r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature);
} else {
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executables += [
+ executable_template + {
+ 'name' : 'systemd-vpick',
+ 'public' : true,
+ 'sources' : files('vpick-tool.c'),
+ },
+]
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+
+#include "architecture.h"
+#include "build.h"
+#include "format-table.h"
+#include "fs-util.h"
+#include "main-func.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "vpick.h"
+
+static char *arg_filter_basename = NULL;
+static char *arg_filter_version = NULL;
+static Architecture arg_filter_architecture = _ARCHITECTURE_INVALID;
+static char *arg_filter_suffix = NULL;
+static uint32_t arg_filter_type_mask = 0;
+static enum {
+ PRINT_PATH,
+ PRINT_FILENAME,
+ PRINT_VERSION,
+ PRINT_TYPE,
+ PRINT_ARCHITECTURE,
+ PRINT_TRIES,
+ PRINT_ALL,
+ _PRINT_INVALID = -EINVAL,
+} arg_print = _PRINT_INVALID;
+static PickFlags arg_flags = PICK_ARCHITECTURE|PICK_TRIES;
+
+STATIC_DESTRUCTOR_REGISTER(arg_filter_basename, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_filter_version, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_filter_suffix, freep);
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-vpick", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] PATH...\n"
+ "\n%5$sPick entry from versioned directory.%6$s\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ "\n%3$sLookup Keys:%4$s\n"
+ " -B --basename=BASENAME\n"
+ " Look for specified basename\n"
+ " -V VERSION Look for specified version\n"
+ " -A ARCH Look for specified architecture\n"
+ " -S --suffix=SUFFIX Look for specified suffix\n"
+ " -t --type=TYPE Look for specified inode type\n"
+ "\n%3$sOutput:%4$s\n"
+ " -p --print=filename Print selected filename rather than path\n"
+ " -p --print=version Print selected version rather than path\n"
+ " -p --print=type Print selected inode type rather than path\n"
+ " -p --print=arch Print selected architecture rather than path\n"
+ " -p --print=tries Print selected tries left/tries done rather than path\n"
+ " -p --print=all Print all of the above\n"
+ " --resolve=yes Canonicalize the result path\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(), ansi_normal(),
+ ansi_highlight(), ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_RESOLVE,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "basename", required_argument, NULL, 'B' },
+ { "suffix", required_argument, NULL, 'S' },
+ { "type", required_argument, NULL, 't' },
+ { "print", required_argument, NULL, 'p' },
+ { "resolve", required_argument, NULL, ARG_RESOLVE },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hB:V:A:S:t:p:", options, NULL)) >= 0) {
+
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case 'B':
+ if (!filename_part_is_valid(optarg))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", optarg);
+
+ r = free_and_strdup_warn(&arg_filter_basename, optarg);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case 'V':
+ if (!version_is_valid(optarg))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", optarg);
+
+ r = free_and_strdup_warn(&arg_filter_version, optarg);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case 'A':
+ if (streq(optarg, "native"))
+ arg_filter_architecture = native_architecture();
+ else if (streq(optarg, "secondary")) {
+#ifdef ARCHITECTURE_SECONDARY
+ arg_filter_architecture = ARCHITECTURE_SECONDARY;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Local architecture has no secondary architecture.");
+#endif
+ } else if (streq(optarg, "uname"))
+ arg_filter_architecture = uname_architecture();
+ else if (streq(optarg, "auto"))
+ arg_filter_architecture = _ARCHITECTURE_INVALID;
+ else {
+ arg_filter_architecture = architecture_from_string(optarg);
+ if (arg_filter_architecture < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", optarg);
+ }
+ break;
+
+ case 'S':
+ if (!filename_part_is_valid(optarg))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", optarg);
+
+ r = free_and_strdup_warn(&arg_filter_suffix, optarg);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case 't':
+ if (isempty(optarg))
+ arg_filter_type_mask = 0;
+ else {
+ mode_t m;
+
+ m = inode_type_from_string(optarg);
+ if (m == MODE_INVALID)
+ return log_error_errno(m, "Unknown inode type: %s", optarg);
+
+ arg_filter_type_mask |= UINT32_C(1) << IFTODT(m);
+ }
+
+ break;
+
+ case 'p':
+ if (streq(optarg, "path"))
+ arg_print = PRINT_PATH;
+ else if (streq(optarg, "filename"))
+ arg_print = PRINT_FILENAME;
+ else if (streq(optarg, "version"))
+ arg_print = PRINT_VERSION;
+ else if (streq(optarg, "type"))
+ arg_print = PRINT_TYPE;
+ else if (STR_IN_SET(optarg, "arch", "architecture"))
+ arg_print = PRINT_ARCHITECTURE;
+ else if (streq(optarg, "tries"))
+ arg_print = PRINT_TRIES;
+ else if (streq(optarg, "all"))
+ arg_print = PRINT_ALL;
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --print= argument: %s", optarg);
+
+ break;
+
+ case ARG_RESOLVE:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --resolve= value: %m");
+
+ SET_FLAG(arg_flags, PICK_RESOLVE, r);
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+ }
+
+ if (arg_print < 0)
+ arg_print = PRINT_PATH;
+
+ return 1;
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_show_color(true);
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ if (optind >= argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to resolve must be specified.");
+
+ for (int i = optind; i < argc; i++) {
+ _cleanup_free_ char *p = NULL;
+ r = path_make_absolute_cwd(argv[i], &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make path '%s' absolute: %m", argv[i]);
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ p,
+ &(PickFilter) {
+ .basename = arg_filter_basename,
+ .version = arg_filter_version,
+ .architecture = arg_filter_architecture,
+ .suffix = arg_filter_suffix,
+ .type_mask = arg_filter_type_mask,
+ },
+ arg_flags,
+ &result);
+ if (r < 0)
+ return log_error_errno(r, "Failed to pick version for '%s': %m", p);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching version for '%s' found.", p);
+
+ switch (arg_print) {
+
+ case PRINT_PATH:
+ fputs(result.path, stdout);
+ if (result.st.st_mode != MODE_INVALID && S_ISDIR(result.st.st_mode) && !endswith(result.path, "/"))
+ fputc('/', stdout);
+ fputc('\n', stdout);
+ break;
+
+ case PRINT_FILENAME: {
+ _cleanup_free_ char *fname = NULL;
+
+ r = path_extract_filename(result.path, &fname);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract filename from path '%s': %m", result.path);
+
+ puts(fname);
+ break;
+ }
+
+ case PRINT_VERSION:
+ if (!result.version)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No version information discovered.");
+
+ puts(result.version);
+ break;
+
+ case PRINT_TYPE:
+ if (result.st.st_mode == MODE_INVALID)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No inode type information discovered.");
+
+ puts(inode_type_to_string(result.st.st_mode));
+ break;
+
+ case PRINT_ARCHITECTURE:
+ if (result.architecture < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No architecture information discovered.");
+
+ puts(architecture_to_string(result.architecture));
+ break;
+
+ case PRINT_TRIES:
+ if (result.tries_left == UINT_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No tries left/tries done information discovered.");
+
+ printf("+%u-%u", result.tries_left, result.tries_done);
+ break;
+
+ case PRINT_ALL: {
+ _cleanup_(table_unrefp) Table *t = NULL;
+
+ t = table_new_vertical();
+ if (!t)
+ return log_oom();
+
+ table_set_ersatz_string(t, TABLE_ERSATZ_NA);
+
+ r = table_add_many(
+ t,
+ TABLE_FIELD, "Path",
+ TABLE_PATH, result.path,
+ TABLE_FIELD, "Version",
+ TABLE_STRING, result.version,
+ TABLE_FIELD, "Type",
+ TABLE_STRING, result.st.st_mode == MODE_INVALID ? NULL : inode_type_to_string(result.st.st_mode),
+ TABLE_FIELD, "Architecture",
+ TABLE_STRING, result.architecture < 0 ? NULL : architecture_to_string(result.architecture));
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (result.tries_left != UINT_MAX) {
+ r = table_add_many(
+ t,
+ TABLE_FIELD, "Tries left",
+ TABLE_UINT, result.tries_left,
+ TABLE_FIELD, "Tries done",
+ TABLE_UINT, result.tries_done);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = table_print(t, stdout);
+ if (r < 0)
+ return table_log_print_error(r);
+
+ break;
+ }
+
+ default:
+ assert_not_reached();
+ }
+ }
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
ddebug "Support for p11-kit is disabled, skipping the PKCS#11 test"
return 1
fi
+ if ! "${SYSTEMCTL:?}" --version | grep -q "+OPENSSL"; then
+ ddebug "Support for openssl is disabled, skipping the PKCS#11 test"
+ return 1
+ fi
if ! "${SYSTEMCTL:?}" --version | grep -q "+LIBCRYPTSETUP\b"; then
ddebug "Support for libcryptsetup is disabled, skipping the PKCS#11 test"
return 1
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
+# (Hopefully) a temporary workaround for https://github.com/systemd/systemd/issues/30573
+KERNEL_APPEND="${KERNEL_APPEND:-} SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST=100"
+
test_append_files() {
local workspace="${1:?}"
install_mdadm
generate_module_dependencies
fi
+
+ image_install socat
}
do_test "$@"
address: fd00:dead:beef:cafe::/64
action: update
+ - id: transfer_acl
+ address: 10.0.0.0/24
+ address: fd00:dead:beef:cafe::/64
+ action: transfer
+
remote:
- id: parent_zone_server
address: 10.0.0.1@53
address: fd00:dead:beef:cafe::1@53
+ - id: forwarded
+ address: 10.99.0.1@53
+
submission:
- id: parent_zone_sbm
check-interval: 2s
parent: [parent_zone_server]
-# Auto ZSK/KSK rollover for DNSSEC-enabled zones + pushing the respective DS
-# records to the parent zone
policy:
+ # Auto ZSK/KSK rollover for DNSSEC-enabled zones + pushing the respective DS
+ # records to the parent zone
- id: auto_rollover
algorithm: ECDSAP256SHA256
cds-cdnskey-publish: always
zone-max-ttl: 1s
zsk-lifetime: 60d
-# Same as auto_rollover, but with NSEC3 turned on
-policy:
+ # Same as auto_rollover, but with NSEC3 turned on
- id: auto_rollover_nsec3
algorithm: ECDSAP256SHA256
cds-cdnskey-publish: always
zone-max-ttl: 1s
zsk-lifetime: 60d
-policy:
- id: untrusted
cds-cdnskey-publish: none
-# Manual ZSK/KSK management
-policy:
+ # Manual ZSK/KSK management
- id: manual
manual: on
-# Sign everything by default and propagate the respective DS records to the parent
+mod-dnsproxy:
+ - id: forwarded
+ remote: forwarded
+ fallback: off
+
template:
+ # Sign everything by default and propagate the respective DS records to the parent
- id: default
acl: update_acl
dnssec-policy: auto_rollover
semantic-checks: on
storage: "/var/lib/knot/zones"
-# A template for unsigned zones (i.e. without DNSSEC)
-template:
+ # A template for unsigned zones (i.e. without DNSSEC)
- id: unsigned
dnssec-signing: off
file: "%s.zone"
semantic-checks: on
storage: "/var/lib/knot/zones"
+ - id: forwarded
+ dnssec-signing: off
+ module: mod-dnsproxy/forwarded
+ zonefile-load: none
+
zone:
# Create our own DNSSEC-aware root zone, so we can test the whole chain of
# trust. This needs a ZSK/KSK keypair to be generated before running knot +
- domain: test
dnssec-policy: auto_rollover_nsec3
- # A fully (pre-)signed zone
+ # A fully (pre-)signed zone with allowed zone transfers (AXFR/IXFR)
- domain: signed.test
+ acl: [update_acl, transfer_acl]
# A fully (online)-signed zone
# See: https://www.knot-dns.cz/docs/3.1/singlehtml/index.html#mod-onlinesign
# An unsigned zone
- domain: unsigned.test
template: unsigned
+
+ # Forward all queries for this zone to our dummy test server
+ - domain: forwarded.test
+ template: forwarded
Description=Test for AmbientCapabilities (dynamic user)
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002081"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002081"'
Type=oneshot
AmbientCapabilities=CAP_CHOWN CAP_SETUID CAP_NET_RAW
DynamicUser=yes
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nfsnobody
AmbientCapabilities=CAP_CHOWN
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nobody
AmbientCapabilities=CAP_CHOWN
Description=Test for AmbientCapabilities (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=daemon
AmbientCapabilities=CAP_CHOWN
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nfsnobody
AmbientCapabilities=CAP_CHOWN CAP_NET_RAW
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nobody
AmbientCapabilities=CAP_CHOWN CAP_NET_RAW
Description=Test for AmbientCapabilities (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=daemon
AmbientCapabilities=CAP_CHOWN CAP_NET_RAW
# Also, through /tmp/test-exec-bindreadonlypaths
ExecStart=test -f /tmp/test-exec-bindreadonlypaths/thisisasimpletest
# The file cannot modify through /tmp/test-exec-bindreadonlypaths
-ExecStart=/bin/sh -x -c '! touch /tmp/test-exec-bindreadonlypaths/thisisasimpletest'
+ExecStart=sh -x -c '! touch /tmp/test-exec-bindreadonlypaths/thisisasimpletest'
# Cleanup
ExecStart=rm /tmp/thisisasimpletest
BindPaths=/tmp:/tmp/test-exec-bindpaths
[Service]
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep "^Bounding set .*cap_chown"'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep "^Bounding set .*cap_chown"'
Type=oneshot
CapabilityBoundingSet=~CAP_CHOWN
Description=Test for CapabilityBoundingSet
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_chown,cap_fowner,cap_kill"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_chown,cap_fowner,cap_kill"'
Type=oneshot
CapabilityBoundingSet=CAP_FOWNER
CapabilityBoundingSet=CAP_KILL CAP_CHOWN
Description=Test for CapabilityBoundingSet
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set ="'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set ="'
Type=oneshot
CapabilityBoundingSet=CAP_FOWNER CAP_KILL
CapabilityBoundingSet=
Description=Test for CapabilityBoundingSet
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_fowner,cap_kill"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_fowner,cap_kill"'
Type=oneshot
CapabilityBoundingSet=CAP_FOWNER CAP_KILL
ExecCondition=/bin/sh -c 'exit 255'
# This should not get run
-ExecStart=/bin/sh -c 'true'
+ExecStart=sh -c 'true'
ExecCondition=/bin/sh -c 'exit 255'
# This should not get run
-ExecStart=/bin/sh -c 'true'
+ExecStart=sh -c 'true'
Description=Test for CPUAffinity (simple)
[Service]
-ExecStart=/bin/sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
+ExecStart=sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
CPUAffinity=0
Description=Test for CPUAffinity (reset)
[Service]
-ExecStart=/bin/sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
+ExecStart=sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
CPUAffinity=0-1 3
CPUAffinity=
CPUAffinity=0
Description=Test for CPUAffinity (merge)
[Service]
-ExecStart=/bin/sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 7'
+ExecStart=sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 7'
CPUAffinity=0,1
CPUAffinity=1-2
[Service]
Type=oneshot
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
# Multiple ExecStart= lines causes the issue #9702.
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
DynamicUser=yes
User=adm
[Service]
Type=oneshot
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
# Multiple ExecStart= lines causes the issue #9702.
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
DynamicUser=yes
User=games
Description=Test DynamicUser with User= and SupplementaryGroups=
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
Type=oneshot
User=1
DynamicUser=yes
Description=Test DynamicUser with User=
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
Type=oneshot
User=1
DynamicUser=yes
Description=Test for RuntimeDirectory with RuntimeDirectoryPreserve=yes and DynamicUser=yes
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
-ExecStart=/bin/sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
+ExecStart=sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectorypreserve
RuntimeDirectoryPreserve=yes
Description=Test for RuntimeDirectory with RuntimeDirectoryPreserve=yes and DynamicUser=yes 2nd trial
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
-ExecStart=/bin/sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
-ExecStart=/bin/sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
+ExecStart=sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectorypreserve
RuntimeDirectoryPreserve=yes
Description=Test for RuntimeDirectory with DynamicUser=yes migrated from RuntimeDirectoryPreserve=yes
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
-ExecStart=/bin/sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
-ExecStart=/bin/sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
+ExecStart=sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectorypreserve
DynamicUser=yes
ExecStart=test -d %S/test-dynamicuser-migrate2/hoge
ExecStart=touch %S/test-dynamicuser-migrate/yay
ExecStart=touch %S/test-dynamicuser-migrate2/hoge/yayyay
-ExecStart=/bin/sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
+ExecStart=sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
Type=oneshot
DynamicUser=no
ExecStart=touch %S/test-dynamicuser-migrate2/hoge/yayyay
ExecStart=touch %S/private/test-dynamicuser-migrate/yay
ExecStart=touch %S/private/test-dynamicuser-migrate2/hoge/yayyay
-ExecStart=/bin/sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
+ExecStart=sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
Type=oneshot
DynamicUser=yes
Description=Test DynamicUser with SupplementaryGroups=
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
Type=oneshot
DynamicUser=yes
SupplementaryGroups=1 2
Description=Test for Environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset"'
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset"'
Type=oneshot
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Environment=
Description=Test for Environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = foobar'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = foobar'
Type=oneshot
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Environment="VAR3=foobar"
Description=Test for No Environment Variable Substitution
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2}" = "word3" && test "$${VAR3-unset}" = \'$word 5 6\''
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2}" = "word3" && test "$${VAR3-unset}" = \'$word 5 6\''
ExecStart=:/bin/sh -x -c 'test "$${VAR1-unset}" != "unset" && test "$${VAR2}" != "word3" && test "$${VAR3-unset}" != \'$word 5 6\''
Type=oneshot
Environment="VAR2=word3" "VAR3=$word 5 6"
Description=Test for Environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"'
Type=oneshot
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Description=Test for EnvironmentFile
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
Type=oneshot
EnvironmentFile=/tmp/test-exec_environmentfile.conf
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/sh -x -c 'test "$$PATH" = "/usr" && test "$$VAR1" = word3 && test "$$VAR2" = "\\$$word 5 6"'
+ExecStart=sh -x -c 'test "$$PATH" = "/usr" && test "$$VAR1" = word3 && test "$$VAR2" = "\\$$word 5 6"'
Type=oneshot
ExecSearchPath=/tmp:/bin
Environment="PATH=/usr" VAR1=word3 "VAR2=$word 5 6"
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$PATH" = "/tmp:/bin"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$PATH" = "/tmp:/bin"'
Type=oneshot
ExecSearchPath=/tmp:/bin
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile sets PATH
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = /usr'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = /usr'
Type=oneshot
EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile-set.conf
ExecSearchPath=/tmp:/bin
Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile does not set PATH
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
Type=oneshot
ExecSearchPath=/tmp:/bin
EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile.conf
Description=Test for PassEnvironment with ExecSearchPath with PATH set by user
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/usr"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/usr"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5 PATH
ExecSearchPath=/tmp:/bin
Description=Test for PassEnvironment with ExecSearchPath with PATH not set by user
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
ExecSearchPath=/tmp:/bin
[Service]
Type=oneshot
ExecSearchPath=/tmp:/bin:/usr/bin:%V
-ExecStart=/bin/sh -x -c 'test %V = /var/tmp && test "$$PATH" = "/tmp:/bin:/usr/bin:/var/tmp"'
+ExecStart=sh -x -c 'test %V = /var/tmp && test "$$PATH" = "/tmp:/bin:/usr/bin:/var/tmp"'
Description=Test for Group
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nfsnobody"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "nfsnobody"'
Type=oneshot
Group=nfsnobody
Description=Test for Group
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nobody"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "nobody"'
Type=oneshot
Group=nobody
Description=Test for Group
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nogroup"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "nogroup"'
Type=oneshot
Group=nogroup
Description=Test for Group (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "daemon"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "daemon"'
Type=oneshot
Group=daemon
Description=Test for IgnoreSIGPIPE=no
[Service]
-ExecStart=/bin/sh -x -c 'kill -PIPE 0'
+ExecStart=sh -x -c 'kill -PIPE 0'
Type=oneshot
IgnoreSIGPIPE=no
Description=Test for IgnoreSIGPIPE=yes
[Service]
-ExecStart=/bin/sh -x -c 'kill -PIPE 0'
+ExecStart=sh -x -c 'kill -PIPE 0'
Type=oneshot
IgnoreSIGPIPE=yes
[Service]
InaccessiblePaths=-/i-dont-exist
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
[Service]
InaccessiblePaths=/sys
-ExecStart=/bin/sh -x -c 'test "$$(stat -c %%a /sys)" = "0"'
+ExecStart=sh -x -c 'test "$$(stat -c %%a /sys)" = "0"'
Type=oneshot
Description=Test for IOSchedulingClass=best-effort
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "best-effort"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "best-effort"'
Type=oneshot
IOSchedulingClass=best-effort
Description=Test for IOSchedulingClass=idle
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "idle"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "idle"'
Type=oneshot
IOSchedulingClass=idle
[Service]
# Old kernels might report "none" here, new kernels "best-effort".
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "none" -o "$${c%%:*}" = "best-effort"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "none" -o "$${c%%:*}" = "best-effort"'
Type=oneshot
IOSchedulingClass=none
Description=Test for IOSchedulingClass=realtime
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "realtime"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "realtime"'
Type=oneshot
IOSchedulingClass=realtime
Description=Test for LoadCredential=
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
-ExecStartPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
-ExecStop=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
-ExecStopPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStart=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStartPost=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStop=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStopPost=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
Type=oneshot
LoadCredential=test-execute.load-credential
Description=Test for NetworkNamespacePath= without mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# Without mount namespacing, we can access the dummy-test-exec interface through sysfs.
-ExecStart=/bin/sh -x -c 'test -e /sys/class/net/dummy-test-exec'
-ExecStart=/bin/sh -x -c 'ip link show dummy-test-ns'
-ExecStart=/bin/sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
+ExecStart=sh -x -c 'test -e /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'ip link show dummy-test-ns'
+ExecStart=sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
# Without mount namespacing, we cannot access the dummy-test-ns interface through sysfs.
-ExecStart=/bin/sh -x -c 'test ! -e /sys/class/net/dummy-test-ns'
+ExecStart=sh -x -c 'test ! -e /sys/class/net/dummy-test-ns'
Type=oneshot
NetworkNamespacePath=/run/netns/test-execute-netns
PrivateMounts=no
Description=Test for NetworkNamespacePath= with mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# With mount namespacing, we cannot access the dummy-test-exec interface through sysfs.
-ExecStart=/bin/sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
-ExecStart=/bin/sh -x -c 'ip link show dummy-test-ns'
-ExecStart=/bin/sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
+ExecStart=sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'ip link show dummy-test-ns'
+ExecStart=sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
# With mount namespacing, we can access the dummy-test-ns interface through sysfs.
-ExecStart=/bin/sh -x -c 'test -e /sys/class/net/dummy-test-ns'
+ExecStart=sh -x -c 'test -e /sys/class/net/dummy-test-ns'
Type=oneshot
NetworkNamespacePath=/run/netns/test-execute-netns
# NetworkNamespacePath= implies PrivateMounts=yes
# This should work, as we explicitly disable the effect of NoExecPaths=
ExecStart=+/bin/sh -c '/bin/cat /dev/null'
# This should also work, as we do not disable the effect of NoExecPaths= but invert the exit code
-ExecStart=/bin/sh -x -c '! /bin/cat /dev/null'
+ExecStart=sh -x -c '! /bin/cat /dev/null'
NoExecPaths=/bin/cat
Description=Test for OOMScoreAdjust
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq -100'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq -100'
Type=oneshot
OOMScoreAdjust=-100
Description=Test for OOMScoreAdjust
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq 100'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq 100'
Type=oneshot
OOMScoreAdjust=100
Description=Test for PassEnvironment with variables absent from the execution environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
Description=Test for PassEnvironment and erasing the variable list
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
PassEnvironment=
Description=Test for PassEnvironment with a variable name repeated
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
Type=oneshot
PassEnvironment=VAR1 VAR2
PassEnvironment=VAR1 VAR3
Description=Test for PassEnvironment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
Description=Test for Personality=aarch64
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "aarch64")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "aarch64")'
Type=oneshot
Personality=aarch64
Description=Test for Personality=loongarch64
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "loongarch64")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "loongarch64")'
Type=oneshot
Personality=loongarch64
Description=Test for Personality=ppc64
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64")'
Type=oneshot
Personality=ppc64
Description=Test for Personality=ppc64le
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64le")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64le")'
Type=oneshot
Personality=ppc64le
Description=Test for Personality=s390
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "s390"'
+ExecStart=sh -x -c 'c=$$(uname -m); test "$$c" = "s390"'
Type=oneshot
Personality=s390
Description=Test for Personality=x86-64
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "x86_64"'
+ExecStart=sh -x -c 'c=$$(uname -m); test "$$c" = "x86_64"'
Type=oneshot
Personality=x86-64
Description=Test for Personality=x86
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "i686" -o "$$c" = "x86_64"'
+ExecStart=sh -x -c 'c=$$(uname -m); test "$$c" = "i686" -o "$$c" = "x86_64"'
Type=oneshot
Personality=x86
Description=Test for PrivateDevices=yes with a bind mounted device
[Service]
-ExecStart=/bin/sh -c 'test -c /dev/kmsg'
-ExecStart=/bin/sh -c 'test ! -w /dev/'
+ExecStart=sh -c 'test -c /dev/kmsg'
+ExecStart=sh -c 'test ! -w /dev/'
Type=oneshot
PrivateDevices=yes
BindPaths=/dev/kmsg
Description=Test for PrivateDevices=yes with prefix
[Service]
-ExecStart=/bin/sh -x -c '! test -c /dev/kmsg'
+ExecStart=sh -x -c '! test -c /dev/kmsg'
ExecStart=+/bin/sh -x -c 'test -c /dev/kmsg'
Type=oneshot
PrivateDevices=yes
[Service]
PrivateDevices=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
Type=oneshot
[Service]
PrivateDevices=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
Type=oneshot
Description=Test for PrivateDevices=no
[Service]
-ExecStart=/bin/sh -x -c 'test -c /dev/kmsg'
+ExecStart=sh -x -c 'test -c /dev/kmsg'
Type=oneshot
PrivateDevices=no
[Service]
PrivateDevices=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
Type=oneshot
[Service]
PrivateDevices=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
Type=oneshot
Type=oneshot
# Check the group applied
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "daemon"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "daemon"'
# Check that the namespace applied
-ExecStart=/bin/sh -c 'test ! -c /dev/kmsg'
+ExecStart=sh -c 'test ! -c /dev/kmsg'
# Check that the owning group of a node is not daemon (should be the host root)
-ExecStart=/bin/sh -x -c 'test ! "$$(stat -c %%G /dev/stderr)" = "daemon"'
+ExecStart=sh -x -c 'test ! "$$(stat -c %%G /dev/stderr)" = "daemon"'
Description=Test for PrivateDevices=yes
[Service]
-ExecStart=/bin/sh -c 'test ! -c /dev/kmsg'
+ExecStart=sh -c 'test ! -c /dev/kmsg'
Type=oneshot
PrivateDevices=yes
Description=Test for PrivateNetwork= without mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# Without mount namespacing, we can access the dummy-test-exec interface through sysfs
-ExecStart=/bin/sh -x -c 'test -d /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'test -d /sys/class/net/dummy-test-exec'
Type=oneshot
PrivateNetwork=yes
PrivateMounts=no
Description=Test for PrivateNetwork= with mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# With mount namespacing, we cannot access the dummy-test-exec interface through sysfs.
-ExecStart=/bin/sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
Type=oneshot
PrivateNetwork=yes
# PrivateNetwork=yes implies PrivateMounts=yes
Description=Test for PrivateTmp=yes with prefix
[Service]
-ExecStart=/bin/sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
ExecStart=+/bin/sh -x -c 'test -f /tmp/test-exec_privatetmp'
Type=oneshot
PrivateTmp=yes
Description=Test for PrivateTmp=no
[Service]
-ExecStart=/bin/sh -x -c 'test -f /tmp/test-exec_privatetmp'
+ExecStart=sh -x -c 'test -f /tmp/test-exec_privatetmp'
Type=oneshot
PrivateTmp=no
Description=Test for PrivateTmp=yes
[Service]
-ExecStart=/bin/sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
Type=oneshot
PrivateTmp=yes
ProtectHome=tmpfs
ProtectSystem=strict
Type=oneshot
-ExecStart=/bin/sh -x -c 'test "$$(stat -fc %%T /home)" = "tmpfs"'
+ExecStart=sh -x -c 'test "$$(stat -fc %%T /home)" = "tmpfs"'
[Service]
ProtectKernelLogs=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
Type=oneshot
[Service]
ProtectKernelLogs=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
Type=oneshot
[Service]
ProtectKernelModules=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
Type=oneshot
[Service]
ProtectKernelModules=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
Type=oneshot
[Service]
ProtectKernelModules=yes
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
[Service]
ReadOnlyPaths=-/i-dont-exist
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
# This should work, as we explicitly disable the effect of ReadOnlyPaths=
ExecStart=+/bin/sh -c 'touch /tmp/thisisasimpletest'
# This should also work, as we do not disable the effect of ReadOnlyPaths= but invert the exit code
-ExecStart=/bin/sh -x -c '! touch /tmp/thisisasimpletest'
+ExecStart=sh -x -c '! touch /tmp/thisisasimpletest'
ExecStart=+/bin/sh -c 'rm /tmp/thisisasimpletest'
ReadOnlyPaths=/tmp
[Service]
ReadOnlyPaths=/etc -/i-dont-exist /usr
BindPaths=/etc:/tmp/etc2
-ExecStart=/bin/sh -x -c 'test ! -w /etc && test ! -w /usr && test ! -e /i-dont-exist && test -w /var'
+ExecStart=sh -x -c 'test ! -w /etc && test ! -w /usr && test ! -e /i-dont-exist && test -w /var'
Type=oneshot
[Service]
ReadOnlyPaths=/usr /etc /sys /dev -/i-dont-exist
PrivateDevices=yes
-ExecStart=/bin/sh -x -c 'test ! -w /usr && test ! -w /etc && test ! -w /sys && test ! -w /sys/fs/cgroup'
-ExecStart=/bin/sh -x -c 'test ! -w /dev && test ! -w /dev/shm && test ! -e /i-dont-exist && test -w /var'
+ExecStart=sh -x -c 'test ! -w /usr && test ! -w /etc && test ! -w /sys && test ! -w /sys/fs/cgroup'
+ExecStart=sh -x -c 'test ! -w /dev && test ! -w /dev/shm && test ! -e /i-dont-exist && test -w /var'
Type=oneshot
[Service]
ReadWritePaths=-/i-dont-exist
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
Description=Test for RuntimeDirectoryMode
[Service]
-ExecStart=/bin/sh -x -c 'mode=$$(stat -c %%a %t/test-exec_runtimedirectory-mode); test "$$mode" = "750"'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory-mode"'
+ExecStart=sh -x -c 'mode=$$(stat -c %%a %t/test-exec_runtimedirectory-mode); test "$$mode" = "750"'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory-mode"'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectory-mode
RuntimeDirectoryMode=0750
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nfsnobody"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nfsnobody"'
Type=oneshot
Group=nfsnobody
User=root
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nobody"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nobody"'
Type=oneshot
Group=nobody
User=root
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nogroup"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nogroup"'
Type=oneshot
Group=nogroup
User=root
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner-daemon); test "$$group" = "daemon"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner-daemon); test "$$group" = "daemon"'
Type=oneshot
Group=daemon
User=root
Description=Test for RuntimeDirectory
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectory'
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectory2/hogehoge'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory:%t/test-exec_runtimedirectory2/hogehoge"'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectory'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectory2/hogehoge'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory:%t/test-exec_runtimedirectory2/hogehoge"'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectory
RuntimeDirectory=./test-exec_runtimedirectory2///./hogehoge/.
Description=Test for SetCredential=
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
-ExecStartPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
-ExecStop=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
-ExecStopPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStart=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStartPost=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStop=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStopPost=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
Type=oneshot
SetCredential=test-execute.set-credential:hoge
[Service]
Type=oneshot
-ExecStart=/bin/bash -x -c "[[ %%U == ?U ]]"
+ExecStart=bash -x -c "[[ %%U == ?U ]]"
Description=Test for StandardInputText= and StandardInputData=
[Service]
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); echo -e "this is a test\nand this is more\nsomething encoded!\nsomething in multiple lines\nand some more\nand a more bas64 data\nsomething with strange\nembedded\tcharacters\nand something with a exec-stdin-data.service specifier" >$d/text ; cmp $d/text ; rm -rf $d'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); echo -e "this is a test\nand this is more\nsomething encoded!\nsomething in multiple lines\nand some more\nand a more bas64 data\nsomething with strange\nembedded\tcharacters\nand something with a exec-stdin-data.service specifier" >$d/text ; cmp $d/text ; rm -rf $d'
Type=oneshot
StandardInput=data
StandardInputText=this is a test
Description=Test for Supplementary Group with multiple groups without Group and User
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "%G" && test "$$(id -u)" = "%U"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "%G" && test "$$(id -u)" = "%U"'
Type=oneshot
SupplementaryGroups=1 2
Description=Test for Supplementary Group with multiple groups and Group=1
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "%U"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "%U"'
Type=oneshot
Group=1
SupplementaryGroups=1 2
Description=Test for Supplementary Group with multiple groups and Uid=1
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
Type=oneshot
User=1
SupplementaryGroups=1 2
Description=Test for Supplementary Group with only one group and uid 1
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
Type=oneshot
User=1
Group=1
Description=Test for Supplementary Group with only one group
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "0"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "0"'
Type=oneshot
Group=1
SupplementaryGroups=1
Description=Test for Supplementary Group
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
Type=oneshot
SupplementaryGroups=1
Description=Test for SystemCallErrorNumber
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname
SystemCallErrorNumber=EACCES
Description=Test for SystemCallErrorNumber
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname
SystemCallErrorNumber=255
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c '/bin/echo "This should not be seen"'
+ExecStart=sh -c '/bin/echo "This should not be seen"'
Type=oneshot
LimitCORE=0
SystemCallFilter=ioperm
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c '/bin/echo "This should not be seen"'
+ExecStart=sh -c '/bin/echo "This should not be seen"'
Type=oneshot
LimitCORE=0
SystemCallFilter=~write open execve fexecve execveat exit_group close mmap munmap fstat DONOTEXIST
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c '/bin/echo "This should not be seen"'
+ExecStart=sh -c '/bin/echo "This should not be seen"'
Type=oneshot
LimitCORE=0
SystemCallArchitectures=native
Description=Test bounding set is right with SystemCallFilter and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_net_bind_service"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_net_bind_service"'
Type=oneshot
User=1
SystemCallFilter=@system-service
Description=Test bounding set is right with SystemCallFilter and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_setpcap,cap_net_bind_service,cap_sys_admin"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_setpcap,cap_net_bind_service,cap_sys_admin"'
Type=oneshot
User=1
SystemCallFilter=@system-service
Description=Test no_new_privs is unset for ProtectClock and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
Type=oneshot
User=1
ProtectClock=yes
Description=Test no_new_privs is unset for SystemCallFilter and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
Type=oneshot
User=1
SystemCallFilter=@system-service
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
SystemCallFilter=~read write open execve ioperm
SystemCallFilter=ioctl
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
SystemCallFilter=
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
SystemCallArchitectures=native
SystemCallFilter=
Description=Test for SystemCallFilter with specific kill action overriding default errno action
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:kill
SystemCallErrorNumber=EILSEQ
Description=Test for SystemCallFilter with specific errno action overriding default kill action
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:EILSEQ
SystemCallErrorNumber=kill
Description=Test for SystemCallFilter in system mode with User set
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
User=nfsnobody
SystemCallFilter=~read write open execve ioperm
Description=Test for SystemCallFilter in system mode with User set
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
User=nobody
SystemCallFilter=~read write open execve ioperm
Description=Test for SystemCallFilter in system mode with User set (daemon)
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
User=daemon
SystemCallFilter=~read write open execve ioperm
Description=Test for SystemCallFilter with errno name (for issue #18916)
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=@system-service
SystemCallFilter=~uname:EILSEQ
# test for issue #9939 which is fixed by a5404992cc7724ebf7572a0aa89d9fdb26ce0b62 (#9942)
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:ENOENT uname:EILSEQ
SystemCallErrorNumber=EACCES
Description=Test for SystemCallFilter with errno name
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:EILSEQ
SystemCallErrorNumber=EACCES
Description=Test for SystemCallFilter with errno number
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:255
SystemCallErrorNumber=EACCES
TemporaryFileSystem=/var:ro,mode=0700,nostrictatime
# Check /proc/self/mountinfo
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$11 !~ /(^|,)mode=700(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$11 !~ /(^|,)mode=700(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)ro(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)nodev(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 ~ /(^|,)strictatime(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)ro(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)nodev(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 ~ /(^|,)strictatime(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
Type=oneshot
# Check directories exist
-ExecStart=/bin/sh -c 'test -d /var/test-exec-temporaryfilesystem/rw && test -d /var/test-exec-temporaryfilesystem/ro'
+ExecStart=sh -c 'test -d /var/test-exec-temporaryfilesystem/rw && test -d /var/test-exec-temporaryfilesystem/ro'
# Check TemporaryFileSystem= are empty
-ExecStart=/bin/sh -c 'for i in $$(ls -A /var); do test $$i = test-exec-temporaryfilesystem || false; done'
+ExecStart=sh -c 'for i in $$(ls -A /var); do test $$i = test-exec-temporaryfilesystem || false; done'
# Check default mode
ExecStart=sh -x -c 'test "$$(stat -c %%a /var)" = "755"'
# Cannot create a file in /var
-ExecStart=/bin/sh -c '! touch /var/hoge'
+ExecStart=sh -c '! touch /var/hoge'
# Create a file in /var/test-exec-temporaryfilesystem/rw
-ExecStart=/bin/sh -c 'touch /var/test-exec-temporaryfilesystem/rw/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'touch /var/test-exec-temporaryfilesystem/rw/thisisasimpletest-temporaryfilesystem'
# Then, the file can be access through /tmp
-ExecStart=/bin/sh -c 'test -f /tmp/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'test -f /tmp/thisisasimpletest-temporaryfilesystem'
# Also, through /var/test-exec-temporaryfilesystem/ro
-ExecStart=/bin/sh -c 'test -f /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'test -f /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
# The file cannot modify through /var/test-exec-temporaryfilesystem/ro
-ExecStart=/bin/sh -c '! touch /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c '! touch /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
# Cleanup
-ExecStart=/bin/sh -c 'rm /tmp/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'rm /tmp/thisisasimpletest-temporaryfilesystem'
TemporaryFileSystem=/var:ro
BindPaths=/tmp:/var/test-exec-temporaryfilesystem/rw
Type=oneshot
# Check TemporaryFileSystem= are empty
-ExecStart=/bin/sh -c 'for i in $$(ls -A /usr); do test $$i = lib -o $$i = lib64 -o $$i = bin -o $$i = sbin || false; done'
+ExecStart=sh -c 'for i in $$(ls -A /usr); do test $$i = lib -o $$i = lib64 -o $$i = bin -o $$i = sbin || false; done'
# Cannot create files under /usr
-ExecStart=/bin/sh -c '! touch /usr/hoge'
-ExecStart=/bin/sh -c '! touch /usr/bin/hoge'
+ExecStart=sh -c '! touch /usr/hoge'
+ExecStart=sh -c '! touch /usr/bin/hoge'
TemporaryFileSystem=/usr:ro
BindReadOnlyPaths=-/usr/lib -/usr/lib64 /usr/bin /usr/sbin
Description=Test for UMask
[Service]
-ExecStart=/bin/sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "600"'
+ExecStart=sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "600"'
Type=oneshot
UMask=0177
PrivateTmp=yes
Description=Test for UMask default
[Service]
-ExecStart=/bin/sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "644"'
+ExecStart=sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "644"'
Type=oneshot
PrivateTmp=yes
Description=Test for UMask= + namespacing
[Service]
-ExecStart=/bin/ls -lahd /tmp/subdir
+ExecStart=ls -lahd /tmp/subdir
Type=oneshot
User=65534
Group=65534
Description=Test for UnsetEnvironment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$FOO" = "bar" && test "$${QUUX-X}" = "X" && test "$$VAR3" = "value3" && test "$${VAR4-X}" = "X" && test "$$VAR5" = "value5" && test "$${X%b-X}" = "X"'
+ExecStart=sh -x -c 'test "$$FOO" = "bar" && test "$${QUUX-X}" = "X" && test "$$VAR3" = "value3" && test "$${VAR4-X}" = "X" && test "$$VAR5" = "value5" && test "$${X%b-X}" = "X"'
Type=oneshot
Environment=FOO=bar QUUX=waldo VAR3=value3 VAR4=value4 VAR5=value5 X%b=%U
UnsetEnvironment=QUUX=waldo VAR3=somethingelse VAR4 X%b=%U
Description=Test for User
[Service]
-ExecStart=/bin/sh -x -c 'test "$$USER" = "nfsnobody"'
+ExecStart=sh -x -c 'test "$$USER" = "nfsnobody"'
Type=oneshot
User=nfsnobody
Description=Test for User
[Service]
-ExecStart=/bin/sh -x -c 'test "$$USER" = "nobody"'
+ExecStart=sh -x -c 'test "$$USER" = "nobody"'
Type=oneshot
User=nobody
Description=Test for User (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'test "$$USER" = "daemon"'
+ExecStart=sh -x -c 'test "$$USER" = "daemon"'
Type=oneshot
User=daemon
Description=Test for WorkingDirectory with trailing dot
[Service]
-ExecStart=/bin/sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
+ExecStart=sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
Type=oneshot
WorkingDirectory=/tmp///./test-exec_workingdirectory/.
Description=Test for WorkingDirectory
[Service]
-ExecStart=/bin/sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
+ExecStart=sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
Type=oneshot
WorkingDirectory=/tmp/test-exec_workingdirectory
[[ -z "$ASAN_RT_PATH" ]] && dfatal "ASAN_RT_PATH is empty, but it shouldn't be"
- default_asan_options="${ASAN_OPTIONS:-strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1}"
+ default_asan_options="${ASAN_OPTIONS:-strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:disable_coredump=0:use_madv_dontdump=1}"
default_ubsan_options="${UBSAN_OPTIONS:-print_stacktrace=1:print_summary=1:halt_on_error=1}"
if [[ "$ASAN_COMPILER" == "clang" ]]; then
image_install kpartx /lib/udev/kpartx_id lsmod mpathpersist multipath multipathd partx
image_install "${ROOTLIBDIR:?}"/system/multipathd.{service,socket}
if get_bool "$LOOKS_LIKE_DEBIAN"; then
- inst_rules 56-dm-parts.rules 56-dm-mpath.rules 60-multipath.rules 68-del-part-nodes.rules 95-kpartx.rules
+ # Note: try both 60-kpartx.rules (as seen on Debian Sid with 0.9.4-7) and 95-kpartx.rules (as seen on
+ # Ubuntu Jammy with 0.8.8-1ubuntu1.22.04.4)
+ inst_rules 56-dm-parts.rules 56-dm-mpath.rules 60-kpartx.rules 60-multipath.rules 68-del-part-nodes.rules 95-kpartx.rules
else
inst_rules 11-dm-mpath.rules 11-dm-parts.rules 62-multipath.rules 66-kpartx.rules 68-del-part-nodes.rules
fi
# SPDX-License-Identifier: LGPL-2.1-or-later
-[Match]
-Name=dummy98
-
-[Network]
-IPv6AcceptRA=no
-
[Neighbor]
Address=192.168.10.1
LinkLayerAddress=00:00:5e:00:02:65
# SPDX-License-Identifier: LGPL-2.1-or-later
-
[Neighbor]
Address=192.168.10.1
LinkLayerAddress=00:00:5e:00:03:65
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Neighbor]
+Address=192.168.10.1
+LinkLayerAddress=00:00:5e:00:03:66
IPv6AcceptRA=no
Address=2001:1234:5:8f63::1/120
Address=192.168.5.10/24
-Gateway=192.168.5.1
[NextHop]
Id=1
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=veth99
+
+[Network]
+IPv6AcceptRA=no
+Address=2001:1234:5:8f63::1/120
+Address=192.168.5.10/24
+
+# Commented out lines are specified in 25-nexthop.network
+
+[NextHop]
+#Id=1
+Id=6
+Gateway=192.168.5.1
+
+[NextHop]
+#Id=2
+Id=7
+Gateway=2001:1234:5:8f63::2
+
+[NextHop]
+#Id=3
+Id=4
+Family=ipv6
+
+[NextHop]
+#Id=4
+Id=3
+Family=ipv4
+
+[NextHop]
+Id=5
+#Gateway=192.168.10.1
+#OnLink=yes
+Gateway=192.168.5.3
+OnLink=no
+
+[NextHop]
+#Id=6
+Id=1
+Family=ipv4
+Blackhole=yes
+
+[NextHop]
+#Id=7
+Id=2
+Family=ipv6
+Blackhole=yes
+
+[NextHop]
+Id=8
+Gateway=fe80::222:4dff:ff:ff:ff:ff
+
+[NextHop]
+Gateway=192.168.5.2
+
+[NextHop]
+Family=ipv4
+Blackhole=yes
+
+[NextHop]
+Family=ipv6
+Blackhole=yes
+
+[Route]
+#NextHop=1
+NextHop=6
+Destination=10.10.10.10
+
+[Route]
+#NextHop=2
+NextHop=7
+Destination=10.10.10.11
+
+[Route]
+#NextHop=2
+NextHop=7
+Destination=2001:1234:5:8f62::1
+
+[Route]
+NextHop=5
+Destination=10.10.10.12
+
+[Route]
+#NextHop=6
+NextHop=1
+Destination=10.10.10.13
+
+[Route]
+#NextHop=7
+NextHop=2
+Destination=2001:1234:5:8f62::2
+
+[Route]
+#NextHop=21
+NextHop=20
+Destination=10.10.10.14
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=dummy98
+
+[Network]
+Address=192.168.20.20/24
+IPv6AcceptRA=no
+
+# Commented out lines are specified in 25-nexthop-dummy.network
+
+[NextHop]
+#Id=20
+Id=21
+Gateway=192.168.20.1
+
+[NextHop]
+#Id=21
+#Group=1:3 20:1
+Id=20
+Group=5:3 21:1
--- /dev/null
+192.168.27.3:51820
--- /dev/null
+EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
Kind=wireguard
[WireGuard]
-PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
+#PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
ListenPort=51821
FwMark=1235
--- /dev/null
+6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=
[WireGuardPeer]
PublicKey=RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=
AllowedIPs=fd31:bf08:57cb::/48,192.168.26.3/24
-#Endpoint=wireguard.example.com:51820
-Endpoint=192.168.27.3:51820
+#Endpoint=192.168.27.3:51820
+Endpoint=@network.wireguard.peer0.endpoint
PresharedKey=IIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=
PersistentKeepalive=20
RouteTable=1234
[WireGuardPeer]
PublicKey=9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=
-PresharedKey=6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=
+PresharedKey=@network.wireguard.peer2.psk
AllowedIPs=192.168.124.3
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=bridge99
+
+[Network]
+IPv6AcceptRA=false
[Network]
IPv6AcceptRA=no
-
-[Neighbor]
-Address=192.168.10.1
-LinkLayerAddress=00:00:5e:00:02:66
+Bridge=bridge99
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+OriginalName=bridge99
+
+[Link]
+MACAddressPolicy=none
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=bridge99
+Kind=bridge
+MACAddress=none
networkd_conf_dropin_dir = '/run/systemd/networkd.conf.d'
networkd_ci_temp_dir = '/run/networkd-ci'
udev_rules_dir = '/run/udev/rules.d'
+credstore_dir = '/run/credstore'
dnsmasq_pid_file = '/run/networkd-ci/test-dnsmasq.pid'
dnsmasq_log_file = '/run/networkd-ci/test-dnsmasq.log'
if has_link:
udev_reload()
+def copy_credential(src, target):
+ mkdir_p(credstore_dir)
+ cp(os.path.join(networkd_ci_temp_dir, src),
+ os.path.join(credstore_dir, target))
+
def remove_network_unit(*units):
"""
Remove previously copied unit files from the testbed.
invocation_id = networkd_invocation_id()
command = [
'journalctl',
+ '--no-hostname',
+ '--output=short-monotonic',
f'_SYSTEMD_INVOCATION_ID={invocation_id}',
]
if since:
command.append(f'--since={since}')
+ check_output('journalctl --sync')
return check_output(*command)
+def networkd_is_failed():
+ return call_quiet('systemctl is-failed -q systemd-networkd.service') != 1
+
def stop_networkd(show_logs=True):
if show_logs:
invocation_id = networkd_invocation_id()
if show_logs:
print(read_networkd_log(invocation_id))
# Check if networkd exits cleanly.
- assert call_quiet('systemctl is-failed -q systemd-networkd.service') == 1
+ assert not networkd_is_failed()
def start_networkd():
check_output('systemctl start systemd-networkd')
def networkd_pid():
return int(check_output('systemctl show --value -p MainPID systemd-networkd.service'))
+def networkctl(*args):
+ # Do not call networkctl if networkd is in failed state.
+ # Otherwise, networkd may be restarted and we may get wrong results.
+ assert not networkd_is_failed()
+ return check_output(*(networkctl_cmd + list(args)), env=env)
+
+def networkctl_status(*args):
+ return networkctl('-n', '0', 'status', *args)
+
+def networkctl_json(*args):
+ return networkctl('--json=short', 'status', *args)
+
def networkctl_reconfigure(*links):
- check_output(*networkctl_cmd, 'reconfigure', *links, env=env)
+ networkctl('reconfigure', *links)
def networkctl_reload(sleep_time=1):
- check_output(*networkctl_cmd, 'reload', env=env)
+ networkctl('reload')
# 'networkctl reload' asynchronously reconfigure links.
# Hence, we need to wait for a short time for link to be in configuring state.
if sleep_time > 0:
time.sleep(sleep_time)
+def resolvectl(*args):
+ return check_output(*(resolvectl_cmd + list(args)), env=env)
+
+def timedatectl(*args):
+ return check_output(*(timedatectl_cmd + list(args)), env=env)
+
def setup_common():
print()
def wait_activated(self, link, state='down', timeout=20, fail_assert=True):
# wait for the interface is activated.
- invocation_id = check_output('systemctl show systemd-networkd -p InvocationID --value')
needle = f'{link}: Bringing link {state}'
flag = state.upper()
for iteration in range(timeout + 1):
time.sleep(1)
if not link_exists(link):
continue
- output = check_output('journalctl _SYSTEMD_INVOCATION_ID=' + invocation_id)
+ output = read_networkd_log()
if needle in output and flag in check_output(f'ip link show {link}'):
return True
if fail_assert:
time.sleep(1)
if not link_exists(link):
continue
- output = check_output(*networkctl_cmd, '-n', '0', 'status', link, env=env)
+ output = networkctl_status(link)
if re.search(rf'(?m)^\s*State:\s+{operstate}\s+\({setup_state}\)\s*$', output):
return True
try:
check_output(*args, env=wait_online_env)
except subprocess.CalledProcessError:
- # show detailed status on failure
- for link in links_with_operstate:
- name = link.split(':')[0]
- if link_exists(name):
- call(*networkctl_cmd, '-n', '0', 'status', name, env=env)
+ if networkd_is_failed():
+ print('!!!!! systemd-networkd.service is failed !!!!!')
+ call('systemctl status systemd-networkd.service')
+ else:
+ # show detailed status on failure
+ for link in links_with_operstate:
+ name = link.split(':')[0]
+ if link_exists(name):
+ networkctl_status(name)
raise
if not bool_any and setup_state:
for link in links_with_operstate:
start_networkd()
self.wait_online(['dummy98:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
self.assertRegex(output, 'hogehogehogehogehogehoge')
@expectedFailureIfAlternativeNameIsNotAvailable()
start_networkd()
self.wait_online(['dummyalt:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummyalt', env=env)
+ output = networkctl_status('dummyalt')
self.assertIn('hogehogehogehogehogehoge', output)
self.assertNotIn('dummy98', output)
def test_renew(self):
def check():
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.3', output)
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
start_networkd()
check()
- output = check_output(*networkctl_cmd, '--lines=0', '--stats', '--all', '--full', '--json=short', 'status')
- check_json(output)
+ check_json(networkctl_json('--lines=0', '--stats', '--all', '--full'))
for verb in ['renew', 'forcerenew']:
- call_check(*networkctl_cmd, verb, 'veth99')
+ networkctl(verb, 'veth99')
check()
- call_check(*networkctl_cmd, verb, 'veth99', 'veth99', 'veth99')
+ networkctl(verb, 'veth99', 'veth99', 'veth99')
check()
def test_up_down(self):
start_networkd()
self.wait_online(['dummy98:routable'])
- call_check(*networkctl_cmd, 'down', 'dummy98')
+ networkctl('down', 'dummy98')
self.wait_online(['dummy98:off'])
- call_check(*networkctl_cmd, 'up', 'dummy98')
+ networkctl('up', 'dummy98')
self.wait_online(['dummy98:routable'])
- call_check(*networkctl_cmd, 'down', 'dummy98', 'dummy98', 'dummy98')
+ networkctl('down', 'dummy98', 'dummy98', 'dummy98')
self.wait_online(['dummy98:off'])
- call_check(*networkctl_cmd, 'up', 'dummy98', 'dummy98', 'dummy98')
+ networkctl('up', 'dummy98', 'dummy98', 'dummy98')
self.wait_online(['dummy98:routable'])
def test_reload(self):
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, 'list', env=env)
+ output = networkctl('list')
self.assertRegex(output, '1 lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, 'list', 'test1', env=env)
+ output = networkctl('list', 'test1')
self.assertNotRegex(output, '1 lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, 'list', 'te*', env=env)
+ output = networkctl('list', 'te*')
self.assertNotRegex(output, '1 lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'te*', env=env)
+ output = networkctl_status('te*')
self.assertNotRegex(output, '1: lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'tes[a-z][0-9]', env=env)
+ output = networkctl_status('tes[a-z][0-9]')
self.assertNotRegex(output, '1: lo ')
self.assertRegex(output, 'test1')
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
self.assertRegex(output, 'MTU: 1600')
def test_type(self):
start_networkd()
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
self.assertRegex(output, 'Type: ether')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'lo', env=env)
+ output = networkctl_status('lo')
print(output)
self.assertRegex(output, 'Type: loopback')
start_networkd()
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
self.assertIn('Link File: /run/systemd/network/11-test-unit-file.link', output)
self.assertIn('/run/systemd/network/11-test-unit-file.link.d/dropin.conf', output)
# In that case, the udev DB for the loopback network interface may already have ID_NET_LINK_FILE property.
# Let's reprocess the interface and drop the property.
check_output(*udevadm_cmd, 'trigger', '--settle', '--action=add', '/sys/class/net/lo')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'lo', env=env)
+ output = networkctl_status('lo')
print(output)
self.assertIn('Link File: n/a', output)
self.assertIn('Network File: n/a', output)
self.wait_online(['test1:degraded', 'veth99:degraded', 'veth-peer:degraded'])
- check_output(*networkctl_cmd, 'delete', 'test1', 'veth99', env=env)
+ networkctl('delete', 'test1', 'veth99')
self.check_link_exists('test1', expected=False)
self.check_link_exists('veth99', expected=False)
self.check_link_exists('veth-peer', expected=False)
def test_label(self):
- call_check(*networkctl_cmd, 'label')
+ networkctl('label')
class NetworkdMatchTests(unittest.TestCase, Utilities):
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-mac-01.network', output)
output = check_output('ip -4 address show dev dummy98')
self.assertIn('10.0.0.1/16', output)
self.wait_address('dummy98', '10.0.0.2/16', ipv='-4', timeout_sec=10)
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-mac-02.network', output)
check_output('ip link set dev dummy98 down')
self.wait_address('dummy98-1', '10.0.1.2/16', ipv='-4', timeout_sec=10)
self.wait_online(['dummy98-1:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98-1', env=env)
+ output = networkctl_status('dummy98-1')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-renamed.network', output)
check_output('ip link set dev dummy98-1 down')
self.wait_address('dummy98-2', '10.0.2.2/16', ipv='-4', timeout_sec=10)
self.wait_online(['dummy98-2:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98-2', env=env)
+ output = networkctl_status('dummy98-2')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-altname.network', output)
def test_match_udev_property(self):
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
self.assertRegex(output, 'Network File: /run/systemd/network/14-match-udev-property')
self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state')))
self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version')))
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bridge99', env=env)
+ output = networkctl_status('bridge99')
print(output)
self.assertRegex(output, 'Priority: 9')
self.assertRegex(output, 'STP: yes')
self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5')
self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bond99', env=env)
+ output = networkctl_status('bond99')
print(output)
self.assertIn('Mode: 802.3ad', output)
self.assertIn('Miimon: 1s', output)
self.assertIn('Updelay: 2s', output)
self.assertIn('Downdelay: 2s', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bond98', env=env)
+ output = networkctl_status('bond98')
print(output)
self.assertIn('Mode: balance-tlb', output)
@expectedFailureIfModuleIsNotAvailable('wireguard')
def test_wireguard(self):
+ copy_credential('25-wireguard-endpoint-peer0-cred.txt', 'network.wireguard.peer0.endpoint')
+ copy_credential('25-wireguard-preshared-key-peer2-cred.txt', 'network.wireguard.peer2.psk')
+ copy_credential('25-wireguard-no-peer-private-key-cred.txt', 'network.wireguard.private.25-wireguard-no-peer')
+
copy_network_unit('25-wireguard.netdev', '25-wireguard.network',
'25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network',
'25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt',
self.assertIn('00:11:22:33:44:66 dst 10.0.0.6 self permanent', output)
self.assertIn('00:11:22:33:44:77 dst 10.0.0.7 via test1 self permanent', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'vxlan99', env=env)
+ output = networkctl_status('vxlan99')
print(output)
self.assertIn('VNI: 999', output)
self.assertIn('Destination Port: 5555', output)
# netlabel
self.check_netlabel('dummy98', r'10\.10\.1\.0/24')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def test_address_static(self):
copy_network_unit('25-address-static.network', '12-dummy.netdev', copy_dropins=False)
check_output(f'ip link set dev test1 carrier {carrier}')
self.wait_online([f'test1:{routable_map[carrier]}:{routable_map[carrier]}'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
self.assertRegex(output, '192.168.0.15')
self.assertRegex(output, '192.168.0.1')
check_output(f'ip link set dev test1 carrier {carrier}')
self.wait_online([f'test1:{routable_map[carrier]}:{routable_map[carrier]}'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
if have_config:
self.assertRegex(output, '192.168.0.15')
self.assertRegex(output, 'iif test1')
self.assertRegex(output, 'lookup 10')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def test_routing_policy_rule_issue_11280(self):
copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev',
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
print('### ip -6 route show dev dummy98')
self.assertIn('via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98', output)
self.assertIn('via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-address-static.network', copy_dropins=False)
networkctl_reload()
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
print('### ip -6 route show dev dummy98')
for i in range(1, 5):
self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy')
- def test_neighbor_section(self):
- copy_network_unit('25-neighbor-section.network', '12-dummy.netdev', copy_dropins=False)
+ def test_neighbor(self):
+ copy_network_unit('12-dummy.netdev', '25-neighbor-dummy.network', '25-neighbor-dummy.network.d/10-step1.conf',
+ '25-gre-tunnel-remote-any.netdev', '25-neighbor-ip.network',
+ '25-ip6gre-tunnel-remote-any.netdev', '25-neighbor-ipv6.network',
+ copy_dropins=False)
start_networkd()
- self.wait_online(['dummy98:degraded'])
+ self.wait_online(['dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable'])
+
+ print('### ip neigh list dev gretun97')
+ output = check_output('ip neigh list dev gretun97')
+ print(output)
+ self.assertIn('10.0.0.22 lladdr 10.65.223.239 PERMANENT', output)
+ self.assertNotIn('10.0.0.23', output)
+
+ print('### ip neigh list dev ip6gretun97')
+ output = check_output('ip neigh list dev ip6gretun97')
+ print(output)
+ self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT')
+ self.assertNotIn('2001:db8:0:f102::18', output)
print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
self.assertNotIn('192.168.10.2', output)
self.assertNotIn('00:00:5e:00:02:67', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
- copy_network_unit('25-neighbor-section.network.d/override.conf')
+ # Here, 10-step1.conf is intendedly kept, to verify that 10-step2.conf overrides
+ # the valid configurations in 10-step1.conf.
+ copy_network_unit('25-neighbor-dummy.network.d/10-step2.conf')
networkctl_reload()
self.wait_online(['dummy98:degraded'])
- print('### ip neigh list dev dummy98 (after reloading)')
+ print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
print(output)
self.assertIn('192.168.10.1 lladdr 00:00:5e:00:03:65 PERMANENT', output)
self.assertIn('2004:da8:1::1 lladdr 00:00:5e:00:03:66 PERMANENT', output)
self.assertNotIn('2004:da8:1:0::2', output)
self.assertNotIn('192.168.10.2', output)
- self.assertNotIn('00:00:5e:00:02', output)
+ self.assertNotIn('00:00:5e:00:02:67', output)
- def test_neighbor_reconfigure(self):
- copy_network_unit('25-neighbor-section.network', '12-dummy.netdev', copy_dropins=False)
- start_networkd()
- self.wait_online(['dummy98:degraded'])
+ check_json(networkctl_json())
- print('### ip neigh list dev dummy98')
- output = check_output('ip neigh list dev dummy98')
- print(output)
- self.assertIn('192.168.10.1 lladdr 00:00:5e:00:02:65 PERMANENT', output)
- self.assertIn('2004:da8:1::1 lladdr 00:00:5e:00:02:66 PERMANENT', output)
-
- remove_network_unit('25-neighbor-section.network')
- copy_network_unit('25-neighbor-next.network')
+ remove_network_unit('25-neighbor-dummy.network.d/10-step1.conf',
+ '25-neighbor-dummy.network.d/10-step2.conf')
+ copy_network_unit('25-neighbor-dummy.network.d/10-step3.conf')
networkctl_reload()
self.wait_online(['dummy98:degraded'])
+
print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
print(output)
+ self.assertIn('192.168.10.1 lladdr 00:00:5e:00:03:66 PERMANENT', output)
self.assertNotIn('00:00:5e:00:02:65', output)
- self.assertIn('192.168.10.1 lladdr 00:00:5e:00:02:66 PERMANENT', output)
+ self.assertNotIn('00:00:5e:00:02:66', output)
+ self.assertNotIn('00:00:5e:00:03:65', output)
self.assertNotIn('2004:da8:1::1', output)
- def test_neighbor_gre(self):
- copy_network_unit('25-neighbor-ip.network', '25-neighbor-ipv6.network', '25-neighbor-ip-dummy.network',
- '12-dummy.netdev', '25-gre-tunnel-remote-any.netdev', '25-ip6gre-tunnel-remote-any.netdev')
- start_networkd()
- self.wait_online(['dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable'], timeout='40s')
-
- output = check_output('ip neigh list dev gretun97')
- print(output)
- self.assertIn('10.0.0.22 lladdr 10.65.223.239 PERMANENT', output)
- self.assertNotIn('10.0.0.23', output)
-
- output = check_output('ip neigh list dev ip6gretun97')
- print(output)
- self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT')
- self.assertNotIn('2001:db8:0:f102::18', output)
-
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
-
def test_link_local_addressing(self):
copy_network_unit('25-link-local-addressing-yes.network', '11-dummy.netdev',
'25-link-local-addressing-no.network', '12-dummy.netdev')
# default is true, if neither are specified
expected = True
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
yesno = 'yes' if expected else 'no'
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
self.assertRegex(output, 'Address: 192.168.42.100')
self.assertRegex(output, 'DNS: 192.168.42.1')
self.assertRegex(output, 'inet 10.1.2.3/16 scope global dummy98')
self.assertNotRegex(output, 'inet 10.2.3.4/16 scope global dynamic dummy98')
- def check_nexthop(self, manage_foreign_nexthops):
+ def check_nexthop(self, manage_foreign_nexthops, first):
self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable'])
output = check_output('ip nexthop list dev veth99')
print(output)
- self.assertIn('id 1 via 192.168.5.1 dev veth99', output)
- self.assertIn('id 2 via 2001:1234:5:8f63::2 dev veth99', output)
+ if first:
+ self.assertIn('id 1 via 192.168.5.1 dev veth99', output)
+ self.assertIn('id 2 via 2001:1234:5:8f63::2 dev veth99', output)
+ else:
+ self.assertIn('id 6 via 192.168.5.1 dev veth99', output)
+ self.assertIn('id 7 via 2001:1234:5:8f63::2 dev veth99', output)
self.assertIn('id 3 dev veth99', output)
self.assertIn('id 4 dev veth99', output)
- self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink')
+ if first:
+ self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink')
+ else:
+ self.assertIn('id 5 via 192.168.5.3 dev veth99', output)
+ self.assertNotRegex(output, 'id 5 via 192.168.5.3 dev veth99 .*onlink')
self.assertIn('id 8 via fe80:0:222:4dff:ff:ff:ff:ff dev veth99', output)
if manage_foreign_nexthops:
self.assertRegex(output, r'id [0-9]* via 192.168.5.2 dev veth99')
output = check_output('ip nexthop list dev dummy98')
print(output)
- self.assertIn('id 20 via 192.168.20.1 dev dummy98', output)
+ if first:
+ self.assertIn('id 20 via 192.168.20.1 dev dummy98', output)
+ else:
+ self.assertIn('id 21 via 192.168.20.1 dev dummy98', output)
if manage_foreign_nexthops:
self.assertNotIn('id 42 via 192.168.20.2 dev dummy98', output)
else:
# kernel manages blackhole nexthops on lo
output = check_output('ip nexthop list dev lo')
print(output)
- self.assertIn('id 6 blackhole', output)
- self.assertIn('id 7 blackhole', output)
+ if first:
+ self.assertIn('id 6 blackhole', output)
+ self.assertIn('id 7 blackhole', output)
+ else:
+ self.assertIn('id 1 blackhole', output)
+ self.assertIn('id 2 blackhole', output)
# group nexthops are shown with -0 option
- output = check_output('ip -0 nexthop list id 21')
- print(output)
- self.assertRegex(output, r'id 21 group (1,3/20|20/1,3)')
+ if first:
+ output = check_output('ip -0 nexthop list id 21')
+ print(output)
+ self.assertRegex(output, r'id 21 group (1,3/20|20/1,3)')
+ else:
+ output = check_output('ip -0 nexthop list id 20')
+ print(output)
+ self.assertRegex(output, r'id 20 group (5,3/21|21/5,3)')
output = check_output('ip route show dev veth99 10.10.10.10')
print(output)
- self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output)
+ if first:
+ self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output)
+ else:
+ self.assertEqual('10.10.10.10 nhid 6 via 192.168.5.1 proto static', output)
output = check_output('ip route show dev veth99 10.10.10.11')
print(output)
- self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output)
+ if first:
+ self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output)
+ else:
+ self.assertEqual('10.10.10.11 nhid 7 via inet6 2001:1234:5:8f63::2 proto static', output)
output = check_output('ip route show dev veth99 10.10.10.12')
print(output)
- self.assertEqual('10.10.10.12 nhid 5 via 192.168.10.1 proto static onlink', output)
+ if first:
+ self.assertEqual('10.10.10.12 nhid 5 via 192.168.10.1 proto static onlink', output)
+ else:
+ self.assertEqual('10.10.10.12 nhid 5 via 192.168.5.3 proto static', output)
output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1')
print(output)
- self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
+ if first:
+ self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
+ else:
+ self.assertEqual('2001:1234:5:8f62::1 nhid 7 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
output = check_output('ip route show 10.10.10.13')
print(output)
- self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output)
+ if first:
+ self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output)
+ else:
+ self.assertEqual('blackhole 10.10.10.13 nhid 1 dev lo proto static', output)
output = check_output('ip -6 route show 2001:1234:5:8f62::2')
print(output)
- self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output)
+ if first:
+ self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output)
+ else:
+ self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 2 dev lo proto static metric 1024 pref medium', output)
output = check_output('ip route show 10.10.10.14')
print(output)
- self.assertIn('10.10.10.14 nhid 21 proto static', output)
+ if first:
+ self.assertIn('10.10.10.14 nhid 21 proto static', output)
+ self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output)
+ else:
+ self.assertIn('10.10.10.14 nhid 20 proto static', output)
+ self.assertIn('nexthop via 192.168.5.3 dev veth99 weight 3', output)
self.assertIn('nexthop via 192.168.20.1 dev dummy98 weight 1', output)
- self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def _test_nexthop(self, manage_foreign_nexthops):
if not manage_foreign_nexthops:
check_output('ip address add 192.168.20.20/24 dev dummy98')
check_output('ip nexthop add id 42 via 192.168.20.2 dev dummy98')
- copy_network_unit('25-nexthop.network', '25-veth.netdev', '25-veth-peer.network',
- '12-dummy.netdev', '25-nexthop-dummy.network')
+ copy_network_unit('25-nexthop-1.network', '25-veth.netdev', '25-veth-peer.network',
+ '12-dummy.netdev', '25-nexthop-dummy-1.network')
start_networkd()
- self.check_nexthop(manage_foreign_nexthops)
+ self.check_nexthop(manage_foreign_nexthops, first=True)
- remove_network_unit('25-nexthop.network')
+ remove_network_unit('25-nexthop-1.network', '25-nexthop-dummy-1.network')
+ copy_network_unit('25-nexthop-2.network', '25-nexthop-dummy-2.network')
+ networkctl_reload()
+ self.check_nexthop(manage_foreign_nexthops, first=False)
+
+ remove_network_unit('25-nexthop-2.network')
copy_network_unit('25-nexthop-nothing.network')
networkctl_reload()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
print(output)
self.assertEqual(output, '')
- remove_network_unit('25-nexthop-nothing.network')
- copy_network_unit('25-nexthop.network')
- networkctl_reconfigure('dummy98')
+ remove_network_unit('25-nexthop-nothing.network', '25-nexthop-dummy-2.network')
+ copy_network_unit('25-nexthop-1.network', '25-nexthop-dummy-1.network')
networkctl_reload()
- self.check_nexthop(manage_foreign_nexthops)
+ self.check_nexthop(manage_foreign_nexthops, first=True)
remove_link('veth99')
time.sleep(2)
self.wait_online(['dummy98:routable'])
# make link state file updated
- check_output(*resolvectl_cmd, 'revert', 'dummy98', env=env)
+ resolvectl('revert', 'dummy98')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
self.assertIn('MDNS=yes', output)
self.assertIn('DNSSEC=no', output)
- check_output(*resolvectl_cmd, 'dns', 'dummy98', '10.10.10.12#ccc.com', '10.10.10.13', '1111:2222::3333', env=env)
- check_output(*resolvectl_cmd, 'domain', 'dummy98', 'hogehogehoge', '~foofoofoo', env=env)
- check_output(*resolvectl_cmd, 'llmnr', 'dummy98', 'yes', env=env)
- check_output(*resolvectl_cmd, 'mdns', 'dummy98', 'no', env=env)
- check_output(*resolvectl_cmd, 'dnssec', 'dummy98', 'yes', env=env)
- check_output(*timedatectl_cmd, 'ntp-servers', 'dummy98', '2.fedora.pool.ntp.org', '3.fedora.pool.ntp.org', env=env)
+ resolvectl('dns', 'dummy98', '10.10.10.12#ccc.com', '10.10.10.13', '1111:2222::3333')
+ resolvectl('domain', 'dummy98', 'hogehogehoge', '~foofoofoo')
+ resolvectl('llmnr', 'dummy98', 'yes')
+ resolvectl('mdns', 'dummy98', 'no')
+ resolvectl('dnssec', 'dummy98', 'yes')
+ timedatectl('ntp-servers', 'dummy98', '2.fedora.pool.ntp.org', '3.fedora.pool.ntp.org')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
self.assertIn('MDNS=no', output)
self.assertIn('DNSSEC=yes', output)
- check_output(*timedatectl_cmd, 'revert', 'dummy98', env=env)
+ timedatectl('revert', 'dummy98')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
self.assertIn('MDNS=no', output)
self.assertIn('DNSSEC=yes', output)
- check_output(*resolvectl_cmd, 'revert', 'dummy98', env=env)
+ resolvectl('revert', 'dummy98')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
def tearDown(self):
tear_down_common()
+ def test_bridge_mac_none(self):
+ copy_network_unit('12-dummy-mac.netdev', '26-bridge-mac-slave.network',
+ '26-bridge-mac.netdev', '26-bridge-mac-master.network', '26-bridge-mac.link')
+ start_networkd()
+ self.wait_online(['dummy98:enslaved', 'bridge99:degraded'])
+
+ output = check_output('ip link show dev dummy98')
+ print(output)
+ self.assertIn('link/ether 12:34:56:78:9a:01', output)
+
+ output = check_output('ip link show dev bridge99')
+ print(output)
+ self.assertIn('link/ether 12:34:56:78:9a:01', output)
+
def test_bridge_vlan(self):
copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave.network',
'26-bridge.netdev', '26-bridge-vlan-master.network',
self.wait_online(['bridge99:no-carrier:no-carrier'])
self.check_link_attr('bridge99', 'carrier', '0')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bridge99', env=env)
+ output = networkctl_status('bridge99')
self.assertRegex(output, '10.1.2.3')
self.assertRegex(output, '10.1.2.1')
if trial > 0:
time.sleep(1)
- output = check_output(*networkctl_cmd, 'lldp', env=env)
+ output = networkctl('lldp')
print(output)
if re.search(r'veth99 .* veth-peer', output):
break
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ output = resolvectl('dns', 'veth99')
print(output)
self.assertRegex(output, 'fe80::')
self.assertRegex(output, '2002:da8:1::1')
- output = check_output(*resolvectl_cmd, 'domain', 'veth99', env=env)
+ output = resolvectl('domain', 'veth99')
print(output)
self.assertIn('hogehoge.test', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, '2002:da8:1:0')
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, '2002:da8:1:0:1a:2b:3c:4d')
self.assertRegex(output, '2002:da8:1:0:fa:de:ca:fe')
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e', output)
self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc', output) # EUI64
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e', output)
self.assertIn('2002:da8:2:0:f689:561a:8eda:7443', output)
self.wait_online(['client:routable'])
self.wait_address('client', '2002:da8:1:99:1034:56ff:fe78:9a00/64', ipv='-6', timeout_sec=10)
- output = check_output(*networkctl_cmd, 'status', 'client', env=env)
+ output = networkctl_status('client')
print(output)
self.assertIn('Captive Portal: http://systemd.io', output)
self.wait_online(['client:routable'])
self.wait_address('client', '2002:da8:1:99:1034:56ff:fe78:9a00/64', ipv='-6', timeout_sec=10)
- output = check_output(*networkctl_cmd, 'status', 'client', env=env)
+ output = networkctl_status('client')
print(output)
self.assertNotIn('Captive Portal:', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.3', output)
self.assertRegex(output, 'DNS: 192.168.5.1\n *192.168.5.10')
self.assertRegex(output, 'NTP: 192.168.5.1\n *192.168.5.11')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env)
+ output = networkctl_status('veth-peer')
self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*")
def test_dhcp_server_null_server_address(self):
client_address = json.loads(output)[0]['addr_info'][0]['local']
print(client_address)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, rf'Address: {client_address} \(DHCP4 via {server_address}\)')
self.assertIn(f'Gateway: {server_address}', output)
self.assertIn(f'DNS: {server_address}', output)
self.assertIn(f'NTP: {server_address}', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env)
+ output = networkctl_status('veth-peer')
self.assertIn(f'Offered DHCP leases: {client_address}', output)
def test_dhcp_server_with_uplink(self):
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.3', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.1', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('Address: 10.1.1.200 (DHCP4 via 10.1.1.1)', output)
self.assertIn('DHCP4 Client ID: 12:34:56:78:9a:bc', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('Address: 10.1.1.200 (DHCP4 via 10.1.1.1)', output)
self.assertRegex(output, 'DHCP4 Client ID: IAID:[0-9a-z]*/DUID')
self.wait_online(['client:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'client', env=env)
+ output = networkctl_status('client')
print(output)
self.assertRegex(output, r'Address: 192.168.5.150 \(DHCP4 via 192.168.5.1\)')
self.assertNotIn('DHCPREPLY(veth-peer)', output)
# Check json format
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- check_json(output)
+ check_json(networkctl_json('veth99'))
# solicit mode
stop_dnsmasq()
self.assertRegex(output, 'token :: dev veth99')
# Make manager and link state file updated
- check_output(*resolvectl_cmd, 'revert', 'veth99', env=env)
+ resolvectl('revert', 'veth99')
# Check link state file
print('## link state file')
self.assertIn('sent size: 0 option: 14 rapid-commit', output)
# Check json format
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- check_json(output)
+ check_json(networkctl_json('veth99'))
# Testing without rapid commit support
with open(os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8') as f:
self.assertRegex(output, 'via fe80::1034:56ff:fe78:9abd')
# Make manager and link state file updated
- check_output(*resolvectl_cmd, 'revert', 'veth99', env=env)
+ resolvectl('revert', 'veth99')
# Check link state file
print('## link state file')
self.assertNotIn('rapid-commit', output)
# Check json format
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- check_json(output)
+ check_json(networkctl_json('veth99'))
def test_dhcp_client_ipv6_dbus_status(self):
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network')
# Test renew command
# See https://github.com/systemd/systemd/pull/29472#issuecomment-1759092138
- check_output(*networkctl_cmd, 'renew', 'veth99', env=env)
+ networkctl('renew', 'veth99')
for _ in range(100):
state = get_dhcp4_client_state('veth99')
self.assertIn('DOMAINS=example.com', output)
print('## json')
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- j = json.loads(output)
+ j = json.loads(networkctl_json('veth99'))
self.assertEqual(len(j['DNS']), 2)
for i in j['DNS']:
self.assertIn('DOMAINS=foo.example.com', output)
print('## json')
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- j = json.loads(output)
+ j = json.loads(networkctl_json('veth99'))
self.assertEqual(len(j['DNS']), 3)
for i in j['DNS']:
self.assertNotRegex(output, r'8.8.8.8 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024')
self.assertNotRegex(output, r'9.9.9.9 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def test_dhcp_client_settings_anonymize(self):
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network')
start_dnsmasq()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, '192.168.5')
self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
# make resolved re-read the link state file
- check_output(*resolvectl_cmd, 'revert', 'veth99', env=env)
+ resolvectl('revert', 'veth99')
- output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ output = resolvectl('dns', 'veth99')
print(output)
if ipv4:
self.assertIn('192.168.5.1', output)
else:
self.assertNotIn('2600::1', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4')
self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
- output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
if ipv4 or ipv6:
self.assertIn('Captive Portal: http://systemd.io', output)
else:
self.assertNotIn('Captive Portal: http://systemd.io', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4')
self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
- output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertNotIn('Captive Portal: ', output)
self.assertNotIn('invalid/url', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
self.assertIn('inet6 2001:db8:0:2:fa:de:ca:fe', output)
self.assertNotIn('inet6 2001:db8:0:3:', output)
- output = check_output(*resolvectl_cmd, 'dns', 'veth-peer', env=env)
+ output = resolvectl('dns', 'veth-peer')
print(output)
self.assertRegex(output, '2001:db8:1:1::2')
- output = check_output(*resolvectl_cmd, 'domain', 'veth-peer', env=env)
+ output = resolvectl('domain', 'veth-peer')
print(output)
self.assertIn('example.com', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth-peer', env=env)
+ output = networkctl_json('veth-peer')
check_json(output)
# PREF64 or NAT64
self.assertNotIn('inet6 2001:db8:0:1:', output)
self.assertIn('inet6 2001:db8:0:2:', output)
- output = check_output(*resolvectl_cmd, 'dns', 'veth-peer', env=env)
+ output = resolvectl('dns', 'veth-peer')
print(output)
self.assertRegex(output, '2001:db8:1:1::2')
- output = check_output(*resolvectl_cmd, 'domain', 'veth-peer', env=env)
+ output = resolvectl('domain', 'veth-peer')
print(output)
self.assertIn('example.com', output)
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Verbose successful service
+
+[Service]
+ExecStart=/bin/echo success
After=testsuite-23-bound-by.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
# --kill-who= (no 'm') to check that the short form is accepted
ExecStopPost=systemctl kill --kill-whom=main -sRTMIN+1 testsuite-23.service
Description=Unit with BoundBy=
[Service]
-ExecStart=/bin/sleep 0.7
+ExecStart=sleep 0.7
OnFailure=testsuite-23-uphold.service
[Service]
-ExecStart=/bin/false
+ExecStart=false
NotifyAccess=all
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
NotifyAccess=all
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
NotifyAccess=all
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file-x && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file-x && systemd-notify --ready && sleep infinity'
PrivateTmp=yes
ExecStartPre=test -e /tmp/shared-private-file-x
ExecStartPre=test -e /tmp/hoge
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file-y && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file-y && systemd-notify --ready && sleep infinity'
BindPaths=/run/testsuite-23-marker-fixed:/tmp/testfile-marker-fixed
InaccessiblePaths=/run/inaccessible
ExecStartPre=grep -q -F MARKER_FIXED /tmp/testfile-marker-fixed
-ExecStart=/bin/sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; test ! -f /run/inaccessible/testfile-marker-fixed'
+ExecStart=sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; test ! -f /run/inaccessible/testfile-marker-fixed'
RuntimeMaxSec=5
Type=notify
RemainAfterExit=yes
-ExecStart=/bin/sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; exit 0'
+ExecStart=sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; exit 0'
StopPropagatedFrom=testsuite-23-prop-stop-two.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecStopPost=systemctl kill --kill-whom=main -sUSR2 testsuite-23.service
Description=Stop Propagation Sender
[Service]
-ExecStart=/bin/sleep 1.5
+ExecStart=sleep 1.5
[Service]
Type=oneshot
RemainAfterExit=yes
-ExecStart=/bin/sh -c "if [ -f /tmp/testsuite-23-retry-fail ]; then exit 0; else exit 1; fi"
+ExecStart=sh -c "if [ -f /tmp/testsuite-23-retry-fail ]; then exit 0; else exit 1; fi"
Restart=no
[Service]
Type=oneshot
RemainAfterExit=yes
-ExecStart=/bin/echo ok
+ExecStart=echo ok
Upholds=testsuite-23-retry-upheld.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
OnSuccess=testsuite-23-fail.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Description=Unit that sets UpheldBy= through [Install]
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
[Install]
UpheldBy=testsuite-23-retry-uphold.service
Upholds=testsuite-23-short-lived.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
Before=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Before=b.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Wants=f.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Requires=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Requires=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=parent.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
CPUAccounting=true
[Service]
Slice=dml-discard.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=dml-discard.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
MemoryLow=15
[Service]
Slice=dml-override.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=dml-passthrough.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=dml-passthrough.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
DefaultMemoryLow=15
[Service]
Slice=dml-passthrough.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
MemoryLow=0
Wants=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Description=F
[Service]
-ExecStart=/bin/true
+ExecStart=true
Conflicts=e.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=parent-deep.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
Wants=g.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
After=b.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Unit]
Conflicts=loopy4.service
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Unit]
Conflicts=loopy4.service
[Service]
Slice=nomem.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
IOWeight=200
MemoryAccounting=true
Description=Bad sched priority for Idle
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=1
Description=Sched idle with prio 0
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=0
Description=Bad sched priority for RR
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=-1
CPUSchedulingPriority=100
CPUSchedulingPolicy=rr
Description=Change prio
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=1
CPUSchedulingPriority=2
CPUSchedulingPriority=99
Description=Default prio for RR
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPolicy=rr
[Service]
Slice=parent.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
CPUShares=100
[Unit]
Description=Wait for 2 seconds
[Service]
-ExecStart=/bin/sh -ec 'sleep 2'
+ExecStart=sh -ec 'sleep 2'
EOF
cat <<EOF >/run/systemd/system/wait5fail.service
[Unit]
Description=Wait for 5 seconds and fail
[Service]
-ExecStart=/bin/sh -ec 'sleep 5; false'
+ExecStart=sh -ec 'sleep 5; false'
EOF
# wait2 succeeds
journalctl --sync
[[ -z "$(journalctl -b -q -u silent-success.service)" ]]
+# Test syslog identifiers exclusion
+systemctl start verbose-success.service
+timeout 30 bash -xec 'while systemctl -q is-active verbose-success.service; do sleep 1; done'
+journalctl --sync
+[[ -n "$(journalctl -b -q -u verbose-success.service -t systemd)" ]]
+[[ -n "$(journalctl -b -q -u verbose-success.service -t echo)" ]]
+[[ -n "$(journalctl -b -q -u verbose-success.service -T systemd)" ]]
+[[ -n "$(journalctl -b -q -u verbose-success.service -T echo)" ]]
+[[ -z "$(journalctl -b -q -u verbose-success.service -T echo -T '(echo)' -T systemd -T '(systemd)' -T systemd-executor)" ]]
+
# Exercise the matching machinery
SYSTEMD_LOG_LEVEL=debug journalctl -b -n 1 /dev/null /dev/zero /dev/null /dev/null /dev/null
journalctl -b -n 1 /bin/true /bin/false
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
+pre=test05
+cat >/run/systemd/system/"$pre"alpha.slice <<EOF
+[Slice]
+MemoryMax=40M
+MemoryHigh=40M
+TasksMax=400
+EOF
+
+cat >/run/systemd/system/"$pre"alpha-beta.slice <<EOF
+[Slice]
+MemoryMax=10M
+MemoryHigh=10M
+TasksMax=100
+EOF
+
+cat >/run/systemd/system/"$pre"alpha-beta-gamma.slice <<EOF
+[Slice]
+MemoryMax=20M
+MemoryHigh=20M
+TasksMax=200
+EOF
+
+systemctl daemon-reload
+
+srv=probe.service
+slc0="$pre"alpha.slice
+slc="$pre"alpha-beta-gamma.slice
+
+systemd-run --unit "$srv" --slice "$slc" \
+ -p MemoryMax=5M \
+ -p MemoryHigh=5M \
+ -p TasksMax=50 \
+ sleep inf
+
+# Compare with inequality because test can run in a constrained container
+assert_le "$(systemctl show -P EffectiveMemoryMax "$srv")" "5242880"
+assert_le "$(systemctl show -P EffectiveMemoryHigh "$srv")" "5242880"
+assert_le "$(systemctl show -P EffectiveTasksMax "$srv")" "50"
+
+systemctl stop "$srv"
+
+systemd-run --unit "$srv" --slice "$slc" \
+ sleep inf
+
+assert_le "$(systemctl show -P EffectiveMemoryMax "$srv")" "10485760"
+assert_le "$(systemctl show -P EffectiveMemoryHigh "$srv")" "10485760"
+assert_le "$(systemctl show -P EffectiveTasksMax "$srv")" "100"
+
+systemctl set-property "$slc0" \
+ MemoryMax=5M \
+ MemoryHigh=5M \
+ TasksMax=50
+
+assert_le "$(systemctl show -P EffectiveMemoryMax "$srv")" "5242880"
+assert_le "$(systemctl show -P EffectiveMemoryHigh "$srv")" "5242880"
+assert_le "$(systemctl show -P EffectiveTasksMax "$srv")" "50"
+
+systemctl stop "$srv"
+
+rm -f /run/systemd/system/"$pre"* || :
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+P=/run/systemd/system.conf.d
+mkdir $P
+
+cat >$P/rlimits.conf <<EOF
+[Manager]
+DefaultLimitNOFILE=10000:16384
+EOF
+
+systemctl daemon-reload
+
+[[ "$(systemctl show -P DefaultLimitNOFILESoft)" = "10000" ]]
+[[ "$(systemctl show -P DefaultLimitNOFILE)" = "16384" ]]
+
+[[ "$(systemctl show -P LimitNOFILESoft testsuite-05.service)" = "10000" ]]
+[[ "$(systemctl show -P LimitNOFILE testsuite-05.service)" = "16384" ]]
+
+# shellcheck disable=SC2016
+systemd-run --wait -t bash -c '[[ "$(ulimit -n -S)" = "10000" ]]'
+# shellcheck disable=SC2016
+systemd-run --wait -t bash -c '[[ "$(ulimit -n -H)" = "16384" ]]'
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
-Description=TEST-05-RLIMITS
+Description=TEST-05-LIMITS
[Service]
ExecStartPre=rm -f /failed /testok
set -eux
set -o pipefail
-P=/run/systemd/system.conf.d
-mkdir $P
+# shellcheck source=test/units/test-control.sh
+. "$(dirname "$0")"/test-control.sh
-cat >$P/rlimits.conf <<EOF
-[Manager]
-DefaultLimitNOFILE=10000:16384
-EOF
-
-systemctl daemon-reload
-
-[[ "$(systemctl show -P DefaultLimitNOFILESoft)" = "10000" ]]
-[[ "$(systemctl show -P DefaultLimitNOFILE)" = "16384" ]]
-
-[[ "$(systemctl show -P LimitNOFILESoft testsuite-05.service)" = "10000" ]]
-[[ "$(systemctl show -P LimitNOFILE testsuite-05.service)" = "16384" ]]
-
-# shellcheck disable=SC2016
-systemd-run --wait -t bash -c '[[ "$(ulimit -n -S)" = "10000" ]]'
-# shellcheck disable=SC2016
-systemd-run --wait -t bash -c '[[ "$(ulimit -n -H)" = "16384" ]]'
+run_subtests
touch /testok
bash -xec "mountpoint /etc; test -d /etc/systemd; mountpoint /mnt; ! mountpoint /usr"
systemd-run --wait --pipe -p BindReadOnlyPaths="/etc /home:/mnt:norbind -/foo/bar/baz:/usr:rbind" \
bash -xec "test ! -w /etc; test ! -w /mnt; ! mountpoint /usr"
+# Make sure we properly serialize/deserialize paths with spaces
+# See: https://github.com/systemd/systemd/issues/30747
+touch "/tmp/test file with spaces"
+systemd-run --wait --pipe -p TemporaryFileSystem="/tmp" -p BindPaths="/etc /home:/mnt:norbind /tmp/test\ file\ with\ spaces" \
+ bash -xec "mountpoint /etc; test -d /etc/systemd; mountpoint /mnt; stat '/tmp/test file with spaces'"
+systemd-run --wait --pipe -p TemporaryFileSystem="/tmp" -p BindPaths="/etc /home:/mnt:norbind /tmp/test\ file\ with\ spaces:/tmp/destination\ wi\:th\ spaces" \
+ bash -xec "mountpoint /etc; test -d /etc/systemd; mountpoint /mnt; stat '/tmp/destination wi:th spaces'"
# Check if we correctly serialize, deserialize, and set directives that
# have more complex internal handling
# {Cache,Configuration,Logs,Runtime,State}Directory=
ARGUMENTS=(
- -p CacheDirectory="foo/bar/baz"
+ -p CacheDirectory="foo/bar/baz also\ with\ spaces"
-p CacheDirectory="foo"
-p CacheDirectory="context"
-p CacheDirectoryMode="0123"
-p CacheDirectoryMode="0666"
- -p ConfigurationDirectory="context/foo also_context/bar context/nested/baz"
+ -p ConfigurationDirectory="context/foo also_context/bar context/nested/baz context/semi\:colon"
-p ConfigurationDirectoryMode="0400"
-p LogsDirectory="context/foo"
-p LogsDirectory=""
-p LogsDirectory="context/a/very/nested/logs/dir"
- -p RuntimeDirectory="context"
- -p RuntimeDirectory="also_context"
+ -p RuntimeDirectory="context/with\ spaces"
+ # Note: {Runtime,State,Cache,Logs}Directory= directives support the directory:symlink syntax, which
+ # requires an additional level of escaping for the colon character
+ -p RuntimeDirectory="also_context:a\ symlink\ with\ \\\:\ col\\\:ons\ and\ \ spaces"
-p RuntimeDirectoryPreserve=yes
-p StateDirectory="context"
-p StateDirectory="./././././././context context context"
rm -rf /run/context
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
- bash -xec '[[ $CACHE_DIRECTORY == /var/cache/context:/var/cache/foo:/var/cache/foo/bar/baz ]];
- [[ $(stat -c "%a" ${CACHE_DIRECTORY##*:}) == 666 ]]'
+ bash -xec '[[ $CACHE_DIRECTORY == "/var/cache/also with spaces:/var/cache/context:/var/cache/foo:/var/cache/foo/bar/baz" ]];
+ [[ $(stat -c "%a" "${CACHE_DIRECTORY##*:}") == 666 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
- bash -xec '[[ $CONFIGURATION_DIRECTORY == /etc/also_context/bar:/etc/context/foo:/etc/context/nested/baz ]];
- [[ $(stat -c "%a" ${CONFIGURATION_DIRECTORY##*:}) == 400 ]]'
+ bash -xec '[[ $CONFIGURATION_DIRECTORY == /etc/also_context/bar:/etc/context/foo:/etc/context/nested/baz:/etc/context/semi:colon ]];
+ [[ $(stat -c "%a" "${CONFIGURATION_DIRECTORY%%:*}") == 400 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
bash -xec '[[ $LOGS_DIRECTORY == /var/log/context/a/very/nested/logs/dir:/var/log/context/foo ]];
- [[ $(stat -c "%a" ${LOGS_DIRECTORY##*:}) == 755 ]]'
+ [[ $(stat -c "%a" "${LOGS_DIRECTORY##*:}") == 755 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
- bash -xec '[[ $RUNTIME_DIRECTORY == /run/also_context:/run/context ]];
- [[ $(stat -c "%a" ${RUNTIME_DIRECTORY##*:}) == 755 ]];
- [[ $(stat -c "%a" ${RUNTIME_DIRECTORY%%:*}) == 755 ]]'
+ bash -xec '[[ $RUNTIME_DIRECTORY == "/run/also_context:/run/context/with spaces" ]];
+ [[ $(stat -c "%a" "${RUNTIME_DIRECTORY##*:}") == 755 ]];
+ [[ $(stat -c "%a" "${RUNTIME_DIRECTORY%%:*}") == 755 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
bash -xec '[[ $STATE_DIRECTORY == /var/lib/context ]]; [[ $(stat -c "%a" $STATE_DIRECTORY) == 0 ]]'
-test -d /run/context
+test -d "/run/context/with spaces"
+test -s "/run/a symlink with : col:ons and spaces"
rm -rf /var/{cache,lib,log}/context /etc/{also_,}context
# Limit*=
# Add one new ExecStart= before the existing ones.
#
# Since, after reload, we should continue running from the "sleep 3" statement, the newly added "echo
- # bar" one will have no efect and we should end up with the same output as in the previous case.
+ # bar" one will have no effect and we should end up with the same output as in the previous case.
cat >"$unit_path" <<EOF
[Service]
Type=oneshot
# Remove the currently executed ExecStart= line.
#
- # In this case we completely drop the currently excuted "sleep 3" statement, so after reload systemd
+ # In this case we completely drop the currently executed "sleep 3" statement, so after reload systemd
# should complain that the currently executed command vanished and simply finish executing the unit,
# resulting in an empty log.
cat >"$unit_path" <<EOF
ExecStartPre=sh -c 'test "$TRIGGER_UNIT" = my.timer'
ExecStartPre=sh -c 'test -n "$TRIGGER_TIMER_REALTIME_USEC"'
ExecStartPre=sh -c 'test -n "$TRIGGER_TIMER_MONOTONIC_USEC"'
-ExecStart=/bin/echo Timer runs me
+ExecStart=echo Timer runs me
EOF
cat >/run/systemd/system/my.timer <<EOF
Description=Test service
[Service]
StandardInput=socket
-ExecStart=/bin/sh -x -c cat
+ExecStart=sh -x -c cat
EOF
systemctl start issue-3171.socket
systemd-analyze log-level debug
-cat > /run/systemd/system/floodme@.service <<EOF
+cat >/run/systemd/system/floodme@.service <<EOF
[Service]
-ExecStart=/bin/true
+ExecStart=true
EOF
-cat > /run/systemd/system/floodme.socket <<EOF
+cat >/run/systemd/system/floodme.socket <<EOF
[Socket]
ListenStream=/tmp/floodme
PollLimitIntervalSec=10s
START=$(date +%s%N)
# Trigger this 100 times in a flood
-for (( i=0 ; i < 100; i++ )) ; do
+for _ in {1..100}; do
logger -u /tmp/floodme foo &
done
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
-set -ex
+set -eux
+TMPDIR=
TEST_RULE="/run/udev/rules.d/49-test.rules"
KILL_PID=
[[ -e /etc/udev/udev.conf ]] && cp -f /etc/udev/udev.conf /etc/udev/udev.conf.bak
cat >"${TEST_RULE}" <<EOF
-ACTION=="add", SUBSYSTEM=="mem", KERNEL=="null", OPTIONS="log_level=debug"
-ACTION=="add", SUBSYSTEM=="mem", KERNEL=="null", PROGRAM=="/bin/sleep 60"
+ACTION!="add", GOTO="test_end"
+SUBSYSTEM!="mem", GOTO="test_end"
+KERNEL!="null", GOTO="test_end"
+
+OPTIONS="log_level=debug"
+PROGRAM=="/bin/touch /tmp/test-udev-marker"
+PROGRAM!="/bin/sleep 60", ENV{PROGRAM_RESULT}="KILLED"
+
+LABEL="test_end"
EOF
cat >/etc/udev/udev.conf <<EOF
event_timeout=10
systemctl restart systemd-udevd.service
}
-run_test() {
- local since
+run_test_timeout() {
+ TMPDIR=$(mktemp -d -p /tmp udev-tests.XXXXXX)
+ udevadm monitor --udev --property --subsystem-match=mem >"$TMPDIR"/monitor.txt &
+ KILL_PID="$!"
+
+ SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --action add /dev/null
+
+ for _ in {1..40}; do
+ if grep -q 'PROGRAM_RESULT=KILLED' "$TMPDIR"/monitor.txt; then
+ sleep .5
+ kill "$KILL_PID"
+ KILL_PID=
+
+ cat "$TMPDIR"/monitor.txt
+ (! grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt)
+ (! grep -q 'UDEV_WORKER_SIGNAL=6' "$TMPDIR"/monitor.txt)
+ (! grep -q 'UDEV_WORKER_SIGNAL_NAME=ABRT' "$TMPDIR"/monitor.txt)
+ grep -q 'PROGRAM_RESULT=KILLED' "$TMPDIR"/monitor.txt
+ rm -rf "$TMPDIR"
+ return 0
+ fi
+ sleep .5
+ done
+
+ return 1
+}
- since="$(date '+%F %T')"
+run_test_killed() {
+ local killed=
TMPDIR=$(mktemp -d -p /tmp udev-tests.XXXXXX)
udevadm monitor --udev --property --subsystem-match=mem >"$TMPDIR"/monitor.txt &
KILL_PID="$!"
+ rm -f /tmp/test-udev-marker
SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --action add /dev/null
for _ in {1..40}; do
- if coredumpctl --since "$since" --no-legend --no-pager | grep /bin/udevadm ; then
+ if [[ -z "$killed" ]]; then
+ if [[ -e /tmp/test-udev-marker ]]; then
+ killall --signal ABRT --regexp udev-worker
+ killed=1
+ fi
+ elif grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt; then
+ sleep .5
kill "$KILL_PID"
KILL_PID=
grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt
grep -q 'UDEV_WORKER_SIGNAL=6' "$TMPDIR"/monitor.txt
grep -q 'UDEV_WORKER_SIGNAL_NAME=ABRT' "$TMPDIR"/monitor.txt
+ (! grep -q 'PROGRAM_RESULT=KILLED' "$TMPDIR"/monitor.txt)
+ rm -rf "$TMPDIR"
return 0
fi
sleep .5
trap teardown EXIT
setup
-run_test
+run_test_timeout
+run_test_killed
exit 0
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
+udevadm control --log-level=debug
+
+mkdir -p /run/systemd/network/
+cat >/run/systemd/network/10-test.link <<EOF
+[Match]
+Kind=dummy
+MACAddress=00:50:56:c0:00:19
+
+[Link]
+Name=test1
+EOF
+
+mkdir /run/systemd/network/10-test.link.d
+cat >/run/systemd/network/10-test.link.d/10-override.conf <<EOF
+[Link]
+Property=HOGE=foo BAR=baz SHOULD_BE_UNSET=unset
+UnsetProperty=SHOULD_BE_UNSET
+EOF
+
+udevadm control --reload
+
+ip link add address 00:50:56:c0:00:19 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/test1
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_in "HOGE=foo" "$output"
+assert_in "BAR=baz" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+cat >/run/systemd/network/10-test.link.d/11-override.conf <<EOF
+[Link]
+Property=
+Property=HOGE2=foo2 BAR2=baz2 SHOULD_BE_UNSET=unset
+ImportProperty=HOGE
+EOF
+
+udevadm control --reload
+
+udevadm trigger --settle --action add /sys/class/net/test1
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_in "HOGE=foo" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# On change event, .link file will not be applied.
+udevadm trigger --settle --action change /sys/class/net/test1
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+### testing with udevadm test-builtin
+output=$(udevadm test-builtin --action add net_setup_link /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_in "SHOULD_BE_UNSET=" "$output" # this is expected, as an empty assignment is also logged.
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# check that test-builtin command does not update udev database.
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm test-builtin --action change net_setup_link /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+### testing with udevadm test
+output=$(udevadm test --action add /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# check that test command _does_ update udev database.
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm test --action change /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# test for specifiers
+cat >/run/systemd/network/10-test.link.d/12-override.conf <<EOF
+[Link]
+Property=
+Property=LINK_VERSION=%v
+EOF
+
+udevadm control --reload
+
+output=$(udevadm test --action add /sys/class/net/test1)
+assert_in "LINK_VERSION=$(uname -r)" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_in "LINK_VERSION=$(uname -r)" "$output"
+
+# test for constant properties
+cat >/run/systemd/network/10-test.link.d/13-override.conf <<EOF
+[Link]
+Property=
+Property=ACTION=foo IFINDEX=bar
+UnsetProperty=DEVPATH
+EOF
+
+udevadm control --reload
+
+output=$(udevadm test --action add /sys/class/net/test1)
+assert_in "ACTION=add" "$output"
+assert_not_in "ACTION=foo" "$output"
+assert_in "IFINDEX=" "$output"
+assert_not_in "IFINDEX=bar" "$output"
+assert_in "DEVPATH=" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "ACTION=foo" "$output"
+assert_in "IFINDEX=" "$output"
+assert_not_in "IFINDEX=bar" "$output"
+assert_in "DEVPATH=" "$output"
+
+# cleanup
+ip link del dev test1
+
+rm -f /run/systemd/network/10-test.link
+rm -rf /run/systemd/network/10-test.link.d
+udevadm control --reload --log-level=info
+
+exit 0
--- /dev/null
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Tests for the --purge switch
+#
+set -eux
+set -o pipefail
+
+export SYSTEMD_LOG_LEVEL=debug
+
+systemd-tmpfiles --create - <<EOF
+d /tmp/somedir
+f /tmp/somedir/somefile - - - - baz
+EOF
+
+test -f /tmp/somedir/somefile
+grep -q baz /tmp/somedir/somefile
+
+systemd-tmpfiles --purge - <<EOF
+d /tmp/somedir
+f /tmp/somedir/somefile - - - - baz
+EOF
+
+test ! -f /tmp/somedir/somefile
+test ! -d /tmp/somedir/
+
+systemd-tmpfiles --create --purge - <<EOF
+d /tmp/somedir
+f /tmp/somedir/somefile - - - - baz
+EOF
+
+test -f /tmp/somedir/somefile
+grep -q baz /tmp/somedir/somefile
echo "[#1] Failing ExecReload= should not kill the service"
cat >"$SERVICE_PATH" <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecReload=/bin/false
EOF
echo "[#2] Failing ExecReload= should not kill the service (multiple ExecReload=)"
cat >"$SERVICE_PATH" <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecReload=/bin/true
ExecReload=/bin/false
ExecReload=/bin/true
echo "[#3] Failing ExecReload=- should not affect reload's exit code"
cat >"$SERVICE_PATH" <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecReload=-/bin/false
EOF
CacheDirectory=test-service
LogsDirectory=test-service
RuntimeDirectoryPreserve=yes
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
Type=exec
EOF
CacheDirectory=test-service
LogsDirectory=test-service
RuntimeDirectoryPreserve=yes
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
Type=exec
EOF
cat >/run/systemd/system/testsuite-23-no-reload.service <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
systemctl start testsuite-23-no-reload.service
cat >/run/systemd/system/testsuite-23-no-reload.service <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
# Start a non-existing unit first, so that the cache is reloaded for an unrelated
cat >/run/systemd/system/testsuite-23-no-reload.service <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
systemctl restart testsuite-23-no-reload.target
if [[ -r /etc/softhsm2.conf ]]; then
# Test unlocking with a PKCS#11 token
export SOFTHSM2_CONF="/etc/softhsm2.conf"
+
PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
cryptsetup_start_and_check empty_pkcs11_auto
cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
+ PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey;type=cert" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
+ cryptsetup_start_and_check empty_pkcs11_auto
+ cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
+ cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=ECTestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
cryptsetup_start_and_check empty_pkcs11_auto
cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
+ PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=ECTestKey;type=cert" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
+ cryptsetup_start_and_check empty_pkcs11_auto
+ cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
+ cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
fi
cryptsetup_start_and_check detached
trap cleanup_test_user EXIT
}
-test_enable_debug() {
- mkdir -p /run/systemd/system/systemd-logind.service.d
- cat >/run/systemd/system/systemd-logind.service.d/debug.conf <<EOF
+test_write_dropin() {
+ systemctl edit --runtime --stdin systemd-logind.service --drop-in=debug.conf <<EOF
[Service]
Environment=SYSTEMD_LOG_LEVEL=debug
EOF
- systemctl daemon-reload
- systemctl stop systemd-logind.service
+
+ # We test "coldplug" (completely stop and start logind) here. So we need to preserve
+ # the fdstore, which might contain session leader pidfds. This is extremely rare use case
+ # and shall not be considered fully supported.
+ # See also: https://github.com/systemd/systemd/pull/30610#discussion_r1440507850
+ systemctl edit --runtime --stdin systemd-logind.service --drop-in=fdstore-preserve.conf <<EOF
+[Service]
+FileDescriptorStorePreserve=yes
+EOF
+
+ systemctl restart systemd-logind.service
}
testcase_properties() {
}
setup_test_user
-test_enable_debug
+test_write_dropin
run_testcases
touch /testok
writeTestUnit() {
mkdir -p "$testUnitFile.d/"
- printf "[Service]\nExecStart=/bin/sleep 3600\n" >"$testUnitFile"
+ printf "[Service]\nExecStart=sleep 3600\n" >"$testUnitFile"
}
writeTestUnitNUMAPolicy() {
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/sleep 3600
+ExecStart=sleep 3600
systemd-analyze log-level debug
+journalctl --list-namespaces -o json | jq .
+
systemd-run --wait -p LogNamespace=foobar echo "hello world"
+systemd-run --wait -p LogNamespace=foobaz echo "hello world"
journalctl --namespace=foobar --sync
+journalctl --namespace=foobaz --sync
+ls -l /var/log/journal/
+journalctl --list-namespaces
+
journalctl -o cat --namespace=foobar >/tmp/hello-world
journalctl -o cat >/tmp/no-hello-world
+journalctl --list-namespaces | grep foobar
+journalctl --list-namespaces | grep foobaz
+journalctl --list-namespaces -o json | jq .
+[[ "$(journalctl --root=/tmp --list-namespaces --quiet)" == "" ]]
+
grep "^hello world$" /tmp/hello-world
(! grep "^hello world$" /tmp/no-hello-world)
Type=simple
AmbientCapabilities=
ExecStart=
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
systemctl daemon-reload
fi
RemainAfterExit=yes
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/sh -c ' \\
+ExecStart=sh -c ' \\
systemd-notify --ready; \\
while [ ! -f /tmp/img/usr/lib/os-release ] || ! grep -q -F MARKER /tmp/img/usr/lib/os-release; do \\
sleep 0.1; \\
ExtensionImages=/usr/share/app0.raw /usr/share/app1.raw:nosuid
# Relevant only for sanitizer runs
UnsetEnvironment=LD_PRELOAD
-ExecStart=/bin/bash -c '/opt/script0.sh | grep ID'
-ExecStart=/bin/bash -c '/opt/script1.sh | grep ID'
+ExecStart=bash -c '/opt/script0.sh | grep ID'
+ExecStart=bash -c '/opt/script1.sh | grep ID'
Type=oneshot
RemainAfterExit=yes
EOF
ExtensionDirectories=${image_dir}/app0 ${image_dir}/app1
# Relevant only for sanitizer runs
UnsetEnvironment=LD_PRELOAD
-ExecStart=/bin/bash -c '/opt/script0.sh | grep ID'
-ExecStart=/bin/bash -c '/opt/script1.sh | grep ID'
+ExecStart=bash -c '/opt/script0.sh | grep ID'
+ExecStart=bash -c '/opt/script1.sh | grep ID'
Type=oneshot
RemainAfterExit=yes
EOF
rm -rf /run/extensions/app-reject
rm /var/lib/extensions/app-nodistro.raw
+# Some super basic test that RootImage= works with .v/ dirs
+VBASE="vtest$RANDOM"
+VDIR="/tmp/${VBASE}.v"
+mkdir "$VDIR"
+
+ln -s "${image}.raw" "$VDIR/${VBASE}_33.raw"
+ln -s "${image}.raw" "$VDIR/${VBASE}_34.raw"
+ln -s "${image}.raw" "$VDIR/${VBASE}_35.raw"
+
+systemd-run -P -p RootImage="$VDIR" cat /usr/lib/os-release | grep -q -F "MARKER=1"
+
+rm "$VDIR/${VBASE}_33.raw" "$VDIR/${VBASE}_34.raw" "$VDIR/${VBASE}_35.raw"
+rmdir "$VDIR"
+
mkdir -p /run/machines /run/portables /run/extensions
touch /run/machines/a.raw /run/portables/b.raw /run/extensions/c.raw
assert_in "${loop}p3 : start= *${start}, size= *${size}, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=DB081670-07AE-48CA-9F5E-813D5E40B976, name=\"linux-generic-2\"" "$output"
}
+testcase_dropped_partitions() {
+ local workdir image defs
+
+ workdir="$(mktemp --directory "/tmp/test-repart.dropped-partitions.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '${workdir:?}'" RETURN
+
+ image="$workdir/image.img"
+ truncate -s 32M "$image"
+
+ defs="$workdir/defs"
+ mkdir "$defs"
+ echo -ne "[Partition]\nType=root\n" >"$defs/10-part1.conf"
+ echo -ne "[Partition]\nType=root\nSizeMinBytes=1T\nPriority=1\n" >"$defs/11-dropped-first.conf"
+ echo -ne "[Partition]\nType=root\n" >"$defs/12-part2.conf"
+ echo -ne "[Partition]\nType=root\nSizeMinBytes=1T\nPriority=2\n" >"$defs/13-dropped-second.conf"
+
+ systemd-repart --empty=allow --pretty=yes --dry-run=no --definitions="$defs" "$image"
+
+ sfdisk -q -l "$image"
+ [[ "$(sfdisk -q -l "$image" | grep -c "$image")" -eq 2 ]]
+}
+
OFFLINE="yes"
run_testcases
[Service]
Type=notify
-ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
+ExecStart=bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
EOF
cat >/run/systemd/system/testservice-fail-restart-59.service <<EOF
[Service]
Type=notify
-ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
+ExecStart=bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
Restart=on-failure
StartLimitBurst=1
EOF
[Service]
Type=notify
-ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 5; exit 1"
+ExecStart=bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 5; exit 1"
Restart=on-abort
EOF
[Unit]
Description=TEST-62-RESTRICT-IFACES-all-pings-work
[Service]
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-allow-list
[Service]
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=veth0
RestrictNetworkInterfaces=veth1
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-deny-list
[Service]
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=~veth0
RestrictNetworkInterfaces=~veth1
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-empty-assignment
[Service]
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=veth0
RestrictNetworkInterfaces=
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-invert-assignment
[Service]
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=veth0
RestrictNetworkInterfaces=veth0 veth1
RestrictNetworkInterfaces=~veth0
ConditionPathExists=/etc/os-release
[Service]
-ExecStart=/bin/true
+ExecStart=true
EOF
systemctl daemon-reload
systemd-analyze condition --unit="$UNIT_NAME"
DeviceAllow=/dev/null r
StandardOutput=file:/tmp/testsuite66serviceresults
ExecStartPre=rm -f /tmp/testsuite66serviceresults
-ExecStart=/bin/bash -c "while true; do sleep 0.01 && echo meow >/dev/null && echo thisshouldnotbehere; done"
+ExecStart=bash -c "while true; do sleep 0.01 && echo meow >/dev/null && echo thisshouldnotbehere; done"
[Service]
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
echo "$CHASSIS"
)
+stop_hostnamed() {
+ systemctl stop systemd-hostnamed.service
+ systemctl reset-failed systemd-hostnamed # reset trigger limit
+}
+
testcase_chassis() {
local i
assert_eq "$(get_chassis)" "$i"
done
- systemctl stop systemd-hostnamed.service
+ stop_hostnamed
rm -f /etc/machine-info
# fallback chassis type
umount /sys/class/dmi/id
rm -rf /run/systemd/system/systemd-hostnamed.service.d
systemctl daemon-reload
- systemctl stop systemd-hostnamed
+ stop_hostnamed
}
testcase_firmware_date() {
echo '1' >/sys/class/dmi/id/uevent
echo '09/08/2000' >/sys/class/dmi/id/bios_date
- systemctl stop systemd-hostnamed
+ stop_hostnamed
assert_in '2000-09-08' "$(hostnamectl)"
echo '2022' >/sys/class/dmi/id/bios_date
- systemctl stop systemd-hostnamed
+ stop_hostnamed
assert_not_in 'Firmware Date' "$(hostnamectl)"
echo 'garbage' >/sys/class/dmi/id/bios_date
- systemctl stop systemd-hostnamed
+ stop_hostnamed
assert_not_in 'Firmware Date' "$(hostnamectl)"
}
(! getent hosts -s myhostname fd00:dead:beef:cafe::1)
}
+test_varlink() {
+ A="$(mktemp -u)"
+ B="$(mktemp -u)"
+ varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}' --json=short > "$A"
+ hostnamectl --json=short > "$B"
+ cmp "$A" "$B"
+}
+
run_testcases
touch /testok
# Extended unit
cat >"/run/systemd/system/delta-test-unit-extended.service" <<EOF
[Service]
-ExecStart=/bin/true
+ExecStart=true
EOF
mkdir -p "/run/systemd/system/delta-test-unit-extended.service.d"
cat >"/run/systemd/system/delta-test-unit-extended.service.d/override.conf" <<EOF
[Unit]
Description=Foo Bar
[Service]
-ExecStartPre=/bin/true
+ExecStartPre=true
EOF
# Masked unit
cp -fv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-masked.service
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+at_exit() {
+ rm -f /run/credstore/network.network.50-testme
+ rm -f /run/systemd/system/systemd-network-generator.service.d/50-testme.conf
+}
+
+trap at_exit EXIT
+
+mkdir -p /run/credstore
+cat > /run/credstore/network.network.50-testme <<EOF
+[Match]
+Property=IDONTEXIST
+EOF
+
+systemctl edit systemd-network-generator.service --stdin --drop-in=50-testme.conf <<EOF
+[Service]
+LoadCredential=network.network.50-testme
+EOF
+
+systemctl restart systemd-network-generator
+
+test -f /run/systemd/network/50-testme.network
varlinkctl introspect /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend
fi
+# SSH transport
+SSHBINDIR="$(mktemp -d)"
+
+rm_rf_sshbindir() {
+ rm -rf "$SSHBINDIR"
+}
+
+trap rm_rf_sshbindir EXIT
+
+# Create a fake "ssh" binary that validates everything works as expected
+cat > "$SSHBINDIR"/ssh <<'EOF'
+#!/bin/sh
+
+set -xe
+
+test "$1" = "-W"
+test "$2" = "/run/systemd/journal/io.systemd.journal"
+test "$3" = "foobar"
+
+exec socat - UNIX-CONNECT:/run/systemd/journal/io.systemd.journal
+EOF
+
+chmod +x "$SSHBINDIR"/ssh
+
+SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh:foobar:/run/systemd/journal/io.systemd.journal
+
# Go through all varlink sockets we can find under /run/systemd/ for some extra coverage
find /run/systemd/ -name "io.systemd*" -type s | while read -r socket; do
varlinkctl info "$socket"
(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord </dev/null)
(! varlinkctl validate-idl "")
(! varlinkctl validate-idl </dev/null)
+
+varlinkctl info /run/systemd/io.systemd.Hostname
+varlinkctl introspect /run/systemd/io.systemd.Hostname io.systemd.Hostname
+varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}'
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+at_exit() {
+ set +e
+ rm -rf /var/lib/machines/mymachine.raw.v
+ rm -rf /var/lib/machines/mytree.v
+ rm -rf /var/lib/machines/testroot.v
+ umount -l /tmp/dotvroot
+ rmdir /tmp/dotvroot
+}
+
+trap at_exit EXIT
+
+mkdir -p /var/lib/machines/mymachine.raw.v
+
+touch /var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw
+touch /var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw
+touch /var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw
+touch /var/lib/machines/mymachine.raw.v/mymachine_7.7.0_x86-64+0-5.raw
+
+mkdir -p /var/lib/machines/mytree.v
+
+mkdir /var/lib/machines/mytree.v/mytree_33.4
+mkdir /var/lib/machines/mytree.v/mytree_33.5
+mkdir /var/lib/machines/mytree.v/mytree_36.0+0-5
+mkdir /var/lib/machines/mytree.v/mytree_37.0_arm64+2-3
+mkdir /var/lib/machines/mytree.v/mytree_38.0_arm64+0-5
+
+ARCH="$(busctl get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager Architecture | cut -d\" -f 2)"
+
+export SYSTEMD_LOG_LEVEL=debug
+
+if [ "$ARCH" = "x86-64" ] ; then
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
+
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.13)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.14)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
+ (! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.6.0)
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.7.0)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.7.0_x86-64+0-5.raw"
+
+ systemd-dissect --discover | grep "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
+elif [ "$ARCH" = "arm64" ] ; then
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
+
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.13)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
+ (! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.14)
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.6.0)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
+ (! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.7.0)
+
+ systemd-dissect --discover | grep "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
+else
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
+
+ test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.13)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
+ (! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.14)
+ (! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.6.0)
+ (! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.7.0)
+
+ systemd-dissect --discover | grep "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
+fi
+
+test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A x86-64)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
+test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
+(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A ia64)
+
+test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p version)" = "7.6.0"
+test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p type)" = "reg"
+test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p filename)" = "mymachine_7.6.0_arm64.raw"
+test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p arch)" = "arm64"
+
+test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t reg)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
+(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t dir)
+(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t fifo)
+(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t sock)
+
+if [ "$ARCH" != "arm64" ] ; then
+ test "$(systemd-vpick /var/lib/machines/mytree.v)" = "/var/lib/machines/mytree.v/mytree_33.5/"
+ test "$(systemd-vpick /var/lib/machines/mytree.v --type=dir)" = "/var/lib/machines/mytree.v/mytree_33.5/"
+else
+ test "$(systemd-vpick /var/lib/machines/mytree.v)" = "/var/lib/machines/mytree.v/mytree_37.0_arm64+2-3/"
+ test "$(systemd-vpick /var/lib/machines/mytree.v --type=dir)" = "/var/lib/machines/mytree.v/mytree_37.0_arm64+2-3/"
+fi
+
+(! systemd-vpick /var/lib/machines/mytree.v --type=reg)
+
+mkdir /tmp/dotvroot
+mount --bind / /tmp/dotvroot
+
+mkdir /var/lib/machines/testroot.v
+mkdir /var/lib/machines/testroot.v/testroot_32
+ln -s /tmp/dotvroot /var/lib/machines/testroot.v/testroot_33
+mkdir /var/lib/machines/testroot.v/testroot_34
+
+ls -l /var/lib/machines/testroot.v
+
+test "$(systemd-vpick /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_34/
+test "$(systemd-vpick --resolve=yes /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_34/
+(! systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true)
+
+find /var/lib/machines/testroot.v/testroot_34
+rm -rf /var/lib/machines/testroot.v/testroot_34
+test "$(systemd-vpick /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_33/
+test "$(systemd-vpick --resolve=yes /var/lib/machines/testroot.v)" = /tmp/dotvroot/
+systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true
+
+rm /var/lib/machines/testroot.v/testroot_33
+test "$(systemd-vpick /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_32/
+test "$(systemd-vpick --resolve=yes /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_32/
+(! systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true)
+
+rm -rf /var/lib/machines/testroot.v/testroot_32
+(! systemd-vpick /var/lib/machines/testroot.v)
+(! systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true)
DNS=10.0.0.1
DNS=fd00:dead:beef:cafe::1
EOF
+cat >/etc/systemd/network/10-dns1.netdev <<EOF
+[NetDev]
+Name=dns1
+Kind=dummy
+EOF
+cat >/etc/systemd/network/10-dns1.network <<EOF
+[Match]
+Name=dns1
+
+[Network]
+Address=10.99.0.1/24
+DNSSEC=no
+EOF
+systemctl edit --stdin --full --runtime --force "resolved-dummy-server.service" <<EOF
+[Service]
+Type=notify
+Environment=SYSTEMD_LOG_LEVEL=debug
+ExecStart=/usr/lib/systemd/tests/unit-tests/manual/test-resolved-dummy-server 10.99.0.1:53
+EOF
DNS_ADDRESSES=(
"10.0.0.1"
systemctl unmask systemd-networkd
systemctl start systemd-networkd
restart_resolved
+systemctl start resolved-dummy-server
# Create knot's runtime dir, since from certain version it's provided only by
# the package and not created by tmpfiles/systemd
if [[ ! -d /run/knot ]]; then
# Wait a bit for the keys to propagate
sleep 4
+systemctl status resolved-dummy-server
networkctl status
resolvectl status
resolvectl log-level debug
systemd-run -u resolvectl-monitor.service -p Type=notify resolvectl monitor
systemd-run -u resolvectl-monitor-json.service -p Type=notify resolvectl monitor --json=short
-# Check if all the zones are valid (zone-check always returns 0, so let's check
-# if it produces any errors/warnings)
-run knotc zone-check
+# FIXME: knot, unfortunately, incorrectly complains about missing zone files for zones
+# that are forwarded using the `dnsproxy` module. Until the issue is resolved,
+# let's fall back to pre-processing the `zone-check` output a bit before checking it
+#
+# See: https://gitlab.nic.cz/knot/knot-dns/-/issues/913
+run knotc zone-check || :
+sed -i '/forwarded.test./d' "$RUN_OUT"
[[ ! -s "$RUN_OUT" ]]
# We need to manually propagate the DS records of onlinesign.test. to the parent
# zone, since they're generated online
run resolvectl openpgp mr.smith@signed.test
grep -qF "5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test" "$RUN_OUT"
grep -qF "authenticated: yes" "$RUN_OUT"
+# Check zone transfers (AXFR/IXFR)
+# Note: since resolved doesn't support zone transfers, let's just make sure it
+# simply refuses such requests without choking on them
+# See: https://github.com/systemd/systemd/pull/30809#issuecomment-1880102804
+run dig @ns1.unsigned.test AXFR signed.test
+grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
+run dig AXFR signed.test
+grep -qF "; Transfer failed" "$RUN_OUT"
+run dig @ns1.unsigned.test IXFR=43 signed.test
+grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
+run dig IXFR=43 signed.test
+grep -qF "; Transfer failed" "$RUN_OUT"
# DNSSEC validation with multiple records of the same type for the same name
# Issue: https://github.com/systemd/systemd/issues/22002
#run dig +dnssec this.does.not.exist.untrusted.test
#grep -qF "status: NXDOMAIN" "$RUN_OUT"
+: "--- ZONE: forwarded.test (queries forwarded to our dummy test server) ---"
+JOURNAL_CURSOR="$(mktemp)"
+journalctl -n0 -q --cursor-file="$JOURNAL_CURSOR"
+
+# See "test-resolved-dummy-server.c" for the server part
+(! run resolvectl query nope.forwarded.test)
+grep -qF "nope.forwarded.test" "$RUN_OUT"
+grep -qF "not found" "$RUN_OUT"
+
+# SERVFAIL + EDE code 6: DNSSEC Bogus
+(! run resolvectl query edns-bogus-dnssec.forwarded.test)
+grep -qE "^edns-bogus-dnssec.forwarded.test:.+: upstream-failure \(DNSSEC Bogus\)" "$RUN_OUT"
+# Same thing, but over Varlink
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-bogus-dnssec.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSSECValidationFailed" "$RUN_OUT"
+grep -qF '{"result":"upstream-failure","extendedDNSErrorCode":6}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(DNSSEC Bogus\). Lookup failed."
+
+# SERVFAIL + EDE code 16: Censored + extra text
+(! run resolvectl query edns-extra-text.forwarded.test)
+grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!"}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Censored: Nothing to see here!\)"
+
+# SERVFAIL + EDE code 0: Other + extra text
+(! run resolvectl query edns-code-zero.forwarded.test)
+grep -qE "^edns-code-zero.forwarded.test:.+: SERVFAIL \(Other: 🐱\)" "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-code-zero.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱"}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Other: 🐱\)"
+
+# SERVFAIL + invalid EDE code
+(! run resolvectl query edns-invalid-code.forwarded.test)
+grep -qE "^edns-invalid-code.forwarded.test:.+: SERVFAIL \([0-9]+\)" "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+\)"
+
+# SERVFAIL + invalid EDE code + extra text
+(! run resolvectl query edns-invalid-code-with-extra-text.forwarded.test)
+grep -qE '^edns-invalid-code-with-extra-text.forwarded.test:.+: SERVFAIL \([0-9]+: Hello \[#\]\$%~ World\)' "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code-with-extra-text.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World"}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+: Hello \[\#\]\\$%~ World\)"
+
### Test resolvectl show-cache
run resolvectl show-cache
run resolvectl show-cache --json=short
Documentation=man:test
[Service]
-ExecStart=/bin/true
+ExecStart=true
fi
)}
+assert_le() {(
+ set +ex
+
+ if [[ "${1:?}" -gt "${2:?}" ]]; then
+ echo "FAIL: '$1' > '$2'" >&2
+ exit 1
+ fi
+)}
+
assert_in() {(
set +ex
pages = glob.glob(source_glob)
pages = (p for p in pages
if Path(p).name not in {
+ 'standard-conf.xml',
'systemd.directives.xml',
'systemd.index.xml',
'directives-template.xml'})
'conditions' : ['ENABLE_HOSTNAMED'],
'symlinks' : ['dbus-org.freedesktop.hostname1.service'],
},
+ {
+ 'file' : 'systemd-hostnamed.socket',
+ 'conditions' : ['ENABLE_HOSTNAMED'],
+ 'symlinks' : ['sockets.target.wants/'],
+ },
{
'file' : 'systemd-hwdb-update.service.in',
'conditions' : ['ENABLE_HWDB'],
[Socket]
ListenStream=/run/systemd/io.systemd.Credentials
FileDescriptorName=varlink
-SocketMode=0600
+SocketMode=0666
Accept=yes
[Install]
Documentation=man:org.freedesktop.hostname1(5)
[Service]
+Type=notify
BusName=org.freedesktop.hostname1
CapabilityBoundingSet=CAP_SYS_ADMIN
ExecStart={{LIBEXECDIR}}/systemd-hostnamed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
-PrivateDevices=yes
+DeviceAllow=/dev/vsock r
PrivateNetwork=yes
PrivateTmp=yes
ProtectProc=invisible
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Hostname Service Varlink Socket
+Documentation=man:systemd-hostnamed.service(8)
+Documentation=man:hostname(5)
+Documentation=man:machine-info(5)
+
+[Socket]
+ListenStream=/run/systemd/io.systemd.Hostname
+FileDescriptorName=varlink
+SocketMode=0666
Documentation=man:org.freedesktop.import1(5)
[Service]
+Type=notify
ExecStart={{LIBEXECDIR}}/systemd-importd
BusName=org.freedesktop.import1
KillMode=mixed
Documentation=man:org.freedesktop.locale1(5)
[Service]
+Type=notify
BusName=org.freedesktop.locale1
CapabilityBoundingSet=
ExecStart={{LIBEXECDIR}}/systemd-localed
DeviceAllow=char-tty rw
DeviceAllow=char-vcs rw
ExecStart={{LIBEXECDIR}}/systemd-logind
-FileDescriptorStoreMax=512
+FileDescriptorStoreMax=768
IPAddressDeny=any
LockPersonality=yes
MemoryDenyWriteExecute=yes
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-network-generator
+ImportCredential=network.netdev.*
+ImportCredential=network.link.*
+ImportCredential=network.network.*
[Install]
WantedBy=sysinit.target
SystemCallFilter=@system-service
Type=notify-reload
User=systemd-network
+ImportCredential=network.wireguard.*
{{SERVICE_WATCHDOG}}
[Install]
Documentation=man:org.freedesktop.timedate1(5)
[Service]
+Type=notify
BusName=org.freedesktop.timedate1
CapabilityBoundingSet=CAP_SYS_TIME
DeviceAllow=char-rtc r