]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #31044 from keszybz/uhttpd-alloca-print
authorLuca Boccassi <bluca@debian.org>
Mon, 22 Jan 2024 22:03:08 +0000 (22:03 +0000)
committerGitHub <noreply@github.com>
Mon, 22 Jan 2024 22:03:08 +0000 (22:03 +0000)
Use macro wrapper instead of alloca in µhttp-utils

256 files changed:
.github/labeler.yml
.github/workflows/unit_tests.sh
catalog/systemd.catalog.in
docs/ENVIRONMENT.md
docs/USER_RECORD.md
factory/etc/pam.d/system-auth
hwdb.d/60-sensor.hwdb
man/busctl.xml
man/loginctl.xml
man/org.freedesktop.login1.xml
man/org.freedesktop.network1.xml
man/org.freedesktop.portable1.xml
man/org.freedesktop.systemd1.xml
man/pam_systemd.xml
man/pam_systemd_home.xml
man/standard-options.xml
man/systemd-path.xml
man/systemd-sleep.conf.xml
man/systemd-vmspawn.xml
man/systemd.network.xml
man/systemd.resource-control.xml
meson.build
mime/io.systemd.xml
mkosi.images/system/mkosi.postinst.chroot
modprobe.d/systemd.conf
network/80-6rd-tunnel.link
po/id.po
po/pa.po
rules.d/60-persistent-storage.rules.in
shell-completion/bash/loginctl
shell-completion/bash/systemd-cryptenroll
shell-completion/bash/systemd-dissect
src/basic/alloc-util.h
src/basic/escape.c
src/basic/escape.h
src/basic/fd-util.h
src/basic/macro.h
src/basic/namespace-util.c
src/basic/namespace-util.h
src/basic/path-lookup.c
src/basic/process-util.c
src/basic/process-util.h
src/basic/socket-util.h
src/basic/strv.c
src/basic/strv.h
src/basic/sysctl-util.c
src/basic/sysctl-util.h
src/basic/terminal-util.c
src/basic/terminal-util.h
src/basic/time-util.c
src/basic/user-util.c
src/basic/user-util.h
src/basic/virt.c
src/boot/bootctl-install.c
src/boot/bootctl-util.c
src/boot/bootctl-util.h
src/boot/efi/devicetree.c
src/boot/efi/util.h
src/boot/measure.c
src/cgtop/cgtop.c
src/core/cgroup.c
src/core/cgroup.h
src/core/core-varlink.c
src/core/dbus-execute.c
src/core/dbus-unit.c
src/core/dbus.c
src/core/dynamic-user.c
src/core/exec-credential.c
src/core/exec-invoke.c
src/core/manager-serialize.c
src/core/manager.c
src/core/manager.h
src/core/mount.c
src/core/path.c
src/core/service.c
src/core/swap.c
src/core/unit.c
src/coredump/coredumpctl.c
src/creds/creds.c
src/cryptenroll/cryptenroll.c
src/dissect/dissect.c
src/environment-d-generator/environment-d-generator.c
src/firstboot/firstboot.c
src/home/homed-manager-bus.c
src/home/homed-manager.c
src/home/homework-directory.c
src/home/homework-luks.c
src/home/homework.c
src/home/homework.h
src/journal/journalctl.c
src/kernel-install/kernel-install.c
src/libsystemd-network/ndisc-router.c
src/libsystemd-network/ndisc-router.h
src/libsystemd-network/sd-dhcp-client.c
src/libsystemd-network/test-ndisc-rs.c
src/libsystemd/sd-bus/bus-container.c
src/libsystemd/sd-bus/bus-message.c
src/libsystemd/sd-bus/bus-socket.c
src/libsystemd/sd-device/device-private.h
src/libsystemd/sd-device/sd-device.c
src/libsystemd/sd-event/event-util.c
src/libsystemd/sd-event/event-util.h
src/libsystemd/sd-login/sd-login.c
src/libsystemd/sd-netlink/netlink-message-rtnl.c
src/libsystemd/sd-netlink/netlink-types-genl.c
src/libsystemd/sd-netlink/netlink-types-rtnl.c
src/libsystemd/sd-netlink/netlink-util.c
src/libsystemd/sd-netlink/netlink-util.h
src/libsystemd/sd-netlink/test-netlink.c
src/libsystemd/sd-network/network-util.c
src/libsystemd/sd-network/network-util.h
src/login/loginctl.c
src/login/logind-action.c
src/login/logind-action.h
src/login/logind-dbus.c
src/login/logind-user.c
src/login/org.freedesktop.login1.conf
src/login/pam_systemd.c
src/machine/machine-dbus.c
src/network/netdev/tuntap.c
src/network/netdev/wireguard.c
src/network/networkctl.c
src/network/networkd-address.c
src/network/networkd-dhcp-prefix-delegation.c
src/network/networkd-dhcp4.c
src/network/networkd-json.c
src/network/networkd-link.c
src/network/networkd-link.h
src/network/networkd-manager-bus.c
src/network/networkd-manager-varlink.c
src/network/networkd-manager.c
src/network/networkd-manager.h
src/network/networkd-ndisc.c
src/network/networkd-neighbor.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/network/networkd-nexthop.c
src/network/networkd-nexthop.h
src/network/networkd-queue.c
src/network/networkd-queue.h
src/network/networkd-route-metric.c
src/network/networkd-route-nexthop.c
src/network/networkd-route-nexthop.h
src/network/networkd-route-util.c
src/network/networkd-route-util.h
src/network/networkd-route.c
src/network/networkd-route.h
src/network/networkd-routing-policy-rule.c
src/network/networkd-routing-policy-rule.h
src/network/networkd-state-file.c
src/network/networkd-sysctl.c
src/network/wait-online/manager.c
src/network/wait-online/wait-online.c
src/nspawn/nspawn-bind-user.c
src/nspawn/nspawn-mount.c
src/nspawn/nspawn-network.c
src/nspawn/nspawn-network.h
src/nspawn/nspawn-oci.c
src/nspawn/nspawn.c
src/nss-resolve/nss-resolve.c
src/pcrlock/pcrlock.c
src/portable/portable.c
src/resolve/resolvectl.c
src/resolve/resolved-dns-packet.c
src/resolve/resolved-dns-packet.h
src/resolve/resolved-dns-rr.c
src/resolve/resolved-dns-rr.h
src/resolve/resolved-dns-transaction.c
src/run/run.c
src/shared/battery-util.c
src/shared/boot-entry.c
src/shared/boot-entry.h
src/shared/bpf-program.c
src/shared/bus-polkit.c
src/shared/conf-parser.c
src/shared/creds-util.c
src/shared/dissect-image.c
src/shared/dissect-image.h
src/shared/group-record.c
src/shared/hibernate-util.c
src/shared/json.c
src/shared/json.h
src/shared/logs-show.c
src/shared/machine-id-setup.c
src/shared/mkfs-util.c
src/shared/module-util.c
src/shared/mount-util.c
src/shared/ptyfwd.c
src/shared/socket-netlink.c
src/shared/socket-netlink.h
src/shared/tpm2-util.c
src/shared/udev-util.c
src/shared/udev-util.h
src/shared/user-record-nss.c
src/shared/user-record.c
src/shared/user-record.h
src/shared/varlink-idl.c
src/shared/varlink-io.systemd.Network.c
src/shared/varlink-io.systemd.Resolve.Monitor.c
src/shared/varlink.c
src/shared/varlink.h
src/ssh-generator/meson.build
src/ssh-generator/ssh-generator.c
src/systemctl/systemctl-is-system-running.c
src/systemctl/systemctl-show.c
src/systemd/sd-messages.h
src/systemd/sd-ndisc.h
src/systemd/sd-netlink.h
src/sysusers/sysusers.c
src/test/test-alloc-util.c
src/test/test-creds.c
src/test/test-escape.c
src/test/test-json.c
src/test/test-socket-netlink.c
src/test/test-strv.c
src/test/test-tpm2.c
src/test/test-varlink-idl.c
src/test/test-varlink.c
src/tmpfiles/tmpfiles.c
src/udev/ata_id/ata_id.c
src/ukify/ukify.py
src/userdb/meson.build
src/varlinkctl/varlinkctl.c
src/vmspawn/vmspawn-util.c
src/vmspawn/vmspawn-util.h
src/vmspawn/vmspawn.c
test/TEST-13-NSPAWN/test.sh
test/fuzz/fuzz-unit-file/directives-all.service
test/knot-data/zones/test.zone
test/test-network/conf/25-agent-bridge-port.network [new file with mode: 0644]
test/test-network/conf/25-agent-bridge.netdev [new file with mode: 0644]
test/test-network/conf/25-agent-bridge.network [new file with mode: 0644]
test/test-network/conf/25-dummy.netdev [new file with mode: 0644]
test/test-network/conf/25-dummy.network [new file with mode: 0644]
test/test-network/conf/25-fibrule-l3mdev.network [new file with mode: 0644]
test/test-network/conf/25-ipv6-neigh-retrans-time-0s.network [new file with mode: 0644]
test/test-network/conf/25-ipv6-neigh-retrans-time-3s.network [new file with mode: 0644]
test/test-network/conf/25-ipv6-neigh-retrans-time-4s.network [new file with mode: 0644]
test/test-network/conf/25-ipv6-neigh-retrans-time-infinity.network [new file with mode: 0644]
test/test-network/conf/25-ipv6-neigh-retrans-time-invalid.network [new file with mode: 0644]
test/test-network/conf/25-ipv6-neigh-retrans-time-toobig.network [new file with mode: 0644]
test/test-network/conf/25-nexthop-test1.network [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py
test/testsuite-63.units/test63-pr-30768.path [new file with mode: 0644]
test/testsuite-63.units/test63-pr-30768.service [new file with mode: 0644]
test/units/testsuite-07.type-exec-parallel.sh [new file with mode: 0755]
test/units/testsuite-13.nspawn.sh
test/units/testsuite-35.sh
test/units/testsuite-43.sh
test/units/testsuite-63.sh
test/units/testsuite-74.ssh.sh
test/units/testsuite-75.sh
units/systemd-hibernate.service.in
units/systemd-hybrid-sleep.service.in
units/systemd-suspend-then-hibernate.service.in

index 45b1ff49eebab0cf8e3a8833897b2d7642e00ae7..f74d680ce9d2920e960773a6a88bf77a9431568b 100644 (file)
@@ -7,6 +7,9 @@ apparmor:
 binfmt:
   - changed-files:
     - any-glob-to-any-file: '**/*binfmt*'
+bsod:
+  - changed-files:
+    - any-glob-to-any-file: '**/*bsod*'
 btrfs:
   - changed-files:
     - any-glob-to-any-file: '**/*btrfs*'
@@ -46,6 +49,9 @@ fstab-generator:
 growfs:
   - changed-files:
     - any-glob-to-any-file: '**/*growfs*'
+hibernate-resume:
+  - changed-files:
+    - any-glob-to-any-file: '**/*hibernate-resume*'
 hwdb:
   - changed-files:
     - any-glob-to-any-file: 'hwdb.d/**/*'
@@ -70,6 +76,9 @@ mkosi:
 network:
   - changed-files:
     - any-glob-to-any-file: ['src/libsystemd-network/**/*', 'src/network/**/*']
+nspawn:
+  - changed-files:
+    - any-glob-to-any-file: '**/*nspawn*'
 portable:
   - changed-files:
     - any-glob-to-any-file: 'src/portable/**/*'
@@ -184,6 +193,9 @@ util-lib:
 vconsole:
   - changed-files:
     - any-glob-to-any-file: '**/*vconsole*'
+vmspawn:
+  - changed-files:
+    - any-glob-to-any-file: '**/*vmspawn*'
 xdg-autostart:
   - changed-files:
     - any-glob-to-any-file: '**/**xdg-autostart-generator*'
index a5b98e089be106abe88b1341247d700e2b035fe3..dc5bf5dd4fcb62bf32f587757fe7ada9a0d3fc30 100755 (executable)
@@ -22,6 +22,7 @@ ADDITIONAL_DEPS=(
     python3-pefile
     python3-pyelftools
     python3-pyparsing
+    python3-pytest
     rpm
     zstd
 )
index 8591a654aa5b6ebac7aa1181437355cff3a2dde3..04e90e0b75fdbddd26b78f069dd4efb7f9024d80 100644 (file)
@@ -748,3 +748,23 @@ Compatibility support for System V services in systemd is deprecated. Please
 make sure to update the package in question to provide proper, native systemd
 unit files. Contact vendor if necessary. Compatibility support for System V
 services is deprecated and will be removed soon.
+
+-- 187c62eb1e7f463bb530394f52cb090f
+Subject: A Portable Service has been attached
+Defined-By: systemd
+Support: %SUPPORT_URL%
+Documentation: https://systemd.io/PORTABLE_SERVICES/
+
+A new Portable Service @PORTABLE_ROOT@ (with extensions: @PORTABLE_EXTENSION@) has
+been attached to the system and is now available for use. The list of attached
+Portable Services can be queried with 'portablectl list'.
+
+-- 76c5c754d628490d8ecba4c9d042112b
+Subject: A Portable Service has been detached
+Defined-By: systemd
+Support: %SUPPORT_URL%
+Documentation: https://systemd.io/PORTABLE_SERVICES/
+
+A Portable Service @PORTABLE_ROOT@ (with extensions: @PORTABLE_EXTENSION@) has been
+detached from the system and is no longer available for use. The list of attached
+Portable Services can be queried with 'portablectl list'.
index c96e6db85eaaf551db46bb0ae002c41855c20f0c..454a02991d948e5167576ee3694643a6647c0a14 100644 (file)
@@ -616,3 +616,8 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as
 
 * `$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.
+
+* `$SYSTEMD_VARLINK_LISTEN` – interpreted by some tools that provide a Varlink
+  service. Takes a file system path: if specified the tool will listen on an
+  `AF_UNIX` stream socket on the specified path in addition to whatever else it
+  would listen on.
index d5d1566267fe351fecef7e8c12441a32d629eb7a..60f75bf39d19ff0a9cab2fa471a5022c70d498ad 100644 (file)
@@ -736,7 +736,7 @@ specified hostnames match the system's local hostname, the fields in this
 object are honored. If both `matchHostname` and `matchMachineId` are used
 within the same array entry, the object is honored when either match succeeds,
 i.e. the two match types are combined in OR, not in AND. (As a special case, if
-only a single machine ID is listed this field may be a single string rather
+only a single hostname is listed this field may be a single string rather
 than an array of strings.)
 
 These two are the only two fields specific to this section. All other fields
index c2d6240930f04ec303bf34fa82010da0392804f4..cb4e570361682369e4c9cf76d0affdafa8a2f302 100644 (file)
@@ -13,7 +13,7 @@ account   sufficient pam_unix.so
 account   required   pam_permit.so
 
 -password sufficient pam_systemd_home.so
-password  sufficient pam_unix.so sha512 shadow try_first_pass use_authtok
+password  sufficient pam_unix.so sha512 shadow try_first_pass
 password  required   pam_deny.so
 
 -session  optional   pam_keyinit.so revoke
index 0e39a6a5663b1de024cb2a369412b0761b55472f..b002be4ece902e98c18963d69a0c32a1d24899fe 100644 (file)
@@ -309,6 +309,10 @@ sensor:modalias:acpi:BOSC0200*:dmi:bvnAmericanMegatrendsInc.:bvrY13D_KB133.103:b
 sensor:modalias:acpi:BOSC0200*:dmi:*:svnHampoo*:pnC3W6_AP108_4GB:*
  ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1
 
+# Chuwi Ubook X (CWI535)
+sensor:modalias:acpi:MXC6655*:dmi*:svnCHUWIInnovationAndTechnology*:pnUBookX:*
+ ACCEL_MOUNT_MATRIX=0, 0, -1; 1, 0, 0; 0, 1, 0
+
 #########################################
 # Connect
 #########################################
index 74f5cf3edbd2f946824b67ffade5c75bb47d05e5..1add61728bb4ac5ddb03bbd686b42347588c8450 100644 (file)
@@ -44,7 +44,7 @@
 
     <variablelist>
       <varlistentry>
-        <term><cmdsynopsis><command>list</command></cmdsynopsis></term>
+        <term><command>list</command></term>
 
         <listitem><para>Show all peers on the bus, by their service
         names. By default, shows both unique and well-known names, but
@@ -56,7 +56,7 @@
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>status</command> <arg choice="opt"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+        <term><command>status</command> <arg choice="opt"><replaceable>SERVICE</replaceable></arg></term>
 
         <listitem><para>Show process information and credentials of a
         bus service (if one is specified by its unique or well-known
@@ -68,7 +68,7 @@
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>monitor</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+        <term><command>monitor</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
 
         <listitem><para>Dump messages being exchanged. If
         <replaceable>SERVICE</replaceable> is specified, show messages
@@ -81,7 +81,7 @@
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>capture</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+        <term><command>capture</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
 
         <listitem><para>Similar to <command>monitor</command> but
         writes the output in pcapng format (for details, see
@@ -96,7 +96,7 @@
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>tree</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+        <term><command>tree</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
 
         <listitem><para>Shows an object tree of one or more
         services. If <replaceable>SERVICE</replaceable> is specified,
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>introspect</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="opt"><replaceable>INTERFACE</replaceable></arg></cmdsynopsis></term>
+        <term><command>introspect</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="opt"><replaceable>INTERFACE</replaceable></arg></term>
 
         <listitem><para>Show interfaces, methods, properties and
         signals of the specified object (identified by its path) on
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>call</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>METHOD</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></cmdsynopsis></term>
+        <term><command>call</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>METHOD</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></term>
 
         <listitem><para>Invoke a method and show the response. Takes a
         service name, object path, interface name and method name. If
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>emit</command> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>SIGNAL</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></cmdsynopsis></term>
+        <term><command>emit</command> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>SIGNAL</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></term>
 
         <listitem><para>Emit a signal. Takes an object path, interface name and method name. If parameters
         shall be passed, a signature string is required, followed by the arguments, individually formatted as
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>get-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>PROPERTY</replaceable></arg></cmdsynopsis></term>
+        <term><command>get-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>PROPERTY</replaceable></arg></term>
 
         <listitem><para>Retrieve the current value of one or more
         object properties. Takes a service name, object path,
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>set-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>PROPERTY</replaceable></arg> <arg choice="plain"><replaceable>SIGNATURE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></cmdsynopsis></term>
+        <term><command>set-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>PROPERTY</replaceable></arg> <arg choice="plain"><replaceable>SIGNATURE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></term>
 
         <listitem><para>Set the current value of an object
         property. Takes a service name, object path, interface name,
       </varlistentry>
 
       <varlistentry>
-        <term><cmdsynopsis><command>help</command></cmdsynopsis></term>
+        <term><command>help</command></term>
 
         <listitem><para>Show command syntax help.</para>
 
index 8a093c6e2982d71dd4d474973242904a8adeced6..099de1dfe5e961fa8587a561186832bda0d2f924 100644 (file)
@@ -50,7 +50,8 @@
       <varlistentry>
         <term><command>list-sessions</command></term>
 
-        <listitem><para>List current sessions.</para></listitem>
+        <listitem><para>List current sessions. The JSON format output can be toggled using <option>--json=</option>
+        or <option>-j</option> option.</para></listitem>
       </varlistentry>
 
       <varlistentry>
       <varlistentry>
         <term><command>list-users</command></term>
 
-        <listitem><para>List currently logged in users.
-        </para></listitem>
+        <listitem><para>List currently logged in users. The JSON format output can be toggled using
+        <option>--json=</option> or <option>-j</option> option.</para></listitem>
       </varlistentry>
 
       <varlistentry>
       <varlistentry>
         <term><command>list-seats</command></term>
 
-        <listitem><para>List currently available seats on the local
-        system.</para></listitem>
+        <listitem><para>List currently available seats on the local system. The JSON format output can be
+        toggled using <option>--json=</option> or <option>-j</option> option.</para></listitem>
       </varlistentry>
 
       <varlistentry>
 
       <xi:include href="standard-options.xml" xpointer="no-pager" />
       <xi:include href="standard-options.xml" xpointer="no-legend" />
+      <xi:include href="standard-options.xml" xpointer="json" />
+      <xi:include href="standard-options.xml" xpointer="j" />
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
     </variablelist>
index f647b1cb5b08babbc8b88dc9b6010c8e7a52ffdd..9151926e64d5c1efb8300ec1912d42222f008ea8 100644 (file)
@@ -53,6 +53,7 @@ node /org/freedesktop/login1 {
       GetSeat(in  s seat_id,
               out o object_path);
       ListSessions(out a(susso) sessions);
+      ListSessionsEx(out a(sussussbto) sessions);
       ListUsers(out a(uso) users);
       ListSeats(out a(so) seats);
       ListInhibitors(out a(ssssuu) inhibitors);
@@ -315,6 +316,8 @@ node /org/freedesktop/login1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="ListSessions()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="ListSessionsEx()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="ListUsers()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="ListSeats()"/>
@@ -549,8 +552,17 @@ node /org/freedesktop/login1 {
       is any.</para>
 
       <para><function>ListSessions()</function> returns an array of all current sessions. The structures in
-      the array consist of the following fields: session id, user id, user name, seat id, session object
-      path. If a session does not have a seat attached, the seat id field will be an empty string.</para>
+      the array consist of the following fields: <varname>session id</varname>, <varname>user id</varname>,
+      <varname>user name</varname>, <varname>seat id</varname>, and <varname>session object path</varname>.
+      If a session does not have a seat attached, the seat id field will be an empty string.</para>
+
+      <para><function>ListSessionsEx()</function> returns an array of all current sessions with more metadata
+      than <function>ListSessions()</function>. The structures in the array consist of the following fields:
+      <varname>session id</varname>, <varname>user id</varname>, <varname>user name</varname>,
+      <varname>seat id</varname>, <varname>leader pid</varname>, <varname>session class</varname>,
+      <varname>tty name</varname>, <varname>idle hint</varname>, <varname>idle hint monotonic timestamp</varname>,
+      and <varname>session object path</varname>. <varname>tty</varname> and <varname>seat id</varname> fields
+      could be empty, if the session has no associated tty or session has no seat attached, respectively.</para>
 
       <para><function>ListUsers()</function> returns an array of all currently logged in users. The
       structures in the array consist of the following fields: user id, user name, user object path.</para>
@@ -1559,8 +1571,9 @@ node /org/freedesktop/login1/session/1 {
       <para><function>PrepareForShutdownWithMetadata</function> and
       <function>CreateSessionWithPIDFD()</function> were added in version 255.</para>
       <para><function>Sleep()</function>,
-      <function>CanSleep()</function>, and
-      <varname>SleepOperation</varname> were added in version 256.</para>
+      <function>CanSleep()</function>,
+      <varname>SleepOperation</varname>, and
+      <function>ListSessionsEx()</function> were added in version 256.</para>
     </refsect2>
     <refsect2>
       <title>Session Objects</title>
index c6cadee177f134fe056ef6822f01e38b53862297..1d8ce0de8114e8888322fad0f6e00091351afcff 100644 (file)
@@ -87,6 +87,8 @@ node /org/freedesktop/network1 {
       readonly s OnlineState = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t NamespaceId = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+      readonly u NamespaceNSID = ...;
   };
   interface org.freedesktop.DBus.Peer { ... };
   interface org.freedesktop.DBus.Introspectable { ... };
@@ -148,8 +150,6 @@ node /org/freedesktop/network1 {
 
     <!--property OnlineState is not documented!-->
 
-    <!--property NamespaceId is not documented!-->
-
     <!--Autogenerated cross-references for systemd.directives, do not edit-->
 
     <variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.network1.Manager"/>
@@ -212,11 +212,24 @@ node /org/freedesktop/network1 {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NamespaceId"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="NamespaceNSID"/>
+
     <!--End of Autogenerated section-->
 
     <para>
       Provides information about the manager.
     </para>
+
+    <refsect2>
+      <title>Properties</title>
+
+      <para><varname>NamespaceId</varname> contains the inode number of the network namespace that the
+      network service runs in. A client may compare this with the inode number of its own network namespace
+      to verify whether the service manages the same network namespace.</para>
+
+      <para><varname>NamespaceNSID</varname> contains the "nsid" identifier the kernel maintains for the
+      network namespace, if there's one assigned.</para>
+    </refsect2>
   </refsect1>
 
   <refsect1>
@@ -584,5 +597,9 @@ $ gdbus introspect --system \
       <title>DHCPv6 Client Object</title>
       <para><varname>State</varname> was added in version 255.</para>
     </refsect2>
+    <refsect2>
+      <title>Manager Object</title>
+      <para><varname>NamespaceNSID</varname> was added in version 256.</para>
+    </refsect2>
   </refsect1>
 </refentry>
index f3c5597442abcceff23ed8f858cc86be7eeca6e5..a41da4f5c3cd20a7da3f5c6da1b0e0776f2b11de 100644 (file)
@@ -249,7 +249,8 @@ node /org/freedesktop/portable1 {
         <listitem><para>mkdir</para></listitem>
       </itemizedlist>
       Note that an image cannot be attached if a unit that it contains is already present
-      on the system.</para>
+      on the system. Note that this method returns only after all the listed operations are completed,
+      and due to the I/O involved it might take some time.</para>
 
       <para><function>AttachImageWithExtensions()</function> attaches a portable image to the system.
       This method is a superset of <function>AttachImage()</function> with the addition of
@@ -271,7 +272,9 @@ node /org/freedesktop/portable1 {
       <itemizedlist>
         <listitem><para>unlink</para></listitem>
       </itemizedlist>
-      Note that an image cannot be detached if a unit that it contains is running.</para>
+      Note that an image cannot be detached if a unit that it contains is running. Note that this method
+      returns only after all the listed operations are completed, and due to the I/O involved it might take
+      some time.</para>
 
       <para><function>DetachImageWithExtensions()</function> detaches a portable image from the system.
       This method is a superset of <function>DetachImage()</function> with the addition of
@@ -307,11 +310,11 @@ node /org/freedesktop/portable1 {
       <para>The <function>AttachImageWithExtensions()</function>,
       <function>DetachImageWithExtensions()</function> and
       <function>ReattachImageWithExtensions()</function> methods take in options as flags instead of
-      booleans to allow for extendability. <varname>SD_SYSTEMD_PORTABLE_FORCE_ATTACH</varname> will cause
-      safety checks that ensure the units are not running while the new image is attached or detached
-      to be skipped. <varname>SD_SYSTEMD_PORTABLE_FORCE_EXTENSION</varname> will cause the check that the
+      booleans to allow for extendability. <varname>SD_SYSTEMD_PORTABLE_FORCE_ATTACH</varname> will bypass
+      the safety checks that ensure the units are not running while the image is attached or detached.
+      <varname>SD_SYSTEMD_PORTABLE_FORCE_EXTENSION</varname> will bypass the check that ensures the
       <filename>extension-release.<replaceable>NAME</replaceable></filename> file in the extension image
-      matches the image name to be skipped. They are defined as follows:</para>
+      matches the image name. They are defined as follows:</para>
 
       <programlisting>
 #define SD_SYSTEMD_PORTABLE_RUNTIME            (UINT64_C(1) &lt;&lt; 0)
index e2814b7e42e185e602c66ef96ea5c9692444f921..5f1a5988a0cb6795d2c003cbe8ac83a5f6165af7 100644 (file)
@@ -1291,7 +1291,13 @@ node /org/freedesktop/systemd1 {
       and terminate all units that aren't dependencies of it. If <literal>ignore-dependencies</literal>, it
       will start a unit but ignore all its dependencies. If <literal>ignore-requirements</literal>, it will
       start a unit but only ignore the requirement dependencies. It is not recommended to make use of the
-      latter two options. On completion, this method returns the newly created job object.</para>
+      latter two options. On reply, if successful, this method returns the newly created job object
+      which has been enqueued for asynchronous activation. Callers that want to track the outcome of the
+      actual start operation need to monitor the result of this job. This can be achieved in a race-free
+      manner by first subscribing to the <function>JobRemoved()</function> signal, then calling
+      <function>StartUnit()</function> and using the returned job object to filter out unrelated
+      <function>JobRemoved()</function> signals, until the desired one is received, which will then carry
+      the result of the start operation.</para>
 
       <para><function>StartUnitReplace()</function> is similar to <function>StartUnit()</function> but
       replaces a job that is queued for one unit by a job for another unit.</para>
index 2c3bbec5d8147531ed39e5c1bde2abb930802ac8..1a841df13bf19441a0ce0a9908a11080f1d32f18 100644 (file)
@@ -411,8 +411,7 @@ account   sufficient pam_unix.so
 account   required   pam_permit.so
 
 -password sufficient pam_systemd_home.so
-password  sufficient pam_unix.so sha512 shadow try_first_pass use_authtok
-
+password  sufficient pam_unix.so sha512 shadow try_first_pass
 password  required   pam_deny.so
 
 -session  optional   pam_keyinit.so revoke
index 10ac7d9838ec50216baa1c2fabfea907c70e0c78..5bd48de4a60284dc209f7339715e6d3dd003990e 100644 (file)
@@ -158,7 +158,7 @@ account   sufficient pam_unix.so
 account   required   pam_permit.so
 
 <command>-password sufficient pam_systemd_home.so</command>
-password  sufficient pam_unix.so sha512 shadow try_first_pass use_authtok
+password  sufficient pam_unix.so sha512 shadow try_first_pass
 password  required   pam_deny.so
 
 -session  optional   pam_keyinit.so revoke
index 28131348286b119daf26f39693a89c90d5c1b334..87058ad657b52203bfd914b6234cd2310f12f406 100644 (file)
@@ -3,9 +3,7 @@
   "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
 <!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
 
-<variablelist
-    xmlns:xi="http://www.w3.org/2001/XInclude">
-
+<variablelist>
   <varlistentry id='help'>
     <term><option>-h</option></term>
     <term><option>--help</option></term>
     </listitem>
   </varlistentry>
 
-  <varlistentry id='no-pager-255'>
-    <term><option>--no-pager</option></term>
-
-    <listitem>
-      <para>Do not pipe output into a pager.</para>
-
-      <xi:include href="version-info.xml" xpointer="v255"/>
-    </listitem>
-  </varlistentry>
-
   <varlistentry id='no-ask-password'>
     <term><option>--no-ask-password</option></term>
 
index d5a14bc1719c0e3ed42487ab33297696a6649655..153a9bd0d561356d15096aa343ba59c497eea58f 100644 (file)
@@ -59,7 +59,7 @@
         <xi:include href="version-info.xml" xpointer="v215"/></listitem>
       </varlistentry>
 
-      <xi:include href="standard-options.xml" xpointer="no-pager-255"/>
+      <xi:include href="standard-options.xml" xpointer="no-pager"/>
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
     </variablelist>
index f7e753990a0ac28ce0a7fb2363fc75c8f257887c..1abec4f34fdfcf00be851f663efaee764d97520b 100644 (file)
         <term>suspend-then-hibernate</term>
 
         <listitem>
-          <para>A low power state where the system is initially suspended (the state is stored in
-          RAM). If the system supports low-battery alarms (ACPI _BTP), then the system will be woken up by
-          the ACPI low-battery signal and hibernated (the state is then stored on disk). Also, if not
-          interrupted within the timespan specified by <varname>HibernateDelaySec=</varname> or the estimated
-          timespan until the system battery charge level goes down to 5%, then the system will be woken up by the
-          RTC alarm and hibernated. The estimated timespan is calculated from the change of the battery
-          capacity level after the time specified by <varname>SuspendEstimationSec=</varname> or when
-          the system is woken up from the suspend.</para>
+          <para>A low power state where the system is initially suspended (the state is stored in RAM).
+          When the battery level is too low (less than 5%) or a certain timespan has passed, whichever
+          happens first, the system is automatically woken up and then hibernated. This establishes a balance
+          between speed and safety.</para>
+
+          <para>If the system has no battery, it would be hibernated after <varname>HibernateDelaySec=</varname>
+          has passed. If not set, then defaults to <literal>2h</literal>.</para>
+
+          <para>If the system has battery and <varname>HibernateDelaySec=</varname> is not set, low-battery
+          alarms (ACPI _BTP) are tried first for detecting battery percentage and wake up the system for hibernation.
+          If not available, or <varname>HibernateDelaySec=</varname> is set, the system would regularly wake
+          up to check the time and detect the battery percentage/discharging rate. The rate is used to
+          schedule the next detection. If that is also not available, <varname>SuspendEstimationSec=</varname>
+          is used as last resort.</para>
 
           <xi:include href="version-info.xml" xpointer="v239"/>
         </listitem>
 
         <para>The allowed set of values is determined by the kernel and is shown in the file itself (use
         <command>cat /sys/power/disk</command> to display). See the kernel documentation page
-        <ulink url="https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
+        <ulink url="https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
           Basic sysfs Interfaces for System Suspend and Hibernation</ulink> for more details.</para>
 
         <para>
 
         <para>The allowed set of values is determined by the kernel and is shown in the file itself (use
         <command>cat /sys/power/state</command> to display). See <ulink
-        url="https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
+        url="https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
           Basic sysfs Interfaces for System Suspend and Hibernation</ulink> for more details.</para>
 
         <para>
           <para>The amount of time the system spends in suspend mode before the system is
           automatically put into hibernate mode. Only used by
           <citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
-          If the system has a battery, then defaults to the estimated timespan until the system battery charge level goes down to 5%.
-          If the system has no battery, then defaults to 2h.</para>
+          Refer to <command>suspend-then-hibernate</command> for details on how this option interacts with
+          other options/system battery state.</para>
 
           <xi:include href="version-info.xml" xpointer="v239"/>
         </listitem>
 
         <listitem>
           <para>The RTC alarm will wake the system after the specified timespan to measure the system battery
-          capacity level and estimate battery discharging rate, which is used for estimating timespan until the system battery charge
-          level goes down to 5%. Only used by
+          capacity level and estimate battery discharging rate. Only used by
           <citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
-          Defaults to 1h.</para>
+          Refer to <command>suspend-then-hibernate</command> for details on how this option interacts with
+          other options/system battery state.</para>
 
           <xi:include href="version-info.xml" xpointer="v253"/></listitem>
       </varlistentry>
index cd46f823ad3e43b8848e28e66b0cc2a6d1c7e0a4..1bd459058a3388d4b83d0be57bc842563cdec9c2 100644 (file)
 
     <para>The following options are understood:</para>
 
+    <variablelist>
+
+      <varlistentry>
+        <term><option>-q</option></term>
+        <term><option>--quiet</option></term>
+
+        <listitem><para>Turns off any status output by the tool
+        itself. When this switch is used, the only output from vmspawn
+        will be the console output of the Virtual Machine OS itself.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+
+    </variablelist>
+
     <refsect2>
       <title>Image Options</title>
 
 
         <xi:include href="version-info.xml" xpointer="v255"/></listitem>
       </varlistentry>
-    </variablelist>
+
+      <varlistentry>
+        <term><option>--firmware=</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Takes an absolute path, or a relative path beginning with
+        <filename>./</filename>. Specifies a JSON firmware definition file, which allows selecting the
+        firmware to boot in the VM. If not specified a suitable firmware is automatically discovered. If the
+        special string <literal>list</literal> is specified lists all discovered firmwares.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+      </variablelist>
 
     </refsect2><refsect2>
       <title>System Identity Options</title>
index 76f9f4d042c584f33ad563eb3d9f90ad4fdeff17..c1b0eec1a72c784ed7fa3148e2f3e5b188d4f430 100644 (file)
       <varlistentry>
         <term><varname>RequiredForOnline=</varname></term>
         <listitem>
-          <para>Takes a boolean or a minimum operational state and an optional maximum operational
-          state. Please see
+          <para>Takes a boolean, a minimum operational state (e.g., <literal>carrier</literal>), or a range
+          of operational state separated with a colon (e.g., <literal>degraded:routable</literal>).
+          Please see
           <citerefentry><refentrytitle>networkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
           for possible operational states. When <literal>yes</literal>, the network is deemed required
           when determining whether the system is online (including when running
           minimum and maximum operational state required for the network interface to be considered
           online.</para>
 
+          <para>When <literal>yes</literal> is specified for a CAN device,
+          <command>systemd-networkd-wait-online</command> deems that the interface is online when its
+          operational state becomes <literal>carrier</literal>. For an interface with other type, e.g.
+          <literal>ether</literal>, the interface is deened online when its online state is
+          <literal>degraded</literal> or <literal>routable</literal>.</para>
+
           <para>Defaults to <literal>yes</literal> when <varname>ActivationPolicy=</varname> is not
           set, or set to <literal>up</literal>, <literal>always-up</literal>, or
           <literal>bound</literal>. Defaults to <literal>no</literal> when
@@ -875,6 +882,18 @@ Table=1234</programlisting></para>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>IPv6RetransmissionTimeSec=</varname></term>
+        <listitem>
+          <para>Configures IPv6 Retransmission Time. The time between retransmitted Neighbor
+          Solicitation messages. Used by address resolution and the Neighbor Unreachability
+          Detection algorithm. A value of zero is ignored and the kernel's current value
+          will be used. Defaults to unset, and the kernel's current value will be used.</para>
+
+          <xi:include href="version-info.xml" xpointer="v256"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>IPv4ReversePathFilter=</varname></term>
         <listitem>
@@ -1622,6 +1641,18 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>L3MasterDevice=</varname></term>
+        <listitem>
+          <para>A boolean. Specifies whether the rule is to direct lookups to the tables associated with
+          level 3 master devices (also known as Virtual Routing and Forwarding or VRF devices).
+          For further details see <ulink url="https://docs.kernel.org/networking/vrf.html">
+          Virtual Routing and Forwarding (VRF)</ulink>. Defaults to false.</para>
+
+          <xi:include href="version-info.xml" xpointer="v256"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>SourcePort=</varname></term>
         <listitem>
@@ -1944,7 +1975,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
           <command>ip route show table <replaceable>num</replaceable></command>. If unset and
           <varname>Type=</varname> is <literal>local</literal>, <literal>broadcast</literal>,
           <literal>anycast</literal>, or <literal>nat</literal>, then <literal>local</literal> is used.
-          In other cases, defaults to <literal>main</literal>.</para>
+          In other cases, defaults to <literal>main</literal>. Ignored if <varname>L3MasterDevice=</varname> is true.</para>
 
           <xi:include href="version-info.xml" xpointer="v230"/>
         </listitem>
@@ -3287,6 +3318,18 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>UseRetransmissionTime=</varname></term>
+        <listitem>
+          <para>Takes a boolean. When true, the retransmission time received in the Router Advertisement will be set
+          on the interface receiving the advertisement. It is used as the time between retransmissions of Neighbor
+          Solicitation messages to a neighbor when resolving the address or when probing the reachability of a neighbor.
+          Defaults to true.</para>
+
+          <xi:include href="version-info.xml" xpointer="v256"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>UseICMP6RateLimit=</varname></term>
         <listitem>
index 2eb6797f4c9d05d70bb6962f916fc73e145bcf2e..972e1d231627b53efa073b70744c636f5cf198fe 100644 (file)
@@ -487,7 +487,7 @@ CPUWeight=20   DisableControllers=cpu              /          \
           cache for swap pages. It takes pages that are in the process of being swapped out and attempts to compress them into a
           dynamically allocated RAM-based memory pool. If the limit specified is hit, no entries from this unit will be
           stored in the pool until existing entries are faulted back or written out to disk. See the kernel's
-          <ulink url="https://www.kernel.org/doc/html/latest/admin-guide/mm/zswap.html">Zswap</ulink> documentation for more details.</para>
+          <ulink url="https://docs.kernel.org/admin-guide/mm/zswap.html">Zswap</ulink> documentation for more details.</para>
 
           <para>Takes a size in bytes. If the value is suffixed with K, M, G or T, the specified size is
           parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the
@@ -566,7 +566,7 @@ CPUWeight=20   DisableControllers=cpu              /          \
           number of tasks on the system. If assigned the special value <literal>infinity</literal>, no tasks
           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 url="https://docs.kernel.org/admin-guide/cgroup-v2.html#pid">pids controller
           </ulink>.
           The effective configuration is reported as <varname>EffectiveTasksMax=</varname>.</para>
 
index 53a3d966ff4b30eb98fe53b25d6424a6a5aa786a..02a0e9b8f82d7b9e0eda69323b239da97e0cdd6b 100644 (file)
@@ -341,6 +341,7 @@ endif
 basic_disabled_warnings = [
         '-Wno-missing-field-initializers',
         '-Wno-unused-parameter',
+        '-Wno-nonnull-compare',
 ]
 
 possible_common_cc_flags = [
index 8b95cef9a53d7eccc2b96593820f7aba8a467070..f362006a478a215548971ea6006c835567a1417c 100644 (file)
     <comment>Configuration Extension DDI</comment>
     <glob pattern="*.confext.raw"/>
   </mime-type>
+  <mime-type type="application/x.systemd-journal">
+    <comment>Journal Log File</comment>
+    <magic>
+      <match type="string" value="LPKSHHRH" offset="0"/>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x.systemd-catalog">
+    <comment>Journal Message Catalog</comment>
+    <magic>
+      <match type="string" value="RHHHKSLP" offset="0"/>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x.systemd-hwdb">
+    <comment>Hardware Database</comment>
+    <magic>
+      <match type="string" value="KSLPHHRH" offset="0"/>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x.systemd-credential">
+    <comment>Encrypted Credential</comment>
+    <generic-icon name="security-high"/>
+    <magic>
+      <match type="string" value="Whxqht+dQJax1aZeCGLxm" offset="0"/>
+      <match type="string" value="DHzAexF2RZGcSwvqCLwg/" offset="0"/>
+      <match type="string" value="+vfrk0HjQSyhpDb5Wik2L" offset="0"/>
+      <match type="string" value="k6iUCUh0RJCQyvL8k8q1U" offset="0"/>
+      <match type="string" value="r0lQqEkTTrGnOEYwT/MMB" offset="0"/>
+      <match type="string" value="BYRp2vb1QySABUnaD46i+" offset="0"/>
+    </magic>
+  </mime-type>
 </mime-info>
index 0fec067ebb2a6c8d80c8f7d01d95b7c0c236e63a..692242da38fb370355d4cbdaea00446b3fe59be4 100755 (executable)
@@ -65,9 +65,17 @@ if [ -n "$IMAGE_VERSION" ] ; then
 fi
 
 if command -v authselect >/dev/null; then
-    authselect select minimal
+    # authselect 1.5.0 renamed the minimal profile to the local profile without keeping backwards compat so
+    # let's use the new name if it exists.
+    if [ -d /usr/share/authselect/default/local ]; then
+        PROFILE=local
+    else
+        PROFILE=minimal
+    fi
+
+    authselect select "$PROFILE"
 
-    if authselect list-features minimal | grep -q "with-homed"; then
+    if authselect list-features "$PROFILE" | grep -q "with-homed"; then
         authselect enable-feature with-homed
     fi
 fi
index 652254155c265c820ba0b3175e24a3cb7cd003c2..e6499a0d612aea4c15c792763d380b098617a433 100644 (file)
@@ -18,3 +18,7 @@ options bonding max_bonds=0
 # Do the same for dummy0.
 
 options dummy numdummies=0
+
+# Do the same for ifb0.
+
+options ifb numifbs=0
index a4f4103649e6aca0d952d3638b91ef4fd2485777..22fc3d6e28322d57696abbabf33aecccfc5e8ff8 100644 (file)
@@ -18,7 +18,7 @@
 Kind=sit
 OriginalName=6rd-*
 
-[Network]
+[Link]
 NamePolicy=keep
 MACAddressPolicy=persistent
 Property=ID_NET_MANAGED_BY=io.systemd.Network
index 6a65db1054eaeea45c841b9a8024fd8ecceb1abe..35dbdc268398fea45b69d509c40d0d6feebd53db 100644 (file)
--- a/po/id.po
+++ b/po/id.po
@@ -1,20 +1,21 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 #
 # Indonesian translation for systemd.
-# Andika Triwidada <andika@gmail.com>, 2014, 2021, 2022.
+# Andika Triwidada <andika@gmail.com>, 2014, 2021, 2022, 2024.
 msgid ""
 msgstr ""
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2023-11-14 21:25+0000\n"
-"PO-Revision-Date: 2022-11-25 08:19+0000\n"
+"PO-Revision-Date: 2024-01-21 10:36+0000\n"
 "Last-Translator: Andika Triwidada <andika@gmail.com>\n"
-"Language-Team: Indonesian <https://translate.fedoraproject.org/projects/systemd/master/id/>\n"
+"Language-Team: Indonesian <https://translate.fedoraproject.org/projects/"
+"systemd/master/id/>\n"
 "Language: id\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Weblate 4.14.2\n"
+"X-Generator: Weblate 5.3.1\n"
 
 #: src/core/org.freedesktop.systemd1.policy.in:22
 msgid "Send passphrase back to system"
@@ -58,12 +59,13 @@ msgstr "Otentikasi diperlukan untuk memuat ulang keadaan systemd."
 
 #: src/core/org.freedesktop.systemd1.policy.in:74
 msgid "Dump the systemd state without rate limits"
-msgstr ""
+msgstr "Curahkan keadaan systemd tanpa pembatasan laju"
 
 #: src/core/org.freedesktop.systemd1.policy.in:75
-#, fuzzy
 msgid "Authentication is required to dump the systemd state without rate limits."
-msgstr "Otentikasi diperlukan untuk memuat ulang keadaan systemd."
+msgstr ""
+"Otentikasi diperlukan untuk mencurahkan keadaan systemd tanpa pembatasan "
+"laju."
 
 #: src/home/org.freedesktop.home1.policy:13
 msgid "Create a home area"
@@ -117,146 +119,160 @@ msgstr "Otentikasi diperlukan untuk mengubah kata sandi dari suatu area rumah pe
 #, c-format
 msgid "Home of user %s is currently absent, please plug in the necessary storage device or backing file system."
 msgstr ""
+"Home dari pengguna %s saat ini tidak ada, harap tancapkan perangkat "
+"penyimpanan yang diperlukan atau sistem berkas pendukung."
 
 #: src/home/pam_systemd_home.c:292
 #, c-format
 msgid "Too frequent login attempts for user %s, try again later."
-msgstr ""
+msgstr "Percobaan log masuk terlalu sering bagi pengguna %s, coba lagi nanti."
 
 #: src/home/pam_systemd_home.c:304
 msgid "Password: "
-msgstr ""
+msgstr "Kata Sandi: "
 
 #: src/home/pam_systemd_home.c:306
 #, c-format
 msgid "Password incorrect or not sufficient for authentication of user %s."
-msgstr ""
+msgstr "Kata sandi salah atau tidak memadai bagi otentikasi dari pengguna %s."
 
 #: src/home/pam_systemd_home.c:307
 msgid "Sorry, try again: "
-msgstr ""
+msgstr "Maaf, coba lagi: "
 
 #: src/home/pam_systemd_home.c:329
 msgid "Recovery key: "
-msgstr ""
+msgstr "Kunci pemulihan: "
 
 #: src/home/pam_systemd_home.c:331
 #, c-format
 msgid "Password/recovery key incorrect or not sufficient for authentication of user %s."
 msgstr ""
+"Kata sandi/kunci pemulihan salah atau tidak memadai untuk otentikasi "
+"pengguna %s."
 
 #: src/home/pam_systemd_home.c:332
 msgid "Sorry, reenter recovery key: "
-msgstr ""
+msgstr "Maaf, masukkan lagi kunci pemulihan: "
 
 #: src/home/pam_systemd_home.c:352
 #, c-format
 msgid "Security token of user %s not inserted."
-msgstr ""
+msgstr "Token keamanan pengguna %s tidak ditancapkan."
 
 #: src/home/pam_systemd_home.c:353 src/home/pam_systemd_home.c:356
 msgid "Try again with password: "
-msgstr ""
+msgstr "Coba lagi dengan kata sandi: "
 
 #: src/home/pam_systemd_home.c:355
 #, c-format
 msgid "Password incorrect or not sufficient, and configured security token of user %s not inserted."
 msgstr ""
+"Kata sandi salah atau tidak memadai, dan token keamanan yang terkonfigurasi "
+"dari pengguna %s tidak ditancapkan."
 
 #: src/home/pam_systemd_home.c:376
 msgid "Security token PIN: "
-msgstr ""
+msgstr "PIN token keamanan: "
 
 #: src/home/pam_systemd_home.c:393
 #, c-format
 msgid "Please authenticate physically on security token of user %s."
-msgstr ""
+msgstr "Harap otentikasikan secara fisik pada token keamanan dari pengguna %s."
 
 #: src/home/pam_systemd_home.c:404
 #, c-format
 msgid "Please confirm presence on security token of user %s."
-msgstr ""
+msgstr "Harap konfirmasikan keberadaan pada token keamanan dari pengguna %s."
 
 #: src/home/pam_systemd_home.c:415
 #, c-format
 msgid "Please verify user on security token of user %s."
-msgstr ""
+msgstr "Harap verifikasikan pengguna pada token keamanan dari pengguna %s."
 
 #: src/home/pam_systemd_home.c:424
 msgid "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"
 msgstr ""
+"PIN token keamanan terkunci, harap buka dulu kuncinya. (Petunjuk: Mencabut "
+"dan menancapkan ulang mungkin sudah cukup)"
 
 #: src/home/pam_systemd_home.c:432
 #, c-format
 msgid "Security token PIN incorrect for user %s."
-msgstr ""
+msgstr "PIN token keamanan salah bagi pengguna %s."
 
 #: src/home/pam_systemd_home.c:433 src/home/pam_systemd_home.c:452
 #: src/home/pam_systemd_home.c:471
 msgid "Sorry, retry security token PIN: "
-msgstr ""
+msgstr "Maaf, coba lagi PIN token keamanan: "
 
 #: src/home/pam_systemd_home.c:451
 #, c-format
 msgid "Security token PIN of user %s incorrect (only a few tries left!)"
 msgstr ""
+"PIN token keamanan dari pengguna %s salah (hanya beberapa percobaan tersisa!)"
 
 #: src/home/pam_systemd_home.c:470
 #, c-format
 msgid "Security token PIN of user %s incorrect (only one try left!)"
 msgstr ""
+"PIN token keamanan dari pengguna %s salah (hanya satu percobaan tersisa!)"
 
 #: src/home/pam_systemd_home.c:616
 #, c-format
 msgid "Home of user %s is currently not active, please log in locally first."
 msgstr ""
+"Home dari pengguna %s saat ini tidak aktif, harap log masuk secara lokal "
+"terlebih dahulu."
 
 #: src/home/pam_systemd_home.c:618
 #, c-format
 msgid "Home of user %s is currently locked, please unlock locally first."
 msgstr ""
+"Home dari pengguna %s saat ini terkunci, harap buka kunci secara lokal "
+"terlebih dahulu."
 
 #: src/home/pam_systemd_home.c:645
 #, c-format
 msgid "Too many unsuccessful login attempts for user %s, refusing."
-msgstr ""
+msgstr "Terlalu banyak upaya log masuk yang gagal bagi pengguna %s, menolak."
 
 #: src/home/pam_systemd_home.c:868
 msgid "User record is blocked, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna terblokir, menolak akses."
 
 #: src/home/pam_systemd_home.c:872
 msgid "User record is not valid yet, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna belum valid, menolak akses."
 
 #: src/home/pam_systemd_home.c:876
 msgid "User record is not valid anymore, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna tidak valid lagi, menolak akses."
 
 #: src/home/pam_systemd_home.c:881 src/home/pam_systemd_home.c:932
 msgid "User record not valid, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna tidak valid, menolak akses."
 
 #: src/home/pam_systemd_home.c:893
 #, c-format
 msgid "Too many logins, try again in %s."
-msgstr ""
+msgstr "Terlalu banyak log masuk, coba lagi dalam %s."
 
 #: src/home/pam_systemd_home.c:904
 msgid "Password change required."
-msgstr ""
+msgstr "Perubahan kata sandi diperlukan."
 
 #: src/home/pam_systemd_home.c:908
 msgid "Password expired, change required."
-msgstr ""
+msgstr "Kata sandi kedaluwarsa, perubahan diperlukan."
 
 #: src/home/pam_systemd_home.c:914
 msgid "Password is expired, but can't change, refusing login."
-msgstr ""
+msgstr "Kata sandi kedaluwarsa, tapi tidak bisa mengubah, menolak log masuk."
 
 #: src/home/pam_systemd_home.c:918
 msgid "Password will expire soon, please change."
-msgstr ""
+msgstr "Kata sandi akan segera kedaluwarsan, harap diubah."
 
 #: src/hostname/org.freedesktop.hostname1.policy:20
 msgid "Set hostname"
index 1af7cf8531f7859c59244412d677424d6d0df6c1..a53463451910272ff6a54c59e484b07131fc7514 100644 (file)
--- a/po/pa.po
+++ b/po/pa.po
@@ -1,12 +1,12 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 #
 # A S Alam <amanpreet.alam@gmail.com>, 2020, 2021, 2023.
-# A S Alam <aalam@users.noreply.translate.fedoraproject.org>, 2023.
+# A S Alam <aalam@users.noreply.translate.fedoraproject.org>, 2023, 2024.
 msgid ""
 msgstr ""
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2023-11-14 21:25+0000\n"
-"PO-Revision-Date: 2023-12-28 15:36+0000\n"
+"PO-Revision-Date: 2024-01-16 14:35+0000\n"
 "Last-Translator: A S Alam <aalam@users.noreply.translate.fedoraproject.org>\n"
 "Language-Team: Punjabi <https://translate.fedoraproject.org/projects/systemd/"
 "master/pa/>\n"
@@ -15,7 +15,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n > 1;\n"
-"X-Generator: Weblate 5.3\n"
+"X-Generator: Weblate 5.3.1\n"
 
 #: src/core/org.freedesktop.systemd1.policy.in:22
 msgid "Send passphrase back to system"
@@ -155,7 +155,7 @@ msgstr "ਅਫ਼ਸੋਸ, ਰਿਕਵਰੀ ਕੁੰਜੀ ਫੇਰ ਭਰ
 #: src/home/pam_systemd_home.c:352
 #, c-format
 msgid "Security token of user %s not inserted."
-msgstr ""
+msgstr "%s ਵਰਤੋਂਕਾਰ ਲਈ ਸੁਰੱਖਿਆ ਟੋਕਨ ਨਹੀਂ ਦਿੱਤਾ ਗਿਆ।"
 
 #: src/home/pam_systemd_home.c:353 src/home/pam_systemd_home.c:356
 msgid "Try again with password: "
@@ -834,19 +834,19 @@ msgstr ""
 
 #: src/network/org.freedesktop.network1.policy:165
 msgid "Reload network settings"
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਸੈਟਿੰਗਾਂ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰੋ"
 
 #: src/network/org.freedesktop.network1.policy:166
 msgid "Authentication is required to reload network settings."
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਸੈਟਿੰਗਾਂ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/network/org.freedesktop.network1.policy:176
 msgid "Reconfigure network interface"
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਇੰਟਰਫੇਸ ਦੀ ਮੁੜ-ਸੰਰਚਨਾ ਕਰੋ"
 
 #: src/network/org.freedesktop.network1.policy:177
 msgid "Authentication is required to reconfigure network interface."
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਇੰਟਰਫੇਸ ਦੀ ਮੁੜ-ਸੰਰਚਨਾ ਕਰਨ ਥਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/portable/org.freedesktop.portable1.policy:13
 msgid "Inspect a portable service image"
@@ -894,7 +894,7 @@ msgstr ""
 
 #: src/resolve/org.freedesktop.resolve1.policy:133
 msgid "Authentication is required to reset name resolution settings."
-msgstr ""
+msgstr "ਨਾਂ ਹੱਲ ਸੈਟਿੰਗਾਂ ਮੁੜ-ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/timedate/org.freedesktop.timedate1.policy:22
 msgid "Set system time"
@@ -902,15 +902,15 @@ msgstr "ਸਿਸਟਮ ਸਮਾੰ ਸੈੱਟ ਕਰੋ"
 
 #: src/timedate/org.freedesktop.timedate1.policy:23
 msgid "Authentication is required to set the system time."
-msgstr ""
+msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/timedate/org.freedesktop.timedate1.policy:33
 msgid "Set system timezone"
-msgstr ""
+msgstr "ਸਿਸਟਮ ਸਮਾਂ-ਖੇਤਰ ਸੈੱਟ ਕਰੋ"
 
 #: src/timedate/org.freedesktop.timedate1.policy:34
 msgid "Authentication is required to set the system timezone."
-msgstr ""
+msgstr "ਸਿਸਟਮ ਸਮਾਂ-ਖੇਤਰ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/timedate/org.freedesktop.timedate1.policy:43
 msgid "Set RTC to local timezone or UTC"
@@ -930,19 +930,19 @@ msgstr ""
 
 #: src/core/dbus-unit.c:352
 msgid "Authentication is required to start '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/core/dbus-unit.c:353
 msgid "Authentication is required to stop '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਰੋਕਣ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/core/dbus-unit.c:354
 msgid "Authentication is required to reload '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਮੁੜ-ਲੋਡ (reload) ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/core/dbus-unit.c:355 src/core/dbus-unit.c:356
 msgid "Authentication is required to restart '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ (restart) ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
 
 #: src/core/dbus-unit.c:553
 msgid "Authentication is required to send a UNIX signal to the processes of '$(unit)'."
index 96b888fdffbff2e150bdcdfac82d9ac677a7f7c5..390c6e00343dbc87ba1b78284af97386981295c9 100644 (file)
@@ -59,6 +59,8 @@ KERNEL=="vd*", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="di
 
 # ATA
 KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_BUS}=="ata", ENV{ID_ATA_PERIPHERAL_DEVICE_TYPE}=="20", PROGRAM="scsi_id -u -g $devnode", \
+  SYMLINK+="disk/by-id/scsi-$result$env{.PART_SUFFIX}"
 
 # ATAPI devices (SPC-3 or later)
 KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
index b307e428bebb4142f2d74db96fdcf48c5b49a077..e6c476742b38dd4c3f783e9928e8c702af7f9622 100644 (file)
@@ -42,9 +42,9 @@ _loginctl () {
 
     local -A OPTS=(
         [STANDALONE]='--all -a --help -h --no-pager --version
-                      --no-legend --no-ask-password -l --full --value'
+                      --no-legend --no-ask-password -l --full --value -j'
         [ARG]='--host -H --kill-whom --property -p --signal -s -M --machine
-                      -n --lines -o --output -P'
+                      -n --lines -o --output -P --json'
     )
 
     if __contains_word "$prev" ${OPTS[ARG]}; then
@@ -68,6 +68,9 @@ _loginctl () {
             --output|-o)
                 comps=$( loginctl --output=help 2>/dev/null )
                 ;;
+            --json)
+                comps=$( loginctl --json=help 2>/dev/null )
+                ;;
         esac
         COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
         return 0
index 66c6524fe8b4e803567503687bf93e5eeb0e69b3..1723f75beea25b67fa8bced3de2de879d41879b1 100644 (file)
@@ -44,7 +44,7 @@ __get_block_devices() {
     done
 }
 
-_systemd-cryptenroll() {
+_systemd_cryptenroll() {
     local comps
     local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
     local -A OPTS=(
@@ -59,11 +59,14 @@ _systemd-cryptenroll() {
                --fido2-with-user-presence
                --fido2-with-user-verification
                --tpm2-device
+               --tpm2-device-key
+               --tpm2-seal-key-handle
                --tpm2-pcrs
                --tpm2-public-key
                --tpm2-public-key-pcrs
                --tpm2-signature
                --tpm2-with-pin
+               --tpm2-pcrlock
                --wipe-slot'
     )
 
@@ -71,7 +74,7 @@ _systemd-cryptenroll() {
 
     if __contains_word "$prev" ${OPTS[ARG]}; then
         case $prev in
-            --unlock-key-file|--tpm2-public-key|--tpm2-signature)
+            --unlock-key-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock)
                 comps=$(compgen -A file -- "$cur")
                 compopt -o filenames
                 ;;
@@ -111,4 +114,4 @@ _systemd-cryptenroll() {
     return 0
 }
 
-complete -F _systemd-cryptenroll systemd-cryptenroll
+complete -F _systemd_cryptenroll systemd-cryptenroll
index 4bb203a2f8f54c01cc3f9d5198cc445f3fa0913a..17fb6420dea6d7bb189f0bfb7fc82179c68a7d2e 100644 (file)
@@ -37,6 +37,8 @@ _systemd_dissect() {
                      --in-memory'
         [ARG]='-m --mount -M
                -u --umount -U
+               --attach
+               --detach
                -l --list
                --mtree
                --with
@@ -72,7 +74,7 @@ _systemd_dissect() {
 
     if __contains_word "$prev_1" ${OPTS[ARG]}; then
         case $prev_1 in
-            -l|--list|--mtree|-m|--mount|-M|-x|--copy-from|-a|--copy-to|--verity-data|--validate|--with)
+            -l|--list|--mtree|-m|--mount|-M|--attach|--detach|-x|--copy-from|-a|--copy-to|--verity-data|--validate|--with)
                 comps=$(compgen -A file -- "$cur")
                 compopt -o filenames
                 ;;
index 5fbfff0af41663d3d0cf69c5281ff27396a52461..136d2b3e687bcbd158248794f4ec447fba9dc495 100644 (file)
@@ -20,7 +20,7 @@ typedef void* (*mfree_func_t)(void *p);
  * proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */
 #define ALLOCA_MAX (4U*1024U*1024U)
 
-#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n)))
+#define new(t, n) ((t*) malloc_multiply((n), sizeof(t)))
 
 #define new0(t, n) ((t*) calloc((n) ?: 1, sizeof(t)))
 
@@ -45,9 +45,9 @@ typedef void* (*mfree_func_t)(void *p);
                 (t*) alloca0((sizeof(t)*_n_));                          \
         })
 
-#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
+#define newdup(t, p, n) ((t*) memdup_multiply(p, (n), sizeof(t)))
 
-#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n)))
+#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, (n), sizeof(t)))
 
 #define malloc0(n) (calloc(1, (n) ?: 1))
 
@@ -112,7 +112,7 @@ static inline bool size_multiply_overflow(size_t size, size_t need) {
         return _unlikely_(need != 0 && size > (SIZE_MAX / need));
 }
 
-_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) {
+_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t need, size_t size) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
@@ -128,7 +128,7 @@ _alloc_(2, 3) static inline void *reallocarray(void *p, size_t need, size_t size
 }
 #endif
 
-_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) {
+_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t need, size_t size) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
@@ -137,7 +137,7 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si
 
 /* Note that we can't decorate this function with _alloc_() since the returned memory area is one byte larger
  * than the product of its parameters. */
-static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
+static inline void *memdup_suffix0_multiply(const void *p, size_t need, size_t size) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
index 75a1d68e967254a73b5ffdde951fb2ffd2798241..d95f35e79807ff470c808837996bc702179205be 100644 (file)
@@ -471,6 +471,33 @@ char* octescape(const char *s, size_t len) {
         return buf;
 }
 
+char* decescape(const char *s, const char *bad, size_t len) {
+        char *buf, *t;
+
+        /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
+
+        assert(s || len == 0);
+
+        t = buf = new(char, len * 4 + 1);
+        if (!buf)
+                return NULL;
+
+        for (size_t i = 0; i < len; i++) {
+                uint8_t u = (uint8_t) s[i];
+
+                if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) {
+                        *(t++) = '\\';
+                        *(t++) = '0' + (u / 100);
+                        *(t++) = '0' + ((u / 10) % 10);
+                        *(t++) = '0' + (u % 10);
+                } else
+                        *(t++) = u;
+        }
+
+        *t = 0;
+        return buf;
+}
+
 static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
         assert(bad);
         assert(t);
index 318da6f220347fb6cb1a880a80e64371c07cb1f4..65caf0dbcfa3b1660fdfadd500115ad8a1850f9b 100644 (file)
@@ -65,6 +65,7 @@ static inline char* xescape(const char *s, const char *bad) {
         return xescape_full(s, bad, SIZE_MAX, 0);
 }
 char* octescape(const char *s, size_t len);
+char* decescape(const char *s, const char *bad, size_t len);
 char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags);
 
 char* shell_escape(const char *s, const char *bad);
index 6a1143b4f301ee8bf0237fb99caa6f209edcb41c..183266513acbe408976b7966435f251fa8c0cf5a 100644 (file)
@@ -52,6 +52,11 @@ static inline void fclosep(FILE **f) {
         safe_fclose(*f);
 }
 
+static inline void* close_fd_ptr(void *p) {
+        safe_close(PTR_TO_FD(p));
+        return NULL;
+}
+
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, pclose, NULL);
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
 
index d3eb980abd3a0f5b7861e0e4b056057afe53c3ac..d63aa816ccb9cd8dbb6cb5ead5f3c7b2aeaf3a4f 100644 (file)
@@ -383,10 +383,10 @@ assert_cc(sizeof(dummy_t) == 0);
 /* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
  * convertible. The iteration variable 'entry' must already be defined. */
 #define VA_ARGS_FOREACH(entry, ...)                                     \
-        _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
-#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...)         \
-        for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \
-             ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \
+        _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), UNIQ_T(_va_sentinel_, UNIQ), ##__VA_ARGS__)
+#define _VA_ARGS_FOREACH(entry, _entries_, _current_, _va_sentinel_, ...)         \
+        for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \
+             ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \
              _current_++)
 
 #include "log.h"
index 2101f617ad2026c273cb816ea4f19bebf7749632..d517263b36b147bdc245ef707967bb513b2ca851 100644 (file)
@@ -33,71 +33,78 @@ const struct namespace_info namespace_info[] = {
 
 #define pid_namespace_path(pid, type) procfs_file_alloca(pid, namespace_info[type].proc_path)
 
-int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) {
-        _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, netnsfd = -EBADF, usernsfd = -EBADF;
-        int rfd = -EBADF;
+int namespace_open(
+                pid_t pid,
+                int *ret_pidns_fd,
+                int *ret_mntns_fd,
+                int *ret_netns_fd,
+                int *ret_userns_fd,
+                int *ret_root_fd) {
+
+        _cleanup_close_ int pidns_fd = -EBADF, mntns_fd = -EBADF, netns_fd = -EBADF,
+                userns_fd = -EBADF, root_fd = -EBADF;
 
         assert(pid >= 0);
 
-        if (mntns_fd) {
-                const char *mntns;
+        if (ret_pidns_fd) {
+                const char *pidns;
 
-                mntns = pid_namespace_path(pid, NAMESPACE_MOUNT);
-                mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (mntnsfd < 0)
+                pidns = pid_namespace_path(pid, NAMESPACE_PID);
+                pidns_fd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (pidns_fd < 0)
                         return -errno;
         }
 
-        if (pidns_fd) {
-                const char *pidns;
+        if (ret_mntns_fd) {
+                const char *mntns;
 
-                pidns = pid_namespace_path(pid, NAMESPACE_PID);
-                pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (pidnsfd < 0)
+                mntns = pid_namespace_path(pid, NAMESPACE_MOUNT);
+                mntns_fd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (mntns_fd < 0)
                         return -errno;
         }
 
-        if (netns_fd) {
+        if (ret_netns_fd) {
                 const char *netns;
 
                 netns = pid_namespace_path(pid, NAMESPACE_NET);
-                netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (netnsfd < 0)
+                netns_fd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (netns_fd < 0)
                         return -errno;
         }
 
-        if (userns_fd) {
+        if (ret_userns_fd) {
                 const char *userns;
 
                 userns = pid_namespace_path(pid, NAMESPACE_USER);
-                usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (usernsfd < 0 && errno != ENOENT)
+                userns_fd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (userns_fd < 0 && errno != ENOENT)
                         return -errno;
         }
 
-        if (root_fd) {
+        if (ret_root_fd) {
                 const char *root;
 
                 root = procfs_file_alloca(pid, "root");
-                rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
-                if (rfd < 0)
+                root_fd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+                if (root_fd < 0)
                         return -errno;
         }
 
-        if (pidns_fd)
-                *pidns_fd = TAKE_FD(pidnsfd);
+        if (ret_pidns_fd)
+                *ret_pidns_fd = TAKE_FD(pidns_fd);
 
-        if (mntns_fd)
-                *mntns_fd = TAKE_FD(mntnsfd);
+        if (ret_mntns_fd)
+                *ret_mntns_fd = TAKE_FD(mntns_fd);
 
-        if (netns_fd)
-                *netns_fd = TAKE_FD(netnsfd);
+        if (ret_netns_fd)
+                *ret_netns_fd = TAKE_FD(netns_fd);
 
-        if (userns_fd)
-                *userns_fd = TAKE_FD(usernsfd);
+        if (ret_userns_fd)
+                *ret_userns_fd = TAKE_FD(userns_fd);
 
-        if (root_fd)
-                *root_fd = TAKE_FD(rfd);
+        if (ret_root_fd)
+                *ret_root_fd = TAKE_FD(root_fd);
 
         return 0;
 }
@@ -221,7 +228,7 @@ int userns_acquire(const char *uid_map, const char *gid_map) {
 
         r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid);
         if (r < 0)
-                return r;
+                return log_debug_errno(r, "Failed to fork process (sd-mkuserns): %m");
         if (r == 0)
                 /* Child. We do nothing here, just freeze until somebody kills us. */
                 freeze();
@@ -229,19 +236,50 @@ int userns_acquire(const char *uid_map, const char *gid_map) {
         xsprintf(path, "/proc/" PID_FMT "/uid_map", pid);
         r = write_string_file(path, uid_map, WRITE_STRING_FILE_DISABLE_BUFFER);
         if (r < 0)
-                return log_error_errno(r, "Failed to write UID map: %m");
+                return log_debug_errno(r, "Failed to write UID map: %m");
 
         xsprintf(path, "/proc/" PID_FMT "/gid_map", pid);
         r = write_string_file(path, gid_map, WRITE_STRING_FILE_DISABLE_BUFFER);
         if (r < 0)
-                return log_error_errno(r, "Failed to write GID map: %m");
-
-        r = namespace_open(pid, NULL, NULL, NULL, &userns_fd, NULL);
+                return log_debug_errno(r, "Failed to write GID map: %m");
+
+        r = namespace_open(pid,
+                           /* ret_pidns_fd = */ NULL,
+                           /* ret_mntns_fd = */ NULL,
+                           /* ret_netns_fd = */ NULL,
+                           &userns_fd,
+                           /* ret_root_fd = */ NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to open userns fd: %m");
+                return log_debug_errno(r, "Failed to open userns fd: %m");
 
         return TAKE_FD(userns_fd);
+}
+
+int netns_acquire(void) {
+        _cleanup_(sigkill_waitp) pid_t pid = 0;
+        _cleanup_close_ int netns_fd = -EBADF;
+        int r;
+
+        /* Forks off a process in a new network namespace, acquires a network namespace fd, and then kills
+         * the process again. This way we have a netns fd that is not bound to any process. */
+
+        r = safe_fork("(sd-mknetns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_NETNS, &pid);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to fork process (sd-mknetns): %m");
+        if (r == 0)
+                /* Child. We do nothing here, just freeze until somebody kills us. */
+                freeze();
+
+        r = namespace_open(pid,
+                           /* ret_pidns_fd = */ NULL,
+                           /* ret_mntns_fd = */ NULL,
+                           &netns_fd,
+                           /* ret_userns_fd = */ NULL,
+                           /* ret_root_fd = */ NULL);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to open netns fd: %m");
 
+        return TAKE_FD(netns_fd);
 }
 
 int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) {
index be5b2281d3c4c1bc556ffd1898c2b7b857ddbf1c..d1d015612fd2d2fd4880022b03e5e81e2ca16b45 100644 (file)
@@ -22,7 +22,13 @@ extern const struct namespace_info {
         unsigned int clone_flag;
 } namespace_info[_NAMESPACE_TYPE_MAX + 1];
 
-int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd);
+int namespace_open(
+                pid_t pid,
+                int *ret_pidns_fd,
+                int *ret_mntns_fd,
+                int *ret_netns_fd,
+                int *ret_userns_fd,
+                int *ret_root_fd);
 int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
 
 int fd_is_ns(int fd, unsigned long nsflag);
@@ -45,4 +51,5 @@ static inline bool userns_shift_range_valid(uid_t shift, uid_t range) {
 }
 
 int userns_acquire(const char *uid_map, const char *gid_map);
+int netns_acquire(void);
 int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type);
index 4e3d59fc563d8b204602e7355abe30b19bb88a90..068054512bcb1cc756af3cc8673ae429720a851c 100644 (file)
@@ -167,19 +167,13 @@ static char** user_dirs(
                 return NULL;
 
         /* Now merge everything we found. */
-        if (strv_extend(&res, persistent_control) < 0)
-                return NULL;
-
-        if (strv_extend(&res, runtime_control) < 0)
-                return NULL;
-
-        if (strv_extend(&res, transient) < 0)
-                return NULL;
-
-        if (strv_extend(&res, generator_early) < 0)
-                return NULL;
-
-        if (strv_extend(&res, persistent_config) < 0)
+        if (strv_extend_many(
+                            &res,
+                            persistent_control,
+                            runtime_control,
+                            transient,
+                            generator_early,
+                            persistent_config) < 0)
                 return NULL;
 
         if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0)
@@ -192,16 +186,12 @@ static char** user_dirs(
         if (strv_extend_strv(&res, (char**) user_config_unit_paths, false) < 0)
                 return NULL;
 
-        if (strv_extend(&res, runtime_config) < 0)
-                return NULL;
-
-        if (strv_extend(&res, global_runtime_config) < 0)
-                return NULL;
-
-        if (strv_extend(&res, generator) < 0)
-                return NULL;
-
-        if (strv_extend(&res, data_home) < 0)
+        if (strv_extend_many(
+                            &res,
+                            runtime_config,
+                            global_runtime_config,
+                            generator,
+                            data_home) < 0)
                 return NULL;
 
         if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0)
index d75d25af9975416896c0048dd0c1b7df14b5f8f0..697c8d9c6baf91bd23d54a22131f618ca8737e4c 100644 (file)
@@ -1541,10 +1541,11 @@ int safe_fork_full(
                 }
         }
 
-        if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS)) != 0)
+        if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS|FORK_NEW_NETNS)) != 0)
                 pid = raw_clone(SIGCHLD|
                                 (FLAGS_SET(flags, FORK_NEW_MOUNTNS) ? CLONE_NEWNS : 0) |
-                                (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0));
+                                (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0) |
+                                (FLAGS_SET(flags, FORK_NEW_NETNS) ? CLONE_NEWNET : 0));
         else
                 pid = fork();
         if (pid < 0)
index de6a2bd2038cfff6897375c48dfbbb64a1c9aa2f..b270fc82ea1641f53c230a2a62b11d0b24276238 100644 (file)
@@ -154,11 +154,11 @@ int must_be_root(void);
 
 pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata);
 
-/* 💣 Note that FORK_NEW_USERNS + FORK_NEW_MOUNTNS should not be called in threaded programs, because they
- * cause us to use raw_clone() which does not synchronize the glibc malloc() locks, and thus will cause
- * deadlocks if the parent uses threads and the child does memory allocations. Hence: if the parent is
- * threaded these flags may not be used. These flags cannot be used if the parent uses threads or the child
- * uses malloc(). 💣 */
+/* 💣 Note that FORK_NEW_USERNS, FORK_NEW_MOUNTNS, or FORK_NEW_NETNS should not be called in threaded
+ * programs, because they cause us to use raw_clone() which does not synchronize the glibc malloc() locks,
+ * and thus will cause deadlocks if the parent uses threads and the child does memory allocations. Hence: if
+ * the parent is threaded these flags may not be used. These flags cannot be used if the parent uses threads
+ * or the child uses malloc(). 💣 */
 typedef enum ForkFlags {
         FORK_RESET_SIGNALS      = 1 <<  0, /* Reset all signal handlers and signal mask */
         FORK_CLOSE_ALL_FDS      = 1 <<  1, /* Close all open file descriptors in the child, except for 0,1,2 */
@@ -179,6 +179,7 @@ typedef enum ForkFlags {
         FORK_CLOEXEC_OFF        = 1 << 16, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
         FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
         FORK_DETACH             = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */
+        FORK_NEW_NETNS          = 1 << 19, /* Run child in its own network namespace                             💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
 } ForkFlags;
 
 int safe_fork_full(
index 86791608b4dc57b475a85b8ae7fcc9df202fa2a7..c784125ccb423450063f116ee7bb216d8e342c42 100644 (file)
@@ -374,6 +374,11 @@ int socket_get_mtu(int fd, int af, size_t *ret);
 
 int connect_unix_path(int fd, int dir_fd, const char *path);
 
+static inline bool VSOCK_CID_IS_REGULAR(unsigned cid) {
+        /* 0, 1, 2, UINT32_MAX are special, refuse those */
+        return cid > 2 && cid < UINT32_MAX;
+}
+
 int vsock_parse_port(const char *s, unsigned *ret);
 int vsock_parse_cid(const char *s, unsigned *ret);
 
index 908e9e251398a15e5e95b01622dd003c7d4b0f6c..e32653818b0b417d797d1102c5ef14271343a873 100644 (file)
@@ -123,6 +123,22 @@ char** strv_copy_n(char * const *l, size_t m) {
         return TAKE_PTR(result);
 }
 
+int strv_copy_unless_empty(char * const *l, char ***ret) {
+        assert(ret);
+
+        if (strv_isempty(l)) {
+                *ret = NULL;
+                return 0;
+        }
+
+        char **copy = strv_copy(l);
+        if (!copy)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(copy);
+        return 1;
+}
+
 size_t strv_length(char * const *l) {
         size_t n = 0;
 
@@ -489,29 +505,31 @@ int strv_insert(char ***l, size_t position, char *value) {
         char **c;
         size_t n, m;
 
+        assert(l);
+
         if (!value)
                 return 0;
 
         n = strv_length(*l);
         position = MIN(position, n);
 
-        /* increase and check for overflow */
-        m = n + 2;
-        if (m < n)
+        /* check for overflow and increase*/
+        if (n > SIZE_MAX - 2)
                 return -ENOMEM;
+        m = n + 2;
 
-        c = new(char*, m);
+        c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m), sizeof(char*));
         if (!c)
                 return -ENOMEM;
 
-        for (size_t i = 0; i < position; i++)
-                c[i] = (*l)[i];
+        if (n > position)
+                memmove(c + position + 1, c + position, (n - position) * sizeof(char*));
+
         c[position] = value;
-        for (size_t i = position; i < n; i++)
-                c[i+1] = (*l)[i];
-        c[n+1] = NULL;
+        c[n + 1] = NULL;
 
-        return free_and_replace(*l, c);
+        *l = c;
+        return 0;
 }
 
 int strv_consume_with_size(char ***l, size_t *n, char *value) {
@@ -572,39 +590,63 @@ int strv_extend_with_size(char ***l, size_t *n, const char *value) {
         return strv_consume_with_size(l, n, v);
 }
 
-int strv_extend_front(char ***l, const char *value) {
+int strv_extend_many_internal(char ***l, const char *value, ...) {
+        va_list ap;
         size_t n, m;
-        char *v, **c;
+        int r;
 
         assert(l);
 
-        /* Like strv_extend(), but prepends rather than appends the new entry */
+        m = n = strv_length(*l);
 
-        if (!value)
-                return 0;
+        r = 0;
+        va_start(ap, value);
+        for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
+                if (!s)
+                        continue;
 
-        n = strv_length(*l);
+                if (m > SIZE_MAX-1) { /* overflow */
+                        r = -ENOMEM;
+                        break;
+                }
+                m++;
+        }
+        va_end(ap);
 
-        /* Increase and overflow check. */
-        m = n + 2;
-        if (m < n)
+        if (r < 0)
+                return r;
+        if (m > SIZE_MAX-1)
                 return -ENOMEM;
 
-        v = strdup(value);
-        if (!v)
+        char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m+1), sizeof(char*));
+        if (!c)
                 return -ENOMEM;
+        *l = c;
 
-        c = reallocarray(*l, m, sizeof(char*));
-        if (!c) {
-                free(v);
-                return -ENOMEM;
+        r = 0;
+        size_t i = n;
+        va_start(ap, value);
+        for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
+                if (!s)
+                        continue;
+
+                c[i] = strdup(s);
+                if (!c[i]) {
+                        r = -ENOMEM;
+                        break;
+                }
+                i++;
         }
+        va_end(ap);
 
-        memmove(c+1, c, n * sizeof(char*));
-        c[0] = v;
-        c[n+1] = NULL;
+        if (r < 0) {
+                /* rollback on error */
+                for (size_t j = n; j < i; j++)
+                        c[j] = mfree(c[j]);
+                return r;
+        }
 
-        *l = c;
+        c[i] = NULL;
         return 0;
 }
 
index f1a8bc49109540bfbbecf94d4ce9f52e997937b4..91337b9287086027e41ae56ecd7303c44ca95218 100644 (file)
@@ -38,6 +38,8 @@ char** strv_copy_n(char * const *l, size_t n);
 static inline char** strv_copy(char * const *l) {
         return strv_copy_n(l, SIZE_MAX);
 }
+int strv_copy_unless_empty(char * const *l, char ***ret);
+
 size_t strv_length(char * const *l) _pure_;
 
 int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
@@ -53,8 +55,10 @@ static inline int strv_extend(char ***l, const char *value) {
         return strv_extend_with_size(l, NULL, value);
 }
 
+int strv_extend_many_internal(char ***l, const char *value, ...);
+#define strv_extend_many(l, ...) strv_extend_many_internal(l, __VA_ARGS__, POINTER_MAX)
+
 int strv_extendf(char ***l, const char *format, ...) _printf_(2,3);
-int strv_extend_front(char ***l, const char *value);
 
 int strv_push_with_size(char ***l, size_t *n, char *value);
 static inline int strv_push(char ***l, char *value) {
index b66a6622ae7c6f8258ee9c6c68ca4c8636447e60..9a1933f57924c1f9d3bce8ff632b48df7c655fca 100644 (file)
@@ -96,6 +96,26 @@ int sysctl_write_ip_property(int af, const char *ifname, const char *property, c
         return sysctl_write(p, value);
 }
 
+int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value) {
+        const char *p;
+
+        assert(property);
+        assert(value);
+        assert(ifname);
+
+        if (!IN_SET(af, AF_INET, AF_INET6))
+                return -EAFNOSUPPORT;
+
+        if (ifname) {
+                if (!ifname_valid_full(ifname, IFNAME_VALID_SPECIAL))
+                        return -EINVAL;
+                p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/", ifname, "/", property);
+        } else
+                p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/default/", property);
+
+        return sysctl_write(p, value);
+}
+
 int sysctl_read(const char *property, char **ret) {
         char *p;
         int r;
index 32364196f97a43c9875667309e961e49542c8e4b..7192e8c0b0a5e9d18a608ad18f76bc814d44caf3 100644 (file)
@@ -19,6 +19,13 @@ static inline int sysctl_write_ip_property_boolean(int af, const char *ifname, c
         return sysctl_write_ip_property(af, ifname, property, one_zero(value));
 }
 
+int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value);
+static inline int sysctl_write_ip_neighbor_property_uint32(int af, const char *ifname, const char *property, uint32_t value) {
+        char buf[DECIMAL_STR_MAX(uint32_t)];
+        xsprintf(buf, "%u", value);
+        return sysctl_write_ip_neighbor_property(af, ifname, property, buf);
+}
+
 #define DEFINE_SYSCTL_WRITE_IP_PROPERTY(name, type, format)           \
         static inline int sysctl_write_ip_property_##name(int af, const char *ifname, const char *property, type value) { \
                 char buf[DECIMAL_STR_MAX(type)];                        \
index 488541ed23be3bd20866cf012d5fce26f1e416ff..4c1824bc83f52a9fa0393587fde06c55ef2b17e1 100644 (file)
@@ -1211,7 +1211,7 @@ int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) {
 
         assert(pid > 0);
 
-        r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+        r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
         if (r < 0)
                 return r;
 
@@ -1262,7 +1262,7 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
         pid_t child;
         int r;
 
-        r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+        r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
         if (r < 0)
                 return r;
 
index ee60ff4840c3c7902eaaf5c7f8de7be99dced792..4822917f28fe38155abaea1e06f024e1f1a6a65b 100644 (file)
@@ -80,6 +80,9 @@
 /* Erase characters until the end of the line */
 #define ANSI_ERASE_TO_END_OF_LINE "\x1B[K"
 
+/* Erase characters until end of screen */
+#define ANSI_ERASE_TO_END_OF_SCREEN "\x1B[J"
+
 /* Move cursor up one line */
 #define ANSI_REVERSE_LINEFEED "\x1BM"
 
index f9014dc560bd4895fcbae0269af878a7de71574e..02123dc591351638846f30f04173b0857157f653 100644 (file)
@@ -1517,7 +1517,7 @@ int get_timezones(char ***ret) {
         /* Always include UTC */
         r = strv_extend(&zones, "UTC");
         if (r < 0)
-                return -ENOMEM;
+                return r;
 
         strv_sort(zones);
         strv_uniq(zones);
index 8aaffe8d0448d715977232f5fa72ee3c78a769f4..cee91541410fef4e138950ba1466f25bee28bc64 100644 (file)
@@ -184,27 +184,28 @@ const char* default_root_shell(const char *root) {
 
 static int synthesize_user_creds(
                 const char **username,
-                uid_t *uid, gid_t *gid,
-                const char **home,
-                const char **shell,
+                uid_t *ret_uid, gid_t *ret_gid,
+                const char **ret_home,
+                const char **ret_shell,
                 UserCredsFlags flags) {
 
+        assert(username);
+        assert(*username);
+
         /* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode
          * their user record data. */
 
         if (STR_IN_SET(*username, "root", "0")) {
                 *username = "root";
 
-                if (uid)
-                        *uid = 0;
-                if (gid)
-                        *gid = 0;
-
-                if (home)
-                        *home = "/root";
-
-                if (shell)
-                        *shell = default_root_shell(NULL);
+                if (ret_uid)
+                        *ret_uid = 0;
+                if (ret_gid)
+                        *ret_gid = 0;
+                if (ret_home)
+                        *ret_home = "/root";
+                if (ret_shell)
+                        *ret_shell = default_root_shell(NULL);
 
                 return 0;
         }
@@ -213,16 +214,14 @@ static int synthesize_user_creds(
             synthesize_nobody()) {
                 *username = NOBODY_USER_NAME;
 
-                if (uid)
-                        *uid = UID_NOBODY;
-                if (gid)
-                        *gid = GID_NOBODY;
-
-                if (home)
-                        *home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
-
-                if (shell)
-                        *shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
+                if (ret_uid)
+                        *ret_uid = UID_NOBODY;
+                if (ret_gid)
+                        *ret_gid = GID_NOBODY;
+                if (ret_home)
+                        *ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
+                if (ret_shell)
+                        *ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
 
                 return 0;
         }
@@ -232,11 +231,12 @@ static int synthesize_user_creds(
 
 int get_user_creds(
                 const char **username,
-                uid_t *uid, gid_t *gid,
-                const char **home,
-                const char **shell,
+                uid_t *ret_uid, gid_t *ret_gid,
+                const char **ret_home,
+                const char **ret_shell,
                 UserCredsFlags flags) {
 
+        bool patch_username = false;
         uid_t u = UID_INVALID;
         struct passwd *p;
         int r;
@@ -245,7 +245,7 @@ int get_user_creds(
         assert(*username);
 
         if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
-            (!home && !shell)) {
+            (!ret_home && !ret_shell)) {
 
                 /* So here's the deal: normally, we'll try to synthesize all records we can synthesize, and override
                  * the user database with that. However, if the user specifies USER_CREDS_PREFER_NSS then the
@@ -256,7 +256,7 @@ int get_user_creds(
                  * of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't
                  * support. */
 
-                r = synthesize_user_creds(username, uid, gid, home, shell, flags);
+                r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags);
                 if (r >= 0)
                         return 0;
                 if (r != -ENOMEDIUM) /* not a username we can synthesize */
@@ -271,15 +271,15 @@ int get_user_creds(
                  * instead of the first occurrence in the database. However if the uid was configured by a numeric uid,
                  * then let's pick the real username from /etc/passwd. */
                 if (p)
-                        *username = p->pw_name;
-                else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !gid && !home && !shell) {
+                        patch_username = true;
+                else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) {
 
                         /* If the specified user is a numeric UID and it isn't in the user database, and the caller
                          * passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that
                          * and don't complain. */
 
-                        if (uid)
-                                *uid = u;
+                        if (ret_uid)
+                                *ret_uid = u;
 
                         return 0;
                 }
@@ -293,65 +293,57 @@ int get_user_creds(
                 r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
 
                 /* If the user requested that we only synthesize as fallback, do so now */
-                if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
-                        if (synthesize_user_creds(username, uid, gid, home, shell, flags) >= 0)
+                if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
+                        if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0)
                                 return 0;
-                }
 
                 return r;
         }
 
-        if (uid) {
-                if (!uid_is_valid(p->pw_uid))
-                        return -EBADMSG;
+        if (ret_uid && !uid_is_valid(p->pw_uid))
+                return -EBADMSG;
 
-                *uid = p->pw_uid;
-        }
+        if (ret_gid && !gid_is_valid(p->pw_gid))
+                return -EBADMSG;
 
-        if (gid) {
-                if (!gid_is_valid(p->pw_gid))
-                        return -EBADMSG;
+        if (ret_uid)
+                *ret_uid = p->pw_uid;
 
-                *gid = p->pw_gid;
-        }
+        if (ret_gid)
+                *ret_gid = p->pw_gid;
 
-        if (home) {
-                if (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
-                    (empty_or_root(p->pw_dir) ||
-                     !path_is_valid(p->pw_dir) ||
-                     !path_is_absolute(p->pw_dir)))
-                        *home = NULL; /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
-                else
-                        *home = p->pw_dir;
-        }
+        if (ret_home)
+                /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
+                *ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
+                             (empty_or_root(p->pw_dir) ||
+                              !path_is_valid(p->pw_dir) ||
+                              !path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir;
 
-        if (shell) {
-                if (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
-                    (isempty(p->pw_shell) ||
-                     !path_is_valid(p->pw_dir) ||
-                     !path_is_absolute(p->pw_shell) ||
-                     is_nologin_shell(p->pw_shell)))
-                        *shell = NULL;
-                else
-                        *shell = p->pw_shell;
-        }
+        if (ret_shell)
+                *ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
+                              (isempty(p->pw_shell) ||
+                               !path_is_valid(p->pw_shell) ||
+                               !path_is_absolute(p->pw_shell) ||
+                               is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell;
+
+        if (patch_username)
+                *username = p->pw_name;
 
         return 0;
 }
 
-int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
-        struct group *g;
-        gid_t id;
+static int synthesize_group_creds(
+                const char **groupname,
+                gid_t *ret_gid) {
 
         assert(groupname);
-
-        /* We enforce some special rules for gid=0: in order to avoid NSS lookups for root we hardcode its data. */
+        assert(*groupname);
 
         if (STR_IN_SET(*groupname, "root", "0")) {
                 *groupname = "root";
 
-                if (gid)
-                        *gid = 0;
+                if (ret_gid)
+                        *ret_gid = 0;
 
                 return 0;
         }
@@ -360,21 +352,41 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
             synthesize_nobody()) {
                 *groupname = NOBODY_GROUP_NAME;
 
-                if (gid)
-                        *gid = GID_NOBODY;
+                if (ret_gid)
+                        *ret_gid = GID_NOBODY;
 
                 return 0;
         }
 
+        return -ENOMEDIUM;
+}
+
+int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) {
+        bool patch_groupname = false;
+        struct group *g;
+        gid_t id;
+        int r;
+
+        assert(groupname);
+        assert(*groupname);
+
+        if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
+                r = synthesize_group_creds(groupname, ret_gid);
+                if (r >= 0)
+                        return 0;
+                if (r != -ENOMEDIUM) /* not a groupname we can synthesize */
+                        return r;
+        }
+
         if (parse_gid(*groupname, &id) >= 0) {
                 errno = 0;
                 g = getgrgid(id);
 
                 if (g)
-                        *groupname = g->gr_name;
+                        patch_groupname = true;
                 else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) {
-                        if (gid)
-                                *gid = id;
+                        if (ret_gid)
+                                *ret_gid = id;
 
                         return 0;
                 }
@@ -383,18 +395,28 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
                 g = getgrnam(*groupname);
         }
 
-        if (!g)
+        if (!g) {
                 /* getgrnam() may fail with ENOENT if /etc/group is missing.
                  * For us that is equivalent to the name not being defined. */
-                return IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
+                r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
+
+                if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
+                        if (synthesize_group_creds(groupname, ret_gid) >= 0)
+                                return 0;
+
+                return r;
+        }
 
-        if (gid) {
+        if (ret_gid) {
                 if (!gid_is_valid(g->gr_gid))
                         return -EBADMSG;
 
-                *gid = g->gr_gid;
+                *ret_gid = g->gr_gid;
         }
 
+        if (patch_groupname)
+                *groupname = g->gr_name;
+
         return 0;
 }
 
@@ -409,31 +431,11 @@ char* uid_to_name(uid_t uid) {
                 return strdup(NOBODY_USER_NAME);
 
         if (uid_is_valid(uid)) {
-                long bufsize;
-
-                bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
-                if (bufsize <= 0)
-                        bufsize = 4096;
-
-                for (;;) {
-                        struct passwd pwbuf, *pw = NULL;
-                        _cleanup_free_ char *buf = NULL;
+                _cleanup_free_ struct passwd *pw = NULL;
 
-                        buf = malloc(bufsize);
-                        if (!buf)
-                                return NULL;
-
-                        r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw);
-                        if (r == 0 && pw)
-                                return strdup(pw->pw_name);
-                        if (r != ERANGE)
-                                break;
-
-                        if (bufsize > LONG_MAX/2) /* overflow check */
-                                return NULL;
-
-                        bufsize *= 2;
-                }
+                r = getpwuid_malloc(uid, &pw);
+                if (r >= 0)
+                        return strdup(pw->pw_name);
         }
 
         if (asprintf(&ret, UID_FMT, uid) < 0)
@@ -452,31 +454,11 @@ char* gid_to_name(gid_t gid) {
                 return strdup(NOBODY_GROUP_NAME);
 
         if (gid_is_valid(gid)) {
-                long bufsize;
+                _cleanup_free_ struct group *gr = NULL;
 
-                bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
-                if (bufsize <= 0)
-                        bufsize = 4096;
-
-                for (;;) {
-                        struct group grbuf, *gr = NULL;
-                        _cleanup_free_ char *buf = NULL;
-
-                        buf = malloc(bufsize);
-                        if (!buf)
-                                return NULL;
-
-                        r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr);
-                        if (r == 0 && gr)
-                                return strdup(gr->gr_name);
-                        if (r != ERANGE)
-                                break;
-
-                        if (bufsize > LONG_MAX/2) /* overflow check */
-                                return NULL;
-
-                        bufsize *= 2;
-                }
+                r = getgrgid_malloc(gid, &gr);
+                if (r >= 0)
+                        return strdup(gr->gr_name);
         }
 
         if (asprintf(&ret, GID_FMT, gid) < 0)
@@ -587,9 +569,10 @@ int getgroups_alloc(gid_t** gids) {
 }
 
 int get_home_dir(char **ret) {
-        struct passwd *p;
+        _cleanup_free_ struct passwd *p = NULL;
         const char *e;
         uid_t u;
+        int r;
 
         assert(ret);
 
@@ -604,19 +587,17 @@ int get_home_dir(char **ret) {
                 e = "/root";
                 goto found;
         }
-
         if (u == UID_NOBODY && synthesize_nobody()) {
                 e = "/";
                 goto found;
         }
 
         /* Check the database... */
-        errno = 0;
-        p = getpwuid(u);
-        if (!p)
-                return errno_or_else(ESRCH);
-        e = p->pw_dir;
+        r = getpwuid_malloc(u, &p);
+        if (r < 0)
+                return r;
 
+        e = p->pw_dir;
         if (!path_is_valid(e) || !path_is_absolute(e))
                 return -EINVAL;
 
@@ -625,9 +606,10 @@ int get_home_dir(char **ret) {
 }
 
 int get_shell(char **ret) {
-        struct passwd *p;
+        _cleanup_free_ struct passwd *p = NULL;
         const char *e;
         uid_t u;
+        int r;
 
         assert(ret);
 
@@ -648,12 +630,11 @@ int get_shell(char **ret) {
         }
 
         /* Check the database... */
-        errno = 0;
-        p = getpwuid(u);
-        if (!p)
-                return errno_or_else(ESRCH);
-        e = p->pw_shell;
+        r = getpwuid_malloc(u, &p);
+        if (r < 0)
+                return r;
 
+        e = p->pw_shell;
         if (!path_is_valid(e) || !path_is_absolute(e))
                 return -EINVAL;
 
@@ -1067,3 +1048,180 @@ const char* get_home_root(void) {
 
         return "/home";
 }
+
+static size_t getpw_buffer_size(void) {
+        long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+        return bufsize <= 0 ? 4096U : (size_t) bufsize;
+}
+
+static bool errno_is_user_doesnt_exist(int error) {
+        /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
+         * not found. */
+        return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM);
+}
+
+int getpwnam_malloc(const char *name, struct passwd **ret) {
+        size_t bufsize = getpw_buffer_size();
+        int r;
+
+        /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must
+         * free() the returned sructured! */
+
+        if (isempty(name))
+                return -EINVAL;
+
+        for (;;) {
+                _cleanup_free_ void *buf = NULL;
+
+                buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize);
+                if (!buf)
+                        return -ENOMEM;
+
+                struct passwd *pw = NULL;
+                r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
+                if (r == 0) {
+                        if (pw) {
+                                if (ret)
+                                        *ret = TAKE_PTR(buf);
+                                return 0;
+                        }
+
+                        return -ESRCH;
+                }
+
+                assert(r > 0);
+
+                /* getpwnam() may fail with ENOENT if /etc/passwd is missing.  For us that is equivalent to
+                 * the name not being defined. */
+                if (errno_is_user_doesnt_exist(r))
+                        return -ESRCH;
+                if (r != ERANGE)
+                        return -r;
+
+                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
+                        return -ENOMEM;
+                bufsize *= 2;
+        }
+}
+
+int getpwuid_malloc(uid_t uid, struct passwd **ret) {
+        size_t bufsize = getpw_buffer_size();
+        int r;
+
+        if (!uid_is_valid(uid))
+                return -EINVAL;
+
+        for (;;) {
+                _cleanup_free_ void *buf = NULL;
+
+                buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize);
+                if (!buf)
+                        return -ENOMEM;
+
+                struct passwd *pw = NULL;
+                r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
+                if (r == 0) {
+                        if (pw) {
+                                if (ret)
+                                        *ret = TAKE_PTR(buf);
+                                return 0;
+                        }
+
+                        return -ESRCH;
+                }
+
+                assert(r > 0);
+
+                if (errno_is_user_doesnt_exist(r))
+                        return -ESRCH;
+                if (r != ERANGE)
+                        return -r;
+
+                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
+                        return -ENOMEM;
+                bufsize *= 2;
+        }
+}
+
+static size_t getgr_buffer_size(void) {
+        long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
+        return bufsize <= 0 ? 4096U : (size_t) bufsize;
+}
+
+int getgrnam_malloc(const char *name, struct group **ret) {
+        size_t bufsize = getgr_buffer_size();
+        int r;
+
+        if (isempty(name))
+                return -EINVAL;
+
+        for (;;) {
+                _cleanup_free_ void *buf = NULL;
+
+                buf = malloc(ALIGN(sizeof(struct group)) + bufsize);
+                if (!buf)
+                        return -ENOMEM;
+
+                struct group *gr = NULL;
+                r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
+                if (r == 0) {
+                        if (gr) {
+                                if (ret)
+                                        *ret = TAKE_PTR(buf);
+                                return 0;
+                        }
+
+                        return -ESRCH;
+                }
+
+                assert(r > 0);
+
+                if (errno_is_user_doesnt_exist(r))
+                        return -ESRCH;
+                if (r != ERANGE)
+                        return -r;
+
+                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
+                        return -ENOMEM;
+                bufsize *= 2;
+        }
+}
+
+int getgrgid_malloc(gid_t gid, struct group **ret) {
+        size_t bufsize = getgr_buffer_size();
+        int r;
+
+        if (!gid_is_valid(gid))
+                return -EINVAL;
+
+        for (;;) {
+                _cleanup_free_ void *buf = NULL;
+
+                buf = malloc(ALIGN(sizeof(struct group)) + bufsize);
+                if (!buf)
+                        return -ENOMEM;
+
+                struct group *gr = NULL;
+                r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
+                if (r == 0) {
+                        if (gr) {
+                                if (ret)
+                                        *ret = TAKE_PTR(buf);
+                                return 0;
+                        }
+
+                        return -ESRCH;
+                }
+
+                assert(r > 0);
+
+                if (errno_is_user_doesnt_exist(r))
+                        return -ESRCH;
+                if (r != ERANGE)
+                        return -r;
+
+                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
+                        return -ENOMEM;
+                bufsize *= 2;
+        }
+}
index b3e254662eba843f73c3e809f472865731e6e6aa..9d07ef31d22d852dcb93f1d717e2ff09d38aa2ef 100644 (file)
@@ -42,8 +42,8 @@ typedef enum UserCredsFlags {
         USER_CREDS_CLEAN         = 1 << 2,  /* try to clean up shell and home fields with invalid data */
 } UserCredsFlags;
 
-int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell, UserCredsFlags flags);
-int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags);
+int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags);
+int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags);
 
 char* uid_to_name(uid_t uid);
 char* gid_to_name(gid_t gid);
@@ -158,3 +158,9 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) {
  * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325.
  */
 #define PASSWORD_UNPROVISIONED "!unprovisioned"
+
+int getpwuid_malloc(uid_t uid, struct passwd **ret);
+int getpwnam_malloc(const char *name, struct passwd **ret);
+
+int getgrnam_malloc(const char *name, struct group **ret);
+int getgrgid_malloc(gid_t gid, struct group **ret);
index 9c1410886f49fe248245da51f8dde5f5dea8c433..465f339c146801d7627d18f9ba95ad59e0552a86 100644 (file)
@@ -54,6 +54,7 @@ static Virtualization detect_vm_cpuid(void) {
                 { "ACRNACRNACRN", VIRTUALIZATION_ACRN      },
                 /* https://www.lockheedmartin.com/en-us/products/Hardened-Security-for-Intel-Processors.html */
                 { "SRESRESRESRE", VIRTUALIZATION_SRE       },
+                { "Apple VZ",     VIRTUALIZATION_APPLE     },
         };
 
         uint32_t eax, ebx, ecx, edx;
index 58470385df27025fa3ff51a005ae3f8819e1617e..a0a474537effe39f69ca36ded84b0d09ab072a59 100644 (file)
@@ -81,11 +81,13 @@ static int load_etc_machine_info(void) {
         return 0;
 }
 
-static int load_etc_kernel_install_conf(void) {
+static int load_kernel_install_conf_one(const char *dir) {
         _cleanup_free_ char *layout = NULL, *p = NULL;
         int r;
 
-        p = path_join(arg_root, etc_kernel(), "install.conf");
+        assert(dir);
+
+        p = path_join(arg_root, dir, "install.conf");
         if (!p)
                 return log_oom();
 
@@ -100,6 +102,23 @@ static int load_etc_kernel_install_conf(void) {
                 free_and_replace(arg_install_layout, layout);
         }
 
+        return 1;
+}
+
+static int load_kernel_install_conf(void) {
+        const char *conf_root;
+        int r;
+
+        conf_root = getenv("KERNEL_INSTALL_CONF_ROOT");
+        if (conf_root)
+                return load_kernel_install_conf_one(conf_root);
+
+        FOREACH_STRING(p, "/etc/kernel", "/usr/lib/kernel") {
+                r = load_kernel_install_conf_one(p);
+                if (r != 0)
+                        return r;
+        }
+
         return 0;
 }
 
@@ -120,7 +139,7 @@ static int settle_make_entry_directory(void) {
         if (r < 0)
                 return r;
 
-        r = load_etc_kernel_install_conf();
+        r = load_kernel_install_conf();
         if (r < 0)
                 return r;
 
@@ -557,7 +576,7 @@ static int install_entry_token(void) {
         if (!arg_make_entry_directory && arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID)
                 return 0;
 
-        p = path_join(arg_root, etc_kernel(), "entry-token");
+        p = path_join(arg_root, getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/", "entry-token");
         if (!p)
                 return log_oom();
 
index 3cab875fa0647ee2b43f31f49686225c5b04c400..448e868a4241cd4d6b0aa25fe4b1e23f95c40537 100644 (file)
@@ -119,7 +119,7 @@ int settle_entry_token(void) {
 
         r = boot_entry_token_ensure(
                         arg_root,
-                        etc_kernel(),
+                        getenv("KERNEL_INSTALL_CONF_ROOT"),
                         arg_machine_id,
                         /* machine_id_is_random = */ false,
                         &arg_entry_token_type,
index 147455e2411acbc1c7198186a6c3cee3c8325d87..6f2c1630de2f9e7619b27ce40ffcd83f46652dc7 100644 (file)
@@ -8,7 +8,3 @@ const char *get_efi_arch(void);
 int get_file_version(int fd, char **ret);
 
 int settle_entry_token(void);
-
-static inline const char* etc_kernel(void) {
-        return getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/";
-}
index b13978051035aa4a25db98d071fbd5ec0c92bdc0..61a43cd77d20efed8dbc32dc4f60185d2e82270a 100644 (file)
@@ -33,8 +33,9 @@ static EFI_STATUS devicetree_fixup(struct devicetree_state *state, size_t len) {
         assert(state);
 
         err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_DT_FIXUP_PROTOCOL), NULL, (void **) &fixup);
+        /* Skip fixup if we cannot locate device tree fixup protocol */
         if (err != EFI_SUCCESS)
-                return log_error_status(EFI_SUCCESS, "Could not locate device tree fixup protocol, skipping.");
+                return EFI_SUCCESS;
 
         size = devicetree_allocated(state);
         err = fixup->Fixup(fixup, PHYSICAL_ADDRESS_TO_POINTER(state->addr), &size,
index aef831d1327f6548e4eebe505d99b0160007918b..6e15a8b85dc4a5287c10a660e7acfa2ee3f6c1e3 100644 (file)
@@ -36,7 +36,7 @@ static inline void *xmalloc(size_t size) {
 }
 
 _malloc_ _alloc_(1, 2) _returns_nonnull_ _warn_unused_result_
-static inline void *xmalloc_multiply(size_t size, size_t n) {
+static inline void *xmalloc_multiply(size_t n, size_t size) {
         assert_se(!__builtin_mul_overflow(size, n, &size));
         return xmalloc(size);
 }
@@ -57,7 +57,7 @@ static inline void* xmemdup(const void *p, size_t l) {
         return memcpy(xmalloc(l), p, l);
 }
 
-#define xnew(type, n) ((type *) xmalloc_multiply(sizeof(type), (n)))
+#define xnew(type, n) ((type *) xmalloc_multiply((n), sizeof(type)))
 
 typedef struct {
         EFI_PHYSICAL_ADDRESS addr;
index 262ef89f1a75cceef4ec88fd456e7d94ea42d850..dea112add0983ab8a37bb129f234bd5f11668345 100644 (file)
@@ -300,12 +300,11 @@ static int parse_argv(int argc, char *argv[]) {
         if (strv_isempty(arg_phase)) {
                 /* If no phases are specifically selected, pick everything from the beginning of the initrd
                  * to the beginning of shutdown. */
-                if (strv_extend_strv(&arg_phase,
-                                     STRV_MAKE("enter-initrd",
-                                               "enter-initrd:leave-initrd",
-                                               "enter-initrd:leave-initrd:sysinit",
-                                               "enter-initrd:leave-initrd:sysinit:ready"),
-                                     /* filter_duplicates= */ false) < 0)
+                if (strv_extend_many(&arg_phase,
+                                     "enter-initrd",
+                                     "enter-initrd:leave-initrd",
+                                     "enter-initrd:leave-initrd:sysinit",
+                                     "enter-initrd:leave-initrd:sysinit:ready") < 0)
                         return log_oom();
         } else {
                 strv_sort(arg_phase);
index e34da7cf7282e7577dbc0f96b93dc82b5572fe77..ca514554408e8749f6d0e71e0dd63660cf1f3b92 100644 (file)
@@ -310,9 +310,9 @@ static int process(
 
                         if (all_unified) {
                                 while (!isempty(l)) {
-                                        if (sscanf(l, "rbytes=%" SCNu64, &k))
+                                        if (sscanf(l, "rbytes=%" SCNu64, &k) == 1)
                                                 rd += k;
-                                        else if (sscanf(l, "wbytes=%" SCNu64, &k))
+                                        else if (sscanf(l, "wbytes=%" SCNu64, &k) == 1)
                                                 wr += k;
 
                                         l += strcspn(l, WHITESPACE);
index d5df93940b9dd79985bfc300e0e2233b293e913b..f82de9b673b3a9b80e27c839ed4377f76850922c 100644 (file)
@@ -3858,7 +3858,7 @@ static void unit_add_to_cgroup_oom_queue(Unit *u) {
                         return;
                 }
 
-                r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_NORMAL-8);
+                r = sd_event_source_set_priority(s, EVENT_PRIORITY_CGROUP_OOM);
                 if (r < 0) {
                         log_error_errno(r, "Failed to set priority of cgroup oom event source: %m");
                         return;
@@ -4064,7 +4064,7 @@ int manager_setup_cgroup(Manager *m) {
         /* Schedule cgroup empty checks early, but after having processed service notification messages or
          * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of
          * notification, and we collected the metadata the notification and SIGCHLD stuff offers first. */
-        r = sd_event_source_set_priority(m->cgroup_empty_event_source, SD_EVENT_PRIORITY_NORMAL-5);
+        r = sd_event_source_set_priority(m->cgroup_empty_event_source, EVENT_PRIORITY_CGROUP_EMPTY);
         if (r < 0)
                 return log_error_errno(r, "Failed to set priority of cgroup empty event source: %m");
 
@@ -4093,7 +4093,7 @@ int manager_setup_cgroup(Manager *m) {
                 /* Process cgroup empty notifications early. Note that when this event is dispatched it'll
                  * just add the unit to a cgroup empty queue, hence let's run earlier than that. Also see
                  * handling of cgroup agent notifications, for the classic cgroup hierarchy support. */
-                r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_NORMAL-9);
+                r = sd_event_source_set_priority(m->cgroup_inotify_event_source, EVENT_PRIORITY_CGROUP_INOTIFY);
                 if (r < 0)
                         return log_error_errno(r, "Failed to set priority of inotify event source: %m");
 
@@ -5032,10 +5032,10 @@ static const char* const cgroup_memory_accounting_metric_table[_CGROUP_MEMORY_AC
 
 DEFINE_STRING_TABLE_LOOKUP(cgroup_memory_accounting_metric, CGroupMemoryAccountingMetric);
 
-static const char *const cgroup_limit_type_table[_CGROUP_LIMIT_TYPE_MAX] = {
+static const char *const cgroup_effective_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);
+DEFINE_STRING_TABLE_LOOKUP(cgroup_effective_limit_type, CGroupLimitType);
index c56979de01fd331564b2af6716b2ae3aa5111892..0e90b6bd22c25c55a46ab0421e5e45d0183a10c8 100644 (file)
@@ -448,8 +448,8 @@ CGroupIPAccountingMetric cgroup_ip_accounting_metric_from_string(const char *s)
 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_effective_limit_type_to_string(CGroupLimitType m) _const_;
+CGroupLimitType cgroup_effective_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_;
index cd913817d2b897927c79d7406ba72460de51c4e6..10650421062a500412678b62f6004ae648132ffd 100644 (file)
@@ -527,7 +527,7 @@ static int manager_varlink_init_system(Manager *m) {
                 }
         }
 
-        r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL);
+        r = varlink_server_attach_event(s, m->event, EVENT_PRIORITY_IPC);
         if (r < 0)
                 return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
 
@@ -585,7 +585,7 @@ static int manager_varlink_init_user(Manager *m) {
         if (r < 0)
                 return r;
 
-        r = varlink_attach_event(link, m->event, SD_EVENT_PRIORITY_NORMAL);
+        r = varlink_attach_event(link, m->event, EVENT_PRIORITY_IPC);
         if (r < 0)
                 return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
 
index 1830d697848bbc5e9da3e1c0932bbb4b1cb8aa11..602fec13143c35e43b83dd869d1ee2e2f5d05b76 100644 (file)
@@ -1949,7 +1949,7 @@ int bus_exec_context_set_transient_property(
 
                                 r = strv_extend_strv(&c->supplementary_groups, l, true);
                                 if (r < 0)
-                                        return -ENOMEM;
+                                        return r;
 
                                 joined = strv_join(c->supplementary_groups, " ");
                                 if (!joined)
@@ -3174,7 +3174,7 @@ int bus_exec_context_set_transient_property(
 
                                 r = strv_extend_strv(dirs, l, true);
                                 if (r < 0)
-                                        return -ENOMEM;
+                                        return r;
 
                                 unit_write_settingf(u, flags, name, "%s=%s", name, joined);
                         }
@@ -3201,7 +3201,7 @@ int bus_exec_context_set_transient_property(
                                 _cleanup_free_ char *joined = NULL;
                                 r = strv_extend_strv(&c->exec_search_path, l, true);
                                 if (r < 0)
-                                        return -ENOMEM;
+                                        return r;
                                 joined = strv_join(c->exec_search_path, ":");
                                 if (!joined)
                                         return log_oom();
index 3aeb85e45248e146f4bbc6a9ee0adba6647f20a9..83d2302faadf4b0627237d9e2e5ddf0cb918a7f2 100644 (file)
@@ -1443,7 +1443,7 @@ static int property_get_effective_limit(
         assert(reply);
         assert(property);
 
-        assert_se((type = cgroup_limit_type_from_string(property)) >= 0);
+        assert_se((type = cgroup_effective_limit_type_from_string(property)) >= 0);
         (void) unit_get_effective_limit(u, type, &value);
         return sd_bus_message_append(reply, "t", value);
 }
@@ -2271,7 +2271,9 @@ static int bus_unit_set_transient_property(
                                 u->documentation = strv_free(u->documentation);
                                 unit_write_settingf(u, flags, name, "%s=", name);
                         } else {
-                                strv_extend_strv(&u->documentation, l, false);
+                                r = strv_extend_strv(&u->documentation, l, /* filter_duplicates= */ false);
+                                if (r < 0)
+                                        return r;
 
                                 STRV_FOREACH(p, l)
                                         unit_write_settingf(u, flags, name, "%s=%s", name, *p);
index e24c5bbc53394140e9a5276b01d3642b9c8ad1e8..49a80bbcfa91a0900637837c5ca58980267d6ac0 100644 (file)
@@ -739,7 +739,7 @@ static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void
                 log_debug("Accepting direct incoming connection from " PID_FMT " (%s) [%s]", pid, strna(comm), strna(description));
         }
 
-        r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL);
+        r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC);
         if (r < 0) {
                 log_warning_errno(r, "Failed to attach new connection bus to event loop: %m");
                 return 0;
@@ -847,7 +847,7 @@ int bus_init_api(Manager *m) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to connect to API bus: %m");
 
-                r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL);
+                r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC);
                 if (r < 0)
                         return log_error_errno(r, "Failed to attach API bus to event loop: %m");
 
@@ -904,7 +904,7 @@ int bus_init_system(Manager *m) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to connect to system bus: %m");
 
-                r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL);
+                r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC);
                 if (r < 0)
                         return log_error_errno(r, "Failed to attach system bus to event loop: %m");
 
index 484b0e29abdce7218c8d4eead231635b74cd9bb8..8462a31e5b541706c91fc06e0473232887a59d95 100644 (file)
@@ -293,8 +293,8 @@ static int pick_uid(char **suggested_paths, const char *name, uid_t *ret_uid) {
                 }
 
                 /* Some superficial check whether this UID/GID might already be taken by some static user */
-                if (getpwuid(candidate) ||
-                    getgrgid((gid_t) candidate) ||
+                if (getpwuid_malloc(candidate, /* ret= */ NULL) >= 0 ||
+                    getgrgid_malloc((gid_t) candidate, /* ret= */ NULL) >= 0 ||
                     search_ipc(candidate, (gid_t) candidate) != 0) {
                         (void) unlink(lock_path);
                         continue;
@@ -417,30 +417,26 @@ static int dynamic_user_realize(
                 /* First, let's parse this as numeric UID */
                 r = parse_uid(d->name, &num);
                 if (r < 0) {
-                        struct passwd *p;
-                        struct group *g;
+                        _cleanup_free_ struct passwd *p = NULL;
+                        _cleanup_free_ struct group *g = NULL;
 
                         if (is_user) {
                                 /* OK, this is not a numeric UID. Let's see if there's a user by this name */
-                                p = getpwnam(d->name);
-                                if (p) {
+                                if (getpwnam_malloc(d->name, &p) >= 0) {
                                         num = p->pw_uid;
                                         gid = p->pw_gid;
                                 } else {
                                         /* if the user does not exist but the group with the same name exists, refuse operation */
-                                        g = getgrnam(d->name);
-                                        if (g)
+                                        if (getgrnam_malloc(d->name, /* ret= */ NULL) >= 0)
                                                 return -EILSEQ;
                                 }
                         } else {
                                 /* Let's see if there's a group by this name */
-                                g = getgrnam(d->name);
-                                if (g)
+                                if (getgrnam_malloc(d->name, &g) >= 0)
                                         num = (uid_t) g->gr_gid;
                                 else {
                                         /* if the group does not exist but the user with the same name exists, refuse operation */
-                                        p = getpwnam(d->name);
-                                        if (p)
+                                        if (getpwnam_malloc(d->name, /* ret= */ NULL) >= 0)
                                                 return -EILSEQ;
                                 }
                         }
@@ -482,13 +478,12 @@ static int dynamic_user_realize(
                         uid_lock_fd = new_uid_lock_fd;
                 }
         } else if (is_user && !uid_is_dynamic(num)) {
-                struct passwd *p;
+                _cleanup_free_ struct passwd *p = NULL;
 
                 /* Statically allocated user may have different uid and gid. So, let's obtain the gid. */
-                errno = 0;
-                p = getpwuid(num);
-                if (!p)
-                        return errno_or_else(ESRCH);
+                r = getpwuid_malloc(num, &p);
+                if (r < 0)
+                        return r;
 
                 gid = p->pw_gid;
         }
index 4aa3e35bd85bdff7ee564e6d2c09ecf86e21b274..41c0fce13b5ebe8ae575c247f2fe73bb46be26ba 100644 (file)
@@ -244,9 +244,8 @@ static char **credential_search_path(const ExecParameters *params, CredentialSea
         }
 
         if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) {
-                if (params->received_credentials_directory)
-                        if (strv_extend(&l, params->received_credentials_directory) < 0)
-                                return NULL;
+                if (strv_extend(&l, params->received_credentials_directory) < 0)
+                        return NULL;
 
                 if (strv_extend_strv(&l, CONF_PATHS_STRV("credstore"), /* filter_duplicates= */ true) < 0)
                         return NULL;
index f39b53280067157b1db4cee58458fd9e0b7af040..1ae766d93ede9e51f0705ba3bbd7fa5060a51376 100644 (file)
@@ -2811,11 +2811,10 @@ static int compile_symlinks(
          * absolute, when they are processed in namespace.c they will be made relative automatically, i.e.:
          * 'os-release -> .os-release-stage/os-release' is what will be created. */
         if (setup_os_release_symlink) {
-                r = strv_extend(&symlinks, "/run/host/.os-release-stage/os-release");
-                if (r < 0)
-                        return r;
-
-                r = strv_extend(&symlinks, "/run/host/os-release");
+                r = strv_extend_many(
+                                &symlinks,
+                                "/run/host/.os-release-stage/os-release",
+                                "/run/host/os-release");
                 if (r < 0)
                         return r;
         }
index e9d567a97b8fe03cc23d28ba8f2ca850e792a441..0d91faee6f214610880096ada7ce6db9063d9333 100644 (file)
@@ -494,8 +494,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         (void) exec_shared_runtime_deserialize_one(m, val, fds);
                 else if ((val = startswith(l, "subscribed="))) {
 
-                        if (strv_extend(&m->deserialized_subscribed, val) < 0)
-                                return -ENOMEM;
+                        r = strv_extend(&m->deserialized_subscribed, val);
+                        if (r < 0)
+                                return r;
                 } else if ((val = startswith(l, "varlink-server-socket-address="))) {
                         if (!m->varlink_server && MANAGER_IS_SYSTEM(m)) {
                                 r = manager_varlink_init(m);
index 95c5f6381d3f26a27c033e472f62e995df433a39..462baab36a6f085bf5c0180eb7d7d15f79bbb104 100644 (file)
@@ -397,7 +397,7 @@ static int manager_setup_time_change(Manager *m) {
                 return log_error_errno(r, "Failed to create time change event source: %m");
 
         /* Schedule this slightly earlier than the .timer event sources */
-        r = sd_event_source_set_priority(m->time_change_event_source, SD_EVENT_PRIORITY_NORMAL-1);
+        r = sd_event_source_set_priority(m->time_change_event_source, EVENT_PRIORITY_TIME_CHANGE);
         if (r < 0)
                 return log_error_errno(r, "Failed to set priority of time change event sources: %m");
 
@@ -464,7 +464,7 @@ static int manager_setup_timezone_change(Manager *m) {
                 return log_error_errno(r, "Failed to create timezone change event source: %m");
 
         /* Schedule this slightly earlier than the .timer event sources */
-        r = sd_event_source_set_priority(new_event, SD_EVENT_PRIORITY_NORMAL-1);
+        r = sd_event_source_set_priority(new_event, EVENT_PRIORITY_TIME_ZONE);
         if (r < 0)
                 return log_error_errno(r, "Failed to set priority of timezone change event sources: %m");
 
@@ -592,7 +592,7 @@ static int manager_setup_signals(Manager *m) {
          * notify processing can still figure out to which process/service a message belongs, before we reap the
          * process. Also, process this before handling cgroup notifications, so that we always collect child exit
          * status information before detecting that there's no process in a cgroup. */
-        r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6);
+        r = sd_event_source_set_priority(m->signal_event_source, EVENT_PRIORITY_SIGNALS);
         if (r < 0)
                 return r;
 
@@ -736,7 +736,7 @@ static int manager_setup_run_queue(Manager *m) {
         if (r < 0)
                 return r;
 
-        r = sd_event_source_set_priority(m->run_queue_event_source, SD_EVENT_PRIORITY_IDLE);
+        r = sd_event_source_set_priority(m->run_queue_event_source, EVENT_PRIORITY_RUN_QUEUE);
         if (r < 0)
                 return r;
 
@@ -759,7 +759,7 @@ static int manager_setup_sigchld_event_source(Manager *m) {
         if (r < 0)
                 return r;
 
-        r = sd_event_source_set_priority(m->sigchld_event_source, SD_EVENT_PRIORITY_NORMAL-7);
+        r = sd_event_source_set_priority(m->sigchld_event_source, EVENT_PRIORITY_SIGCHLD);
         if (r < 0)
                 return r;
 
@@ -1016,9 +1016,9 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags,
 
                 m->executor_fd = open(SYSTEMD_EXECUTOR_BINARY_PATH, O_CLOEXEC|O_PATH);
                 if (m->executor_fd < 0)
-                        return log_warning_errno(errno,
-                                                 "Failed to open executor binary '%s': %m",
-                                                 SYSTEMD_EXECUTOR_BINARY_PATH);
+                        return log_emergency_errno(errno,
+                                                   "Failed to open executor binary '%s': %m",
+                                                   SYSTEMD_EXECUTOR_BINARY_PATH);
         } else if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) {
                 _cleanup_free_ char *self_exe = NULL, *executor_path = NULL;
                 _cleanup_close_ int self_dir_fd = -EBADF;
@@ -1113,7 +1113,7 @@ static int manager_setup_notify(Manager *m) {
 
                 /* Process notification messages a bit earlier than SIGCHLD, so that we can still identify to which
                  * service an exit message belongs. */
-                r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-8);
+                r = sd_event_source_set_priority(m->notify_event_source, EVENT_PRIORITY_NOTIFY);
                 if (r < 0)
                         return log_error_errno(r, "Failed to set priority of notify event source: %m");
 
@@ -1187,7 +1187,7 @@ static int manager_setup_cgroups_agent(Manager *m) {
                 /* Process cgroups notifications early. Note that when the agent notification is received
                  * we'll just enqueue the unit in the cgroup empty queue, hence pick a high priority than
                  * that. Also see handling of cgroup inotify for the unified cgroup stuff. */
-                r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-9);
+                r = sd_event_source_set_priority(m->cgroups_agent_event_source, EVENT_PRIORITY_CGROUP_AGENT);
                 if (r < 0)
                         return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m");
 
@@ -1240,7 +1240,7 @@ static int manager_setup_user_lookup_fd(Manager *m) {
 
                 /* Process even earlier than the notify event source, so that we always know first about valid UID/GID
                  * resolutions */
-                r = sd_event_source_set_priority(m->user_lookup_event_source, SD_EVENT_PRIORITY_NORMAL-11);
+                r = sd_event_source_set_priority(m->user_lookup_event_source, EVENT_PRIORITY_USER_LOOKUP);
                 if (r < 0)
                         return log_error_errno(errno, "Failed to set priority of user lookup event source: %m");
 
index d7bc6e761d40ce740a601fc2540ffc2f7c28886d..ba7c1e28c93bd48399323c1cc5b43dfbfb5e5b00 100644 (file)
@@ -644,3 +644,25 @@ OOMPolicy oom_policy_from_string(const char *s) _pure_;
 
 void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope);
 void unit_defaults_done(UnitDefaults *defaults);
+
+enum {
+        /* most important … */
+        EVENT_PRIORITY_USER_LOOKUP      = SD_EVENT_PRIORITY_NORMAL-10,
+        EVENT_PRIORITY_MOUNT_TABLE      = SD_EVENT_PRIORITY_NORMAL-9,
+        EVENT_PRIORITY_SWAP_TABLE       = SD_EVENT_PRIORITY_NORMAL-9,
+        EVENT_PRIORITY_CGROUP_AGENT     = SD_EVENT_PRIORITY_NORMAL-8, /* cgroupv1 */
+        EVENT_PRIORITY_CGROUP_INOTIFY   = SD_EVENT_PRIORITY_NORMAL-8, /* cgroupv2 */
+        EVENT_PRIORITY_CGROUP_OOM       = SD_EVENT_PRIORITY_NORMAL-7,
+        EVENT_PRIORITY_EXEC_FD          = SD_EVENT_PRIORITY_NORMAL-6,
+        EVENT_PRIORITY_NOTIFY           = SD_EVENT_PRIORITY_NORMAL-5,
+        EVENT_PRIORITY_SIGCHLD          = SD_EVENT_PRIORITY_NORMAL-4,
+        EVENT_PRIORITY_SIGNALS          = SD_EVENT_PRIORITY_NORMAL-3,
+        EVENT_PRIORITY_CGROUP_EMPTY     = SD_EVENT_PRIORITY_NORMAL-2,
+        EVENT_PRIORITY_TIME_CHANGE      = SD_EVENT_PRIORITY_NORMAL-1,
+        EVENT_PRIORITY_TIME_ZONE        = SD_EVENT_PRIORITY_NORMAL-1,
+        EVENT_PRIORITY_IPC              = SD_EVENT_PRIORITY_NORMAL,
+        EVENT_PRIORITY_REWATCH_PIDS     = SD_EVENT_PRIORITY_IDLE,
+        EVENT_PRIORITY_SERVICE_WATCHDOG = SD_EVENT_PRIORITY_IDLE+1,
+        EVENT_PRIORITY_RUN_QUEUE        = SD_EVENT_PRIORITY_IDLE+2,
+        /* … to least important */
+};
index ff97eebb597fd64bf1d757e7e3cfc055da96c907..048802fa1be85e230d88f945d75c8ef5a4891841 100644 (file)
@@ -2090,7 +2090,7 @@ static void mount_enumerate(Manager *m) {
                         goto fail;
                 }
 
-                r = sd_event_source_set_priority(m->mount_event_source, SD_EVENT_PRIORITY_NORMAL-10);
+                r = sd_event_source_set_priority(m->mount_event_source, EVENT_PRIORITY_MOUNT_TABLE);
                 if (r < 0) {
                         log_error_errno(r, "Failed to adjust mount watch priority: %m");
                         goto fail;
index f6b5683467a13bac47c6aadee67ea3c247334657..00da301fd2225caf0dc752895c64579150acfc41 100644 (file)
@@ -582,7 +582,7 @@ static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify)
 
         /* If the triggered unit is already running, so are we */
         trigger = UNIT_TRIGGER(UNIT(p));
-        if (trigger && !UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(trigger))) {
+        if (trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger))) {
                 path_set_state(p, PATH_RUNNING);
                 path_unwatch(p);
                 return;
@@ -853,11 +853,11 @@ static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer) {
                 return;
 
         if (p->state == PATH_RUNNING &&
-            UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
+            UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
                 if (!on_defer)
                         log_unit_debug(u, "Got notified about unit deactivation.");
         } else if (p->state == PATH_WAITING &&
-                   !UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
+                   !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
                 if (!on_defer)
                         log_unit_debug(u, "Got notified about unit activation.");
         } else
@@ -994,11 +994,7 @@ static int activation_details_path_append_pair(ActivationDetails *details, char
         if (isempty(p->trigger_path_filename))
                 return 0;
 
-        r = strv_extend(strv, "trigger_path");
-        if (r < 0)
-                return r;
-
-        r = strv_extend(strv, p->trigger_path_filename);
+        r = strv_extend_many(strv, "trigger_path", p->trigger_path_filename);
         if (r < 0)
                 return r;
 
index 9a26bc174424ace2d2249b8b3f0f008f197709d7..aacfe0d57e855a8c2268f9e9122b79d9c79adea5 100644 (file)
@@ -291,7 +291,7 @@ static void service_start_watchdog(Service *s) {
 
                 /* Let's process everything else which might be a sign
                  * of living before we consider a service died. */
-                r = sd_event_source_set_priority(s->watchdog_event_source, SD_EVENT_PRIORITY_IDLE);
+                r = sd_event_source_set_priority(s->watchdog_event_source, EVENT_PRIORITY_SERVICE_WATCHDOG);
         }
         if (r < 0)
                 log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m");
@@ -1512,9 +1512,10 @@ static int service_allocate_exec_fd_event_source(
         if (r < 0)
                 return log_unit_error_errno(UNIT(s), r, "Failed to allocate exec_fd event source: %m");
 
-        /* This is a bit lower priority than SIGCHLD, as that carries a lot more interesting failure information */
+        /* This is a bit higher priority than SIGCHLD, to make sure we don't confuse the case "failed to
+         * start" from the case "succeeded to start, but failed immediately after". */
 
-        r = sd_event_source_set_priority(source, SD_EVENT_PRIORITY_NORMAL-3);
+        r = sd_event_source_set_priority(source, EVENT_PRIORITY_EXEC_FD);
         if (r < 0)
                 return log_unit_error_errno(UNIT(s), r, "Failed to adjust priority of exec_fd event source: %m");
 
@@ -3098,7 +3099,7 @@ int service_deserialize_exec_command(
                 case STATE_EXEC_COMMAND_ARGS:
                         r = strv_extend(&argv, arg);
                         if (r < 0)
-                                return -ENOMEM;
+                                return r;
                         break;
                 default:
                         assert_not_reached();
index ce35a5c4413adcad0c1dd06e78c765944b9e7be4..9f7c12b0c3cf12c73ab7d2e9b0a0f86280536861 100644 (file)
@@ -1358,7 +1358,7 @@ static void swap_enumerate(Manager *m) {
                 /* Dispatch this before we dispatch SIGCHLD, so that
                  * we always get the events from /proc/swaps before
                  * the SIGCHLD of /sbin/swapon. */
-                r = sd_event_source_set_priority(m->swap_event_source, SD_EVENT_PRIORITY_NORMAL-10);
+                r = sd_event_source_set_priority(m->swap_event_source, EVENT_PRIORITY_SWAP_TABLE);
                 if (r < 0) {
                         log_error_errno(r, "Failed to change /proc/swaps priority: %m");
                         goto fail;
index 011261a7fcaa92840785d1a7089ab7d2a9c8efc2..d9e67067425811765c829db3c9326fc0104c252a 100644 (file)
@@ -2992,7 +2992,7 @@ int unit_enqueue_rewatch_pids(Unit *u) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to allocate event source for tidying watched PIDs: %m");
 
-                r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
+                r = sd_event_source_set_priority(s, EVENT_PRIORITY_REWATCH_PIDS);
                 if (r < 0)
                         return log_error_errno(r, "Failed to adjust priority of event source for tidying watched PIDs: %m");
 
@@ -6530,16 +6530,12 @@ int activation_details_append_pair(ActivationDetails *details, char ***strv) {
                 return 0;
 
         if (!isempty(details->trigger_unit_name)) {
-                r = strv_extend(strv, "trigger_unit");
-                if (r < 0)
-                        return r;
-
-                r = strv_extend(strv, details->trigger_unit_name);
+                r = strv_extend_many(strv, "trigger_unit", details->trigger_unit_name);
                 if (r < 0)
                         return r;
         }
 
-        if (ACTIVATION_DETAILS_VTABLE(details)->append_env) {
+        if (ACTIVATION_DETAILS_VTABLE(details)->append_pair) {
                 r = ACTIVATION_DETAILS_VTABLE(details)->append_pair(details, strv);
                 if (r < 0)
                         return r;
index 53370c9f6e4238a13f26ae7ddd6e773942d37c34..90b2fe4a7a5ec3128c196bf3842e018314b8418f 100644 (file)
@@ -1242,7 +1242,7 @@ static int run_debug(int argc, char **argv, void *userdata) {
         if (r < 0)
                 return r;
 
-        r = strv_extend_strv(&debugger_call, STRV_MAKE(exe, "-c", path), false);
+        r = strv_extend_many(&debugger_call, exe, "-c", path);
         if (r < 0)
                 return log_oom();
 
@@ -1251,14 +1251,14 @@ static int run_debug(int argc, char **argv, void *userdata) {
                         const char *sysroot_cmd;
                         sysroot_cmd = strjoina("set sysroot ", arg_root);
 
-                        r = strv_extend_strv(&debugger_call, STRV_MAKE("-iex", sysroot_cmd), false);
+                        r = strv_extend_many(&debugger_call, "-iex", sysroot_cmd);
                         if (r < 0)
                                 return log_oom();
                 } else if (streq(arg_debugger, "lldb")) {
                         const char *sysroot_cmd;
                         sysroot_cmd = strjoina("platform select --sysroot ", arg_root, " host");
 
-                        r = strv_extend_strv(&debugger_call, STRV_MAKE("-O", sysroot_cmd), false);
+                        r = strv_extend_many(&debugger_call, "-O", sysroot_cmd);
                         if (r < 0)
                                 return log_oom();
                 }
index c9d1a6e8d94f3029db33005b885329bd581a8bd2..bbc705c0069b5c9833e5847c9ed172d66d99563a 100644 (file)
@@ -996,8 +996,6 @@ static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth
 
         assert(link);
 
-        json_variant_sensitive(parameters);
-
         r = varlink_dispatch(link, parameters, dispatch_table, &p);
         if (r != 0)
                 return r;
@@ -1079,9 +1077,6 @@ static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth
 
         assert(link);
 
-        /* Let's also mark the (theoretically encrypted) input as sensitive, in case the NULL encryption scheme was used. */
-        json_variant_sensitive(parameters);
-
         r = varlink_dispatch(link, parameters, dispatch_table, &p);
         if (r != 0)
                 return r;
@@ -1144,7 +1139,7 @@ static int run(int argc, char *argv[]) {
 
                 /* Invocation as Varlink service */
 
-                r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
+                r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
                 if (r < 0)
                         return log_error_errno(r, "Failed to allocate Varlink server: %m");
 
index 687b908f060c97f65e108544c322430fa6e69d8c..e1fdc3f5f02448099185f2bd19da9a8fd38c6de1 100644 (file)
@@ -488,7 +488,7 @@ static int parse_argv(int argc, char *argv[]) {
                                         if (n > INT_MAX)
                                                 return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Slot index out of range: %u", n);
 
-                                        a = reallocarray(arg_wipe_slots, sizeof(int), arg_n_wipe_slots + 1);
+                                        a = reallocarray(arg_wipe_slots, arg_n_wipe_slots + 1, sizeof(int));
                                         if (!a)
                                                 return log_oom();
 
index 2b080257fed7363fff77e02c8a3f12a19a17ad69..f6a8161d408168b6737835e7ecb25351374628fb 100644 (file)
@@ -150,7 +150,7 @@ static int help(void) {
                "  -u --umount             Unmount the image from the specified directory\n"
                "  -U                      Shortcut for --umount --rmdir\n"
                "     --attach             Attach the disk image to a loopback block device\n"
-               "     --detach             Detach a loopback block device gain\n"
+               "     --detach             Detach a loopback block device again\n"
                "  -l --list               List all the files and directories of the specified\n"
                "                          OS image\n"
                "     --mtree              Show BSD mtree manifest of OS image\n"
@@ -1273,8 +1273,7 @@ static int mtree_print_item(
 
 static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
         _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
-        _cleanup_(rmdir_and_freep) char *created_dir = NULL;
-        _cleanup_free_ char *temp = NULL;
+        _cleanup_free_ char *t = NULL;
         const char *root;
         int r;
 
@@ -1288,19 +1287,13 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to detach mount namespace: %m");
 
-                r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to generate temporary mount directory: %m");
-
-                r = mkdir_p(temp, 0700);
+                r = get_common_dissect_directory(&t);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to create mount point: %m");
-
-                created_dir = TAKE_PTR(temp);
+                        return log_error_errno(r, "Failed generate private mount directory: %m");
 
                 r = dissected_image_mount_and_warn(
                                 m,
-                                created_dir,
+                                t,
                                 /* uid_shift= */ UID_INVALID,
                                 /* uid_range= */ UID_INVALID,
                                 /* userns_fd= */ -EBADF,
@@ -1308,7 +1301,7 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
                 if (r < 0)
                         return r;
 
-                mounted_dir = TAKE_PTR(created_dir);
+                mounted_dir = TAKE_PTR(t);
 
                 r = loop_device_flock(d, LOCK_UN);
                 if (r < 0)
index 90e31c98efa41074618b51a17bd77d9058e4d1ef..fa2c54af31d6a992bef9e20c226043dfee1a1401 100644 (file)
@@ -26,7 +26,7 @@ static int environment_dirs(char ***ret) {
         if (r < 0)
                 return r;
 
-        r = strv_extend_front(&dirs, c);
+        r = strv_consume_prepend(&dirs, TAKE_PTR(c));
         if (r < 0)
                 return r;
 
index 18a7cb83b97d62a28e86a6c552900f2ced9495f7..652a6d5c89fe2d96714123f9973e7f5f310674ca 100644 (file)
@@ -1079,12 +1079,11 @@ static int process_root_account(int rfd) {
                 return log_error_errno(k, "Failed to check if directory file descriptor is root: %m");
 
         if (arg_copy_root_shell && k == 0) {
-                struct passwd *p;
+                _cleanup_free_ struct passwd *p = NULL;
 
-                errno = 0;
-                p = getpwnam("root");
-                if (!p)
-                        return log_error_errno(errno_or_else(EIO), "Failed to find passwd entry for root: %m");
+                r = getpwnam_malloc("root", &p);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to find passwd entry for root: %m");
 
                 r = free_and_strdup(&arg_root_shell, p->pw_shell);
                 if (r < 0)
index 5395ae62c1b7fc70f6d2bcecfaea200a1ca1a26e..d45e0f123855ae543c8922b1f0d785e2a810b195 100644 (file)
@@ -342,8 +342,6 @@ static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bu
 
 static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd_bus_error *error) {
         _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
-        struct passwd *pw;
-        struct group *gr;
         bool signed_locally;
         Home *other;
         int r;
@@ -360,13 +358,17 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd
         if (other)
                 return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name);
 
-        pw = getpwnam(hr->user_name);
-        if (pw)
+        r = getpwnam_malloc(hr->user_name, /* ret= */ NULL);
+        if (r >= 0)
                 return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name);
+        if (r != -ESRCH)
+                return r;
 
-        gr = getgrnam(hr->user_name);
-        if (gr)
+        r = getgrnam_malloc(hr->user_name, /* ret= */ NULL);
+        if (r >= 0)
                 return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name);
+        if (r != -ESRCH)
+                return r;
 
         r = manager_verify_user_record(m, hr);
         switch (r) {
@@ -397,17 +399,24 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd
         }
 
         if (uid_is_valid(hr->uid)) {
+                _cleanup_free_ struct passwd *pw = NULL;
+                _cleanup_free_ struct group *gr = NULL;
+
                 other = hashmap_get(m->homes_by_uid, UID_TO_PTR(hr->uid));
                 if (other)
                         return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by home %s, refusing.", hr->uid, other->user_name);
 
-                pw = getpwuid(hr->uid);
-                if (pw)
+                r = getpwuid_malloc(hr->uid, &pw);
+                if (r >= 0)
                         return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by NSS user %s, refusing.", hr->uid, pw->pw_name);
+                if (r != -ESRCH)
+                        return r;
 
-                gr = getgrgid(hr->uid);
-                if (gr)
+                r = getgrgid_malloc(hr->uid, &gr);
+                if (r >= 0)
                         return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use as GID by NSS group %s, refusing.", hr->uid, gr->gr_name);
+                if (r != -ESRCH)
+                        return r;
         } else {
                 r = manager_augment_record_with_uid(m, hr);
                 if (r < 0)
index 94b2ea5181a16b25e3872b8836728801142f209d..810ecb23fcfc843b0334cc87a0d4788429c2432b 100644 (file)
@@ -588,8 +588,8 @@ static int manager_acquire_uid(
         assert(ret);
 
         for (;;) {
-                struct passwd *pw;
-                struct group *gr;
+                _cleanup_free_ struct passwd *pw = NULL;
+                _cleanup_free_ struct group *gr = NULL;
                 uid_t candidate;
                 Home *other;
 
@@ -632,19 +632,27 @@ static int manager_acquire_uid(
                         continue;
                 }
 
-                pw = getpwuid(candidate);
-                if (pw) {
+                r = getpwuid_malloc(candidate, &pw);
+                if (r >= 0) {
                         log_debug("Candidate UID " UID_FMT " already registered by another user in NSS (%s), let's try another.",
                                   candidate, pw->pw_name);
                         continue;
                 }
+                if (r != -ESRCH) {
+                        log_debug_errno(r, "Failed to check if an NSS user is already registered for candidate UID " UID_FMT ", assuming there might be: %m", candidate);
+                        continue;
+                }
 
-                gr = getgrgid((gid_t) candidate);
-                if (gr) {
+                r = getgrgid_malloc((gid_t) candidate, &gr);
+                if (r >= 0) {
                         log_debug("Candidate UID " UID_FMT " already registered by another group in NSS (%s), let's try another.",
                                   candidate, gr->gr_name);
                         continue;
                 }
+                if (r != -ESRCH) {
+                        log_debug_errno(r, "Failed to check if an NSS group is already registered for candidate UID " UID_FMT ", assuming there might be: %m", candidate);
+                        continue;
+                }
 
                 r = search_ipc(candidate, (gid_t) candidate);
                 if (r < 0)
@@ -715,7 +723,7 @@ static int manager_add_home_by_image(
                 }
         } else {
                 /* Check NSS, in case there's another user or group by this name */
-                if (getpwnam(user_name) || getgrnam(user_name)) {
+                if (getpwnam_malloc(user_name, /* ret= */ NULL) >= 0 || getgrnam_malloc(user_name, /* ret= */ NULL) >= 0) {
                         log_debug("Found an existing user or group by name '%s', ignoring image '%s'.", user_name, image_path);
                         return 0;
                 }
@@ -998,7 +1006,7 @@ static int manager_bind_varlink(Manager *m) {
         assert(m);
         assert(!m->varlink_server);
 
-        r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
+        r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate varlink server object: %m");
 
index 6870ae98916857d5bdd7d14eb9c5db5081190e4a..161d7199385fe7d2e1f142d081e2749f4e30f3d8 100644 (file)
@@ -290,7 +290,7 @@ int home_resize_directory(
         if (r < 0)
                 return r;
 
-        r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+        r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
         if (r < 0)
                 return r;
 
index 57016fb14b0ce171829d7dcb3fec3466ab8841b1..a66ebb6ca5e48b7a61b2ac32034e8f7facd8fa50 100644 (file)
@@ -3445,7 +3445,7 @@ int home_resize_luks(
                 /* → Shrink */
 
                 if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
-                        r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+                        r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
                         if (r < 0)
                                 return r;
                 }
@@ -3533,7 +3533,7 @@ int home_resize_luks(
 
         } else { /* → Grow */
                 if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
-                        r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+                        r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
                         if (r < 0)
                                 return r;
                 }
index 066483e342bfe00e4549116fe053e6ca5b4ebaa2..a003c783cfc6ed6f0bc51a38d482e44668b907f5 100644 (file)
@@ -638,7 +638,7 @@ int home_load_embedded_identity(
          *
          *      · The record we got passed from the host
          *      · The record included in the LUKS header (only if LUKS is used)
-         *      · The record in the home directory itself (~.identity)
+         *      · The record in the home directory itself (~/.identity)
          *
          *  Now we have to reconcile all three, and let the newest one win. */
 
@@ -698,13 +698,12 @@ int home_load_embedded_identity(
         return 0;
 }
 
-int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home) {
+int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home) {
         _cleanup_(user_record_unrefp) UserRecord *embedded = NULL;
         int r;
 
         assert(h);
         assert(root_fd >= 0);
-        assert(uid_is_valid(uid));
 
         r = user_record_clone(h, USER_RECORD_EXTRACT_EMBEDDED|USER_RECORD_PERMISSIVE, &embedded);
         if (r < 0)
@@ -848,7 +847,7 @@ int home_refresh(
         if (r < 0)
                 return r;
 
-        r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+        r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
         if (r < 0)
                 return r;
 
@@ -1068,7 +1067,7 @@ int home_populate(UserRecord *h, int dir_fd) {
         if (r < 0)
                 return r;
 
-        r = home_store_embedded_identity(h, dir_fd, h->uid, NULL);
+        r = home_store_embedded_identity(h, dir_fd, NULL);
         if (r < 0)
                 return r;
 
@@ -1608,7 +1607,7 @@ static int home_update(UserRecord *h, UserRecord **ret) {
         if (r < 0)
                 return r;
 
-        r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+        r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home);
         if (r < 0)
                 return r;
 
@@ -1733,7 +1732,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
         if (r < 0)
                 return r;
 
-        r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+        r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home);
         if (r < 0)
                 return r;
 
index cef3f4eb98b99996136502068e2ef1fe292f87ad..fb2b43edd63fdd4c43677f7be56798a61ff78c42 100644 (file)
@@ -87,7 +87,7 @@ int home_maybe_shift_uid(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup);
 int home_populate(UserRecord *h, int dir_fd);
 
 int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, PasswordCache *cache, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
-int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home);
+int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home);
 int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup);
 
 int user_record_authenticate(UserRecord *h, UserRecord *secret, PasswordCache *cache, bool strict_verify);
index cc476c30db764e918ba4daba5bbe2d69cbb49580..d3ec22e224ef369c1295fbe605fc2550807f7ee5 100644 (file)
@@ -1915,54 +1915,93 @@ static int verify(sd_journal *j, bool verbose) {
         return r;
 }
 
-static int simple_varlink_call(const char *option, const char *method) {
-        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
-        const char *error, *fn;
+static int varlink_connect_journal(Varlink **ret_link) {
+        const char *address;
         int r;
 
-        if (arg_machine)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "%s is not supported in conjunction with --machine=.", option);
-
-        fn = arg_namespace ?
-                strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") :
-                "/run/systemd/journal/io.systemd.journal";
+        address = arg_namespace ?
+                  strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") :
+                  "/run/systemd/journal/io.systemd.journal";
 
-        r = varlink_connect_address(&link, fn);
+        r = varlink_connect_address(ret_link, address);
         if (r < 0)
-                return log_error_errno(r, "Failed to connect to %s: %m", fn);
+                return r;
 
-        (void) varlink_set_description(link, "journal");
-        (void) varlink_set_relative_timeout(link, USEC_INFINITY);
-
-        r = varlink_call(link, method, NULL, NULL, &error, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to execute varlink call: %m");
-        if (error)
-                return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
-                                       "Failed to execute varlink call: %s", error);
+        (void) varlink_set_description(*ret_link, "journal");
+        (void) varlink_set_relative_timeout(*ret_link, USEC_INFINITY);
 
         return 0;
 }
 
 static int flush_to_var(void) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        if (arg_machine || arg_namespace)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--flush is not supported in conjunction with %s.",
+                                       arg_machine ? "--machine=" : "--namespace=");
+
         if (access("/run/systemd/journal/flushed", F_OK) >= 0)
                 return 0; /* Already flushed, no need to contact journald */
         if (errno != ENOENT)
                 return log_error_errno(errno, "Unable to check for existence of /run/systemd/journal/flushed: %m");
 
-        return simple_varlink_call("--flush", "io.systemd.Journal.FlushToVar");
+        r = varlink_connect_journal(&link);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.FlushToVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
 }
 
 static int relinquish_var(void) {
-        return simple_varlink_call("--relinquish-var/--smart-relinquish-var", "io.systemd.Journal.RelinquishVar");
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        if (arg_machine || arg_namespace)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--(smart-)relinquish-var is not supported in conjunction with %s.",
+                                       arg_machine ? "--machine=" : "--namespace=");
+
+        r = varlink_connect_journal(&link);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.RelinquishVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
 }
 
 static int rotate(void) {
-        return simple_varlink_call("--rotate", "io.systemd.Journal.Rotate");
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        if (arg_machine)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--rotate is not supported in conjunction with --machine=.");
+
+        r = varlink_connect_journal(&link);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.Rotate", /* parameters= */ NULL, /* ret_parameters= */ NULL);
 }
 
 static int sync_journal(void) {
-        return simple_varlink_call("--sync", "io.systemd.Journal.Synchronize");
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        if (arg_machine)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--sync is not supported in conjunction with --machine=.");
+
+        r = varlink_connect_journal(&link);
+        if (ERRNO_IS_NEG_DISCONNECT(r) && arg_namespace)
+                /* If the namespaced sd-journald instance was shut down due to inactivity, it should already
+                 * be synchronized */
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.Synchronize", /* parameters= */ NULL, /* ret_parameters= */ NULL);
 }
 
 static int action_list_fields(sd_journal *j) {
index a93b4e688fe1e69929ecaca8e1b8eea29417048c..2523d43944959bd62e65cc5ce6b5b0c7ca5804cd 100644 (file)
@@ -133,9 +133,10 @@ static int context_copy(const Context *source, Context *ret) {
 
         assert(source);
         assert(ret);
+        assert(source->rfd >= 0 || source->rfd == AT_FDCWD);
 
         _cleanup_(context_done) Context copy = (Context) {
-                .rfd = -EBADF,
+                .rfd = AT_FDCWD,
                 .action = source->action,
                 .machine_id = source->machine_id,
                 .machine_id_is_random = source->machine_id_is_random,
@@ -144,9 +145,11 @@ static int context_copy(const Context *source, Context *ret) {
                 .entry_token_type = source->entry_token_type,
         };
 
-        copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
-        if (copy.rfd < 0)
-                return copy.rfd;
+        if (source->rfd >= 0) {
+                copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
+                if (copy.rfd < 0)
+                        return copy.rfd;
+        }
 
         r = strdup_or_null(source->layout_other, &copy.layout_other);
         if (r < 0)
@@ -169,9 +172,9 @@ static int context_copy(const Context *source, Context *ret) {
         r = strdup_or_null(source->kernel, &copy.kernel);
         if (r < 0)
                 return r;
-        copy.initrds = strv_copy(source->initrds);
-        if (!copy.initrds)
-                return -ENOMEM;
+        r = strv_copy_unless_empty(source->initrds, &copy.initrds);
+        if (r < 0)
+                return r;
         r = strdup_or_null(source->initrd_generator, &copy.initrd_generator);
         if (r < 0)
                 return r;
@@ -181,15 +184,15 @@ static int context_copy(const Context *source, Context *ret) {
         r = strdup_or_null(source->staging_area, &copy.staging_area);
         if (r < 0)
                 return r;
-        copy.plugins = strv_copy(source->plugins);
-        if (!copy.plugins)
-                return -ENOMEM;
-        copy.argv = strv_copy(source->argv);
-        if (!copy.argv)
-                return -ENOMEM;
-        copy.envp = strv_copy(source->envp);
-        if (!copy.envp)
-                return -ENOMEM;
+        r = strv_copy_unless_empty(source->plugins, &copy.plugins);
+        if (r < 0)
+                return r;
+        r = strv_copy_unless_empty(source->argv, &copy.argv);
+        if (r < 0)
+                return r;
+        r = strv_copy_unless_empty(source->envp, &copy.envp);
+        if (r < 0)
+                return r;
 
         *ret = copy;
         copy = CONTEXT_NULL;
@@ -428,21 +431,6 @@ static int context_load_environment(Context *c) {
         return 0;
 }
 
-static int context_ensure_conf_root(Context *c) {
-        int r;
-
-        assert(c);
-
-        if (c->conf_root)
-                return 0;
-
-        r = chaseat(c->rfd, "/etc/kernel", CHASE_AT_RESOLVE_IN_ROOT, &c->conf_root, /* ret_fd = */ NULL);
-        if (r < 0)
-                log_debug_errno(r, "Failed to chase /etc/kernel, ignoring: %m");
-
-        return 0;
-}
-
 static int context_load_install_conf_one(Context *c, const char *path) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char
@@ -495,8 +483,8 @@ static int context_load_install_conf(Context *c) {
                         return r;
         }
 
-        STRV_FOREACH(p, STRV_MAKE("/etc/kernel", "/usr/lib/kernel")) {
-                r = context_load_install_conf_one(c, *p);
+        FOREACH_STRING(p, "/etc/kernel", "/usr/lib/kernel") {
+                r = context_load_install_conf_one(c, p);
                 if (r != 0)
                         return r;
         }
@@ -730,10 +718,6 @@ static int context_init(Context *c) {
         if (r < 0)
                 return r;
 
-        r = context_ensure_conf_root(c);
-        if (r < 0)
-                return r;
-
         r = context_load_install_conf(c);
         if (r < 0)
                 return r;
@@ -994,11 +978,10 @@ static int context_build_arguments(Context *c) {
                         return log_oom();
 
         } else if (c->action == ACTION_INSPECT) {
-                r = strv_extend(&a, c->kernel ?: "[KERNEL_IMAGE]");
-                if (r < 0)
-                        return log_oom();
-
-                r = strv_extend(&a, "[INITRD...]");
+                r = strv_extend_many(
+                                &a,
+                                c->kernel ?: "[KERNEL_IMAGE]",
+                                "[INITRD...]");
                 if (r < 0)
                         return log_oom();
         }
@@ -1284,7 +1267,7 @@ static int verb_add_all(int argc, char *argv[], void *userdata) {
         }
 
         if (n > 0)
-                log_info("Installed %zu kernels.", n);
+                log_debug("Installed %zu kernel(s).", n);
         else if (ret == 0)
                 ret = log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No kernels to install found.");
 
index 5162df799c3e89adde494da0eb593a1bdf2327d1..89681d00758b9a364d0bffe07cb4923dee77b30f 100644 (file)
@@ -144,6 +144,7 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
         rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */
         rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false);
         rt->icmp6_ratelimit_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false);
+        rt->retransmission_time_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false);
 
         rt->preference = (rt->flags >> 3) & 3;
         if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
@@ -275,6 +276,14 @@ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
         return 0;
 }
 
+int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret) {
+        assert_return(rt, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        *ret = rt->retransmission_time_usec;
+        return 0;
+}
+
 int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret) {
         assert_return(rt, -EINVAL);
         assert_return(ret, -EINVAL);
index 0a55e1ac57bdf8e34c379847bdf5eaf53c2ee20d..63d4f90ba909973c9c7ef814c75b444d1adf9fa4 100644 (file)
@@ -24,6 +24,7 @@ struct sd_ndisc_router {
         uint64_t flags;
         unsigned preference;
         uint64_t lifetime_usec;
+        usec_t retransmission_time_usec;
 
         uint8_t hop_limit;
         uint32_t mtu;
index dc1c17035d1ff01e20fb94b34737b3aba9b6570c..395f7ce4d045ba9d8dcdd3c148185158b9e967bd 100644 (file)
@@ -84,8 +84,10 @@ struct sd_dhcp_client {
         usec_t t1_time;
         usec_t t2_time;
         usec_t expire_time;
-        uint64_t attempt;
-        uint64_t max_attempts;
+        uint64_t discover_attempt;
+        uint64_t request_attempt;
+        uint64_t max_discover_attempts;
+        uint64_t max_request_attempts;
         OrderedHashmap *extra_options;
         OrderedHashmap *vendor_options;
         sd_event_source *timeout_t1;
@@ -149,6 +151,7 @@ static int client_receive_message_udp(
                 uint32_t revents,
                 void *userdata);
 static void client_stop(sd_dhcp_client *client, int error);
+static int client_restart(sd_dhcp_client *client);
 
 int dhcp_client_set_state_callback(
                 sd_dhcp_client *client,
@@ -529,7 +532,7 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt
         assert_return(client, -EINVAL);
         assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
 
-        client->max_attempts = max_attempts;
+        client->max_discover_attempts = max_attempts;
 
         return 0;
 }
@@ -654,7 +657,8 @@ static int client_initialize(sd_dhcp_client *client) {
         (void) event_source_disable(client->timeout_expire);
         (void) event_source_disable(client->timeout_ipv6_only_mode);
 
-        client->attempt = 0;
+        client->discover_attempt = 0;
+        client->request_attempt = 0;
 
         client_set_state(client, DHCP_STATE_STOPPED);
         client->xid = 0;
@@ -1188,13 +1192,19 @@ static int client_timeout_resend(
         case DHCP_STATE_INIT:
         case DHCP_STATE_INIT_REBOOT:
         case DHCP_STATE_SELECTING:
+                if (client->discover_attempt >= client->max_discover_attempts)
+                        goto error;
+
+                client->discover_attempt++;
+                next_timeout = client_compute_request_timeout(time_now, client->discover_attempt);
+                break;
         case DHCP_STATE_REQUESTING:
         case DHCP_STATE_BOUND:
-                if (client->attempt >= client->max_attempts)
+                if (client->request_attempt >= client->max_request_attempts)
                         goto error;
 
-                client->attempt++;
-                next_timeout = client_compute_request_timeout(time_now, client->attempt);
+                client->request_attempt++;
+                next_timeout = client_compute_request_timeout(time_now, client->request_attempt);
                 break;
 
         case DHCP_STATE_STOPPED:
@@ -1218,14 +1228,14 @@ static int client_timeout_resend(
                 r = client_send_discover(client);
                 if (r >= 0) {
                         client_set_state(client, DHCP_STATE_SELECTING);
-                        client->attempt = 0;
-                } else if (client->attempt >= client->max_attempts)
+                        client->discover_attempt = 0;
+                } else if (client->discover_attempt >= client->max_discover_attempts)
                         goto error;
                 break;
 
         case DHCP_STATE_SELECTING:
                 r = client_send_discover(client);
-                if (r < 0 && client->attempt >= client->max_attempts)
+                if (r < 0 && client->discover_attempt >= client->max_discover_attempts)
                         goto error;
                 break;
 
@@ -1234,7 +1244,7 @@ static int client_timeout_resend(
         case DHCP_STATE_RENEWING:
         case DHCP_STATE_REBINDING:
                 r = client_send_request(client);
-                if (r < 0 && client->attempt >= client->max_attempts)
+                if (r < 0 && client->request_attempt >= client->max_request_attempts)
                          goto error;
 
                 if (client->state == DHCP_STATE_INIT_REBOOT)
@@ -1251,12 +1261,20 @@ static int client_timeout_resend(
                 goto error;
         }
 
-        if (client->attempt >= TRANSIENT_FAILURE_ATTEMPTS)
+        if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS)
                 client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE);
 
         return 0;
 
 error:
+        /* Avoid REQUEST infinite loop. Per RFC 2131 section 3.1.5: if the client receives
+           neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm,
+           the client reverts to INIT state and restarts the initialization process */
+        if (client->request_attempt >= client->max_request_attempts) {
+                log_dhcp_client(client, "Max REQUEST attempts reached. Restarting...");
+                client_restart(client);
+                return 0;
+        }
         client_stop(client, r);
 
         /* Errors were dealt with when stopping the client, don't spill
@@ -1389,7 +1407,8 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
         client->fd = safe_close(client->fd);
 
         client_set_state(client, DHCP_STATE_REBINDING);
-        client->attempt = 0;
+        client->discover_attempt = 0;
+        client->request_attempt = 0;
 
         r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
                                          &client->hw_addr, &client->bcast_addr,
@@ -1412,7 +1431,8 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata)
                 client_set_state(client, DHCP_STATE_RENEWING);
         else if (client->state != DHCP_STATE_INIT)
                 client_set_state(client, DHCP_STATE_INIT_REBOOT);
-        client->attempt = 0;
+        client->discover_attempt = 0;
+        client->request_attempt = 0;
 
         return client_initialize_time_events(client);
 }
@@ -1550,7 +1570,8 @@ static int client_enter_requesting_now(sd_dhcp_client *client) {
         assert(client);
 
         client_set_state(client, DHCP_STATE_REQUESTING);
-        client->attempt = 0;
+        client->discover_attempt = 0;
+        client->request_attempt = 0;
 
         return event_reset_time(client->event, &client->timeout_resend,
                                 CLOCK_BOOTTIME, 0, 0,
@@ -1777,7 +1798,8 @@ static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
                 notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
 
         client_set_state(client, DHCP_STATE_BOUND);
-        client->attempt = 0;
+        client->discover_attempt = 0;
+        client->request_attempt = 0;
 
         client->last_addr = client->lease->address;
 
@@ -2107,7 +2129,8 @@ int sd_dhcp_client_send_renew(sd_dhcp_client *client) {
         assert(client->lease);
 
         client->start_delay = 0;
-        client->attempt = 1;
+        client->discover_attempt = 1;
+        client->request_attempt = 1;
         client_set_state(client, DHCP_STATE_RENEWING);
 
         return client_initialize_time_events(client);
@@ -2360,7 +2383,8 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
                 .mtu = DHCP_MIN_PACKET_SIZE,
                 .port = DHCP_PORT_CLIENT,
                 .anonymize = !!anonymize,
-                .max_attempts = UINT64_MAX,
+                .max_discover_attempts = UINT64_MAX,
+                .max_request_attempts = 5,
                 .ip_service_type = -1,
         };
         /* NOTE: this could be moved to a function. */
index d94cc1ceb74ad8672657c397ce1dbb69d4e8cad2..d0648b95fabf165b7ac8f8357ea0042d2f430bbf 100644 (file)
@@ -28,7 +28,7 @@ static sd_ndisc *test_timeout_nd;
 static void router_dump(sd_ndisc_router *rt) {
         struct in6_addr addr;
         uint8_t hop_limit;
-        usec_t t, lifetime;
+        usec_t t, lifetime, retrans_time;
         uint64_t flags;
         uint32_t mtu;
         unsigned preference;
@@ -65,6 +65,9 @@ static void router_dump(sd_ndisc_router *rt) {
         assert_se(sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
         log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
 
+        assert_se(sd_ndisc_router_get_retransmission_time(rt, &retrans_time) >= 0);
+        log_info("Retransmission Time: %s", FORMAT_TIMESPAN(retrans_time, USEC_PER_SEC));
+
         if (sd_ndisc_router_get_mtu(rt, &mtu) < 0)
                 log_info("No MTU set");
         else
index 4146a6efd322c31455d43faac6a89db338e79518..2eca82b0338c4b845c18b87998a4776977eb54ac 100644 (file)
@@ -34,7 +34,7 @@ int bus_container_connect_socket(sd_bus *b) {
                 log_debug("sd-bus: connecting bus%s%s to namespace of PID "PID_FMT"...",
                           b->description ? " " : "", strempty(b->description), b->nspid);
 
-        r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+        r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
         if (r < 0)
                 return log_debug_errno(r, "Failed to open namespace of PID "PID_FMT": %m", b->nspid);
 
index 5ac90223ffd88874e2fe1f8da42ef0e71f381758..e3d1eb8f33b7752bcddde0130e92564082fa5fa5 100644 (file)
@@ -1288,7 +1288,7 @@ static int message_push_fd(sd_bus_message *m, int fd) {
         if (copy < 0)
                 return -errno;
 
-        f = reallocarray(m->fds, sizeof(int), m->n_fds + 1);
+        f = reallocarray(m->fds, m->n_fds + 1, sizeof(int));
         if (!f) {
                 m->poisoned = true;
                 safe_close(copy);
index 6dcae2e183a20746bc9bc96d149cbdb6ed01c637..8fe8854350c98159427d4c9ba0857d5175375e5a 100644 (file)
@@ -735,12 +735,12 @@ static int bus_socket_inotify_setup(sd_bus *b) {
         assert(b->sockaddr.sa.sa_family == AF_UNIX);
         assert(b->sockaddr.un.sun_path[0] != 0);
 
-        /* Sets up an inotify fd in case watch_bind is enabled: wait until the configured AF_UNIX file system socket
-         * appears before connecting to it. The implemented is pretty simplistic: we just subscribe to relevant changes
-         * to all prefix components of the path, and every time we get an event for that we try to reconnect again,
-         * without actually caring what precisely the event we got told us. If we still can't connect we re-subscribe
-         * to all relevant changes of anything in the path, so that our watches include any possibly newly created path
-         * components. */
+        /* Sets up an inotify fd in case watch_bind is enabled: wait until the configured AF_UNIX file system
+         * socket appears before connecting to it. The implemented is pretty simplistic: we just subscribe to
+         * relevant changes to all components of the path, and every time we get an event for that we try to
+         * reconnect again, without actually caring what precisely the event we got told us. If we still
+         * can't connect we re-subscribe to all relevant changes of anything in the path, so that our watches
+         * include any possibly newly created path components. */
 
         if (b->inotify_fd < 0) {
                 b->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
@@ -759,17 +759,17 @@ static int bus_socket_inotify_setup(sd_bus *b) {
         if (r < 0)
                 goto fail;
 
-        /* Watch all parent directories, and don't mind any prefix that doesn't exist yet. For the innermost directory
-         * that exists we want to know when files are created or moved into it. For all parents of it we just care if
-         * they are removed or renamed. */
+        /* Watch all components of the path, and don't mind any prefix that doesn't exist yet. For the
+         * innermost directory that exists we want to know when files are created or moved into it. For all
+         * parents of it we just care if they are removed or renamed. */
 
         if (!GREEDY_REALLOC(new_watches, n + 1)) {
                 r = -ENOMEM;
                 goto fail;
         }
 
-        /* Start with the top-level directory, which is a bit simpler than the rest, since it can't be a symlink, and
-         * always exists */
+        /* Start with the top-level directory, which is a bit simpler than the rest, since it can't be a
+         * symlink, and always exists */
         wd = inotify_add_watch(b->inotify_fd, "/", IN_CREATE|IN_MOVED_TO);
         if (wd < 0) {
                 r = log_debug_errno(errno, "Failed to add inotify watch on /: %m");
index 79a0013dc71d1085aaf6e63dcda484503730bd97..e1f3b6e80cf72380c30d00f2562deb9c9ca64e4e 100644 (file)
@@ -21,6 +21,7 @@ int device_get_property_bool(sd_device *device, const char *key);
 int device_get_property_int(sd_device *device, const char *key, int *ret);
 int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value);
 int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value);
+int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value);
 int device_get_sysattr_bool(sd_device *device, const char *sysattr);
 int device_get_device_id(sd_device *device, const char **ret);
 int device_get_devlink_priority(sd_device *device, int *ret);
index f18c8e69220bcb9502a835a61192e101db181e55..22b6437823839c4e53f1d55ac4620af499aecbee 100644 (file)
@@ -2423,6 +2423,25 @@ int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned
         return v > 0;
 }
 
+int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value) {
+        const char *value;
+        int r;
+
+        r = sd_device_get_sysattr_value(device, sysattr, &value);
+        if (r < 0)
+                return r;
+
+        uint32_t v;
+        r = safe_atou32(value, &v);
+        if (r < 0)
+                return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr);
+
+        if (ret_value)
+                *ret_value = v;
+        /* We return "true" if the value is positive. */
+        return v > 0;
+}
+
 int device_get_sysattr_bool(sd_device *device, const char *sysattr) {
         const char *value;
         int r;
index a3101227af7dc645473f416b7ef06dc4a31dbb08..2338df1d629c0d2f6e17f44798d9bf49ce3d2a09 100644 (file)
@@ -151,3 +151,20 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle
 
         return 0;
 }
+
+int event_add_child_pidref(
+                sd_event *e,
+                sd_event_source **s,
+                const PidRef *pid,
+                int options,
+                sd_event_child_handler_t callback,
+                void *userdata) {
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        if (pid->fd >= 0)
+                return sd_event_add_child_pidfd(e, s, pid->fd, options, callback, userdata);
+
+        return sd_event_add_child(e, s, pid->pid, options, callback, userdata);
+}
index c185584412314dfd2066d78d0e5da1fffa7b3a0f..6259d5ae2552ae0784fbf3e7e6dfa73408a0cc06 100644 (file)
@@ -5,6 +5,8 @@
 
 #include "sd-event.h"
 
+#include "pidref.h"
+
 int event_reset_time(
                 sd_event *e,
                 sd_event_source **s,
@@ -32,3 +34,5 @@ static inline int event_source_disable(sd_event_source *s) {
 }
 
 int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata);
+
+int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata);
index 3e88d349e1689cc37bea398734cc7c0b152fe5b0..c72fccc229e9d30ba1c21074055e117553b2a695 100644 (file)
@@ -1081,7 +1081,7 @@ _public_ int sd_get_uids(uid_t **users) {
                                 uid_t *t;
 
                                 n = MAX(16, 2*r);
-                                t = reallocarray(l, sizeof(uid_t), n);
+                                t = reallocarray(l, n, sizeof(uid_t));
                                 if (!t)
                                         return -ENOMEM;
 
index f84097e16de294b79c0144f26340f328ca93c5fd..fb11c7e02bb2816877c409ed56b7d7c328ff01f2 100644 (file)
@@ -56,6 +56,10 @@ static bool rtnl_message_type_is_mdb(uint16_t type) {
         return IN_SET(type, RTM_NEWMDB, RTM_DELMDB, RTM_GETMDB);
 }
 
+static bool rtnl_message_type_is_nsid(uint16_t type) {
+        return IN_SET(type, RTM_NEWNSID, RTM_DELNSID, RTM_GETNSID);
+}
+
 int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) {
         struct rtmsg *rtm;
 
@@ -1216,3 +1220,24 @@ int sd_rtnl_message_new_mdb(
 
         return 0;
 }
+
+int sd_rtnl_message_new_nsid(
+                sd_netlink *rtnl,
+                sd_netlink_message **ret,
+                uint16_t nlmsg_type) {
+
+        struct rtgenmsg *rt;
+        int r;
+
+        assert_return(rtnl_message_type_is_nsid(nlmsg_type), -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        r = message_new(rtnl, ret, nlmsg_type);
+        if (r < 0)
+                return r;
+
+        rt = NLMSG_DATA((*ret)->hdr);
+        rt->rtgen_family = AF_UNSPEC;
+
+        return 0;
+}
index 6fe9adcdbd437a456168de52629cb985ed275c45..226ac865c9372b79d51192f17ea4d3aa95d46b98 100644 (file)
@@ -199,6 +199,7 @@ static const NLAPolicy genl_nl80211_policies[] = {
         [NL80211_ATTR_SSID]        = BUILD_POLICY_WITH_SIZE(BINARY, IEEE80211_MAX_SSID_LEN),
         [NL80211_ATTR_STATUS_CODE] = BUILD_POLICY(U16),
         [NL80211_ATTR_4ADDR]       = BUILD_POLICY(U8),
+        [NL80211_ATTR_NETNS_FD]    = BUILD_POLICY(U32),
 };
 
 /***************** genl wireguard type systems *****************/
index 0153456d9beb9fa87e4482941a39dd2f3f155e3c..681b3086d08f79706275649042f2b1cd88e8fa1d 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/if_tunnel.h>
 #include <linux/ip.h>
 #include <linux/l2tp.h>
+#include <linux/net_namespace.h>
 #include <linux/netlink.h>
 #include <linux/nexthop.h>
 #include <linux/nl80211.h>
@@ -1185,6 +1186,13 @@ static const NLAPolicy rtnl_mdb_policies[] = {
 
 DEFINE_POLICY_SET(rtnl_mdb);
 
+static const NLAPolicy rtnl_nsid_policies[] = {
+        [NETNSA_FD]         = BUILD_POLICY(S32),
+        [NETNSA_NSID]       = BUILD_POLICY(U32),
+};
+
+DEFINE_POLICY_SET(rtnl_nsid);
+
 static const NLAPolicy rtnl_policies[] = {
         [RTM_NEWLINK]      = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)),
         [RTM_DELLINK]      = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)),
@@ -1220,6 +1228,9 @@ static const NLAPolicy rtnl_policies[] = {
         [RTM_NEWMDB]       = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)),
         [RTM_DELMDB]       = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)),
         [RTM_GETMDB]       = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)),
+        [RTM_NEWNSID]      = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)),
+        [RTM_DELNSID]      = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)),
+        [RTM_GETNSID]      = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)),
 };
 
 DEFINE_POLICY_SET(rtnl);
index 636af1a2d59503cf95fdf2a5a7e8ec7dfa8e9360..a591c7d6d6077635c5c4e0de99de8d685ea914b5 100644 (file)
@@ -20,6 +20,13 @@ static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) {
         assert(name);
 
         /* Assign the requested name. */
+
+        if (!*rtnl) {
+                r = sd_netlink_open(rtnl);
+                if (r < 0)
+                        return r;
+        }
+
         r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex);
         if (r < 0)
                 return r;
@@ -31,6 +38,37 @@ static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) {
         return sd_netlink_call(*rtnl, message, 0, NULL);
 }
 
+int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name) {
+        _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL;
+        int r, ifindex;
+
+        assert(orig_name);
+        assert(new_name);
+
+        /* This does not check alternative names. Callers must check the requested name is not used as an
+         * alternative name. */
+
+        if (streq(orig_name, new_name))
+                return 0;
+
+        if (!ifname_valid(new_name))
+                return -EINVAL;
+
+        if (!rtnl)
+                rtnl = &our_rtnl;
+        if (!*rtnl) {
+                r = sd_netlink_open(rtnl);
+                if (r < 0)
+                        return r;
+        }
+
+        ifindex = rtnl_resolve_ifname(rtnl, orig_name);
+        if (ifindex < 0)
+                return ifindex;
+
+        return set_link_name(rtnl, ifindex, new_name);
+}
+
 int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const *alternative_names) {
         _cleanup_strv_free_ char **original_altnames = NULL, **new_altnames = NULL;
         bool altname_deleted = false;
@@ -376,7 +414,7 @@ int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char
         assert(ifindex > 0);
 
         if (ret) {
-                r = sd_netlink_message_read_string_strdup(message, IFLA_IFNAME, ret);
+                r = sd_netlink_message_read_string_strdup(reply, IFLA_IFNAME, ret);
                 if (r < 0)
                         return r;
         }
index 56bbfd30d11d39c6e60aa83e8f7405a2cef85fc9..b895ac41d19b307021be83a5ce5d863f059f78a9 100644 (file)
@@ -31,6 +31,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(MultipathRoute*, multipath_route_free);
 
 int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret);
 
+int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name);
 int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const* alternative_names);
 static inline int rtnl_append_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) {
         return rtnl_set_link_name(rtnl, ifindex, NULL, alternative_names);
index 4c2d3173fbebb843fe71cd22c3e2d94b15d9dd9d..fb3150f1310ef4c22100e29e896e735248681db8 100644 (file)
@@ -677,6 +677,23 @@ TEST(rtnl_set_link_name) {
         assert_se(!strv_contains(alternative_names, "testlongalternativename"));
         assert_se(strv_contains(alternative_names, "test-additional-name"));
         assert_se(!strv_contains(alternative_names, "test-shortname"));
+
+        _cleanup_free_ char *resolved = NULL;
+        assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex);
+        assert_se(streq_ptr(resolved, "test-shortname"));
+        resolved = mfree(resolved);
+
+        assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname") >= 0);
+        assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname2") >= 0);
+        assert_se(rtnl_rename_link(NULL, "test-shortname2", "test-shortname3") >= 0);
+
+        assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex);
+        assert_se(streq_ptr(resolved, "test-shortname3"));
+        resolved = mfree(resolved);
+
+        assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-shortname3", &resolved) == ifindex);
+        assert_se(streq_ptr(resolved, "test-shortname3"));
+        resolved = mfree(resolved);
 }
 
 DEFINE_TEST_MAIN(LOG_DEBUG);
index 2059567ef894f031c33437ef3f4cd685ed31a37a..25c6e44a7791cdd95eb1a1b5fbba10bb86881470 100644 (file)
@@ -90,49 +90,48 @@ static const char *const link_online_state_table[_LINK_ONLINE_STATE_MAX] = {
 
 DEFINE_STRING_TABLE_LOOKUP(link_online_state, LinkOnlineState);
 
-int parse_operational_state_range(const char *str, LinkOperationalStateRange *out) {
-        LinkOperationalStateRange range = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID };
-        _cleanup_free_ const char *min = NULL;
+int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret) {
+        LinkOperationalStateRange range = LINK_OPERSTATE_RANGE_INVALID;
+        _cleanup_free_ char *buf = NULL;
         const char *p;
 
-        assert(str);
-        assert(out);
-
-        p = strchr(str, ':');
-        if (p) {
-                min = strndup(str, p - str);
+        assert(s);
+        assert(ret);
 
-                if (!isempty(p + 1)) {
-                        range.max = link_operstate_from_string(p + 1);
-                        if (range.max < 0)
-                                return -EINVAL;
-                }
-        } else
-                min = strdup(str);
+        /* allowed formats: "min", "min:", "min:max", ":max" */
 
-        if (!min)
-                return -ENOMEM;
+        if (isempty(s) || streq(s, ":"))
+                return -EINVAL;
 
-        if (!isempty(min)) {
-                range.min = link_operstate_from_string(min);
-                if (range.min < 0)
+        p = strchr(s, ':');
+        if (!p || isempty(p + 1))
+                range.max = LINK_OPERSTATE_ROUTABLE;
+        else {
+                range.max = link_operstate_from_string(p + 1);
+                if (range.max < 0)
                         return -EINVAL;
         }
 
-        /* Fail on empty strings. */
-        if (range.min == _LINK_OPERSTATE_INVALID && range.max == _LINK_OPERSTATE_INVALID)
-                return -EINVAL;
+        if (p) {
+                buf = strndup(s, p - s);
+                if (!buf)
+                        return -ENOMEM;
 
-        if (range.min == _LINK_OPERSTATE_INVALID)
+                s = buf;
+        }
+
+        if (isempty(s))
                 range.min = LINK_OPERSTATE_MISSING;
-        if (range.max == _LINK_OPERSTATE_INVALID)
-                range.max = LINK_OPERSTATE_ROUTABLE;
+        else {
+                range.min = link_operstate_from_string(s);
+                if (range.min < 0)
+                        return -EINVAL;
+        }
 
-        if (range.min > range.max)
+        if (!operational_state_range_is_valid(&range))
                 return -EINVAL;
 
-        *out = range;
-
+        *ret = range;
         return 0;
 }
 
index c47e271a768fd29ab04438b0dc98fc173f150cb1..6fc6015902e878bcaf632298a93b2e16347f7a09 100644 (file)
@@ -79,8 +79,30 @@ typedef struct LinkOperationalStateRange {
         LinkOperationalState max;
 } LinkOperationalStateRange;
 
-#define LINK_OPERSTATE_RANGE_DEFAULT (LinkOperationalStateRange) { LINK_OPERSTATE_DEGRADED, \
-                                                                   LINK_OPERSTATE_ROUTABLE }
-
-int parse_operational_state_range(const char *str, LinkOperationalStateRange *out);
+#define LINK_OPERSTATE_RANGE_DEFAULT            \
+        (const LinkOperationalStateRange) {     \
+                .min = LINK_OPERSTATE_DEGRADED, \
+                .max = LINK_OPERSTATE_ROUTABLE, \
+        }
+
+#define LINK_OPERSTATE_RANGE_INVALID            \
+        (const LinkOperationalStateRange) {     \
+                .min = _LINK_OPERSTATE_INVALID, \
+                .max = _LINK_OPERSTATE_INVALID, \
+        }
+
+int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret);
 int network_link_get_operational_state(int ifindex, LinkOperationalState *ret);
+
+static inline bool operational_state_is_valid(LinkOperationalState s) {
+        return s >= 0 && s < _LINK_OPERSTATE_MAX;
+}
+static inline bool operational_state_range_is_valid(const LinkOperationalStateRange *range) {
+        return range &&
+                operational_state_is_valid(range->min) &&
+                operational_state_is_valid(range->max) &&
+                range->min <= range->max;
+}
+static inline bool operational_state_is_in_range(LinkOperationalState s, const LinkOperationalStateRange *range) {
+        return range && range->min <= s && s <= range->max;
+}
index 9b76a63990859d008d21915cb479d654ba1f1e56..63253bd9dcf3c04b7e07e60f6dc2a671ccacacd8 100644 (file)
@@ -44,6 +44,7 @@ static BusPrintPropertyFlags arg_print_flags = 0;
 static bool arg_full = false;
 static PagerFlags arg_pager_flags = 0;
 static bool arg_legend = true;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 static const char *arg_kill_whom = NULL;
 static int arg_signal = SIGTERM;
 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
@@ -113,80 +114,115 @@ static OutputFlags get_output_flags(void) {
                 colors_enabled() * OUTPUT_COLOR;
 }
 
-static int show_table(Table *table, const char *word) {
+static int list_table_print(Table *table, const char *type) {
         int r;
 
         assert(table);
-        assert(word);
+        assert(type);
 
-        if (!table_isempty(table) || OUTPUT_MODE_IS_JSON(arg_output)) {
-                r = table_set_sort(table, (size_t) 0);
-                if (r < 0)
-                        return table_log_sort_error(r);
+        r = table_set_sort(table, (size_t) 0);
+        if (r < 0)
+                return table_log_sort_error(r);
 
-                table_set_header(table, arg_legend);
+        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
+        if (r < 0)
+                return r;
 
-                if (OUTPUT_MODE_IS_JSON(arg_output))
-                        r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO);
+        if (arg_legend) {
+                if (table_isempty(table))
+                        printf("No %s.\n", type);
                 else
-                        r = table_print(table, NULL);
-                if (r < 0)
-                        return table_log_print_error(r);
+                        printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type);
         }
 
-        if (arg_legend) {
-                if (table_isempty(table))
-                        printf("No %s.\n", word);
+        return 0;
+}
+
+static int list_sessions_table_add(Table *table, sd_bus_message *reply) {
+        int r;
+
+        assert(table);
+        assert(reply);
+
+        r = sd_bus_message_enter_container(reply, 'a', "(sussussbto)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        for (;;) {
+                const char *session_id, *user, *seat, *class, *tty;
+                uint32_t uid, leader_pid;
+                int idle;
+                uint64_t idle_timestamp_monotonic;
+
+                r = sd_bus_message_read(reply, "(sussussbto)",
+                                        &session_id,
+                                        &uid,
+                                        &user,
+                                        &seat,
+                                        &leader_pid,
+                                        &class,
+                                        &tty,
+                                        &idle,
+                                        &idle_timestamp_monotonic,
+                                        /* object = */ NULL);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
+
+                r = table_add_many(table,
+                                   TABLE_STRING, session_id,
+                                   TABLE_UID, (uid_t) uid,
+                                   TABLE_STRING, user,
+                                   TABLE_STRING, empty_to_null(seat),
+                                   TABLE_PID, (pid_t) leader_pid,
+                                   TABLE_STRING, class,
+                                   TABLE_STRING, empty_to_null(tty),
+                                   TABLE_BOOLEAN, idle);
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                if (idle)
+                        r = table_add_cell(table, NULL, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, &idle_timestamp_monotonic);
                 else
-                        printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
+                        r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+                if (r < 0)
+                        return table_log_add_error(r);
         }
 
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
         return 0;
 }
 
-static int list_sessions(int argc, char *argv[], void *userdata) {
+static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, sd_bus *bus) {
 
         static const struct bus_properties_map map[] = {
+                { "Leader",                 "u", NULL, offsetof(SessionStatusInfo, leader)                        },
+                { "Class",                  "s", NULL, offsetof(SessionStatusInfo, class)                         },
+                { "TTY",                    "s", NULL, offsetof(SessionStatusInfo, tty)                           },
                 { "IdleHint",               "b", NULL, offsetof(SessionStatusInfo, idle_hint)                     },
                 { "IdleSinceHintMonotonic", "t", NULL, offsetof(SessionStatusInfo, idle_hint_timestamp.monotonic) },
-                { "State",                  "s", NULL, offsetof(SessionStatusInfo, state)                         },
-                { "TTY",                    "s", NULL, offsetof(SessionStatusInfo, tty)                           },
                 {},
         };
 
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        _cleanup_(table_unrefp) Table *table = NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
         int r;
 
-        assert(argv);
-
-        pager_open(arg_pager_flags);
-
-        r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r));
+        assert(table);
+        assert(reply);
+        assert(bus);
 
         r = sd_bus_message_enter_container(reply, 'a', "(susso)");
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        table = table_new("session", "uid", "user", "seat", "tty", "state", "idle", "since");
-        if (!table)
-                return log_oom();
-
-        /* Right-align the first two fields (since they are numeric) */
-        (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100);
-        (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100);
-
-        (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
-
         for (;;) {
                 _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
                 const char *id, *user, *seat, *object;
                 uint32_t uid;
-                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
                 SessionStatusInfo i = {};
 
                 r = sd_bus_message_read(reply, "(susso)", &id, &uid, &user, &seat, &object);
@@ -209,8 +245,9 @@ static int list_sessions(int argc, char *argv[], void *userdata) {
                                    TABLE_UID, (uid_t) uid,
                                    TABLE_STRING, user,
                                    TABLE_STRING, empty_to_null(seat),
+                                   TABLE_PID, i.leader,
+                                   TABLE_STRING, i.class,
                                    TABLE_STRING, empty_to_null(i.tty),
-                                   TABLE_STRING, i.state,
                                    TABLE_BOOLEAN, i.idle_hint);
                 if (r < 0)
                         return table_log_add_error(r);
@@ -227,7 +264,49 @@ static int list_sessions(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        return show_table(table, "sessions");
+        return 0;
+}
+
+static int list_sessions(int argc, char *argv[], void *userdata) {
+        sd_bus *bus = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        bool use_ex = true;
+        int r;
+
+        assert(argv);
+
+        r = bus_call_method(bus, bus_login_mgr, "ListSessionsEx", &error, &reply, NULL);
+        if (r < 0) {
+                if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+                        sd_bus_error_free(&error);
+
+                        use_ex = false;
+                        r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL);
+                }
+                if (r < 0)
+                        return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r));
+        }
+
+        table = table_new("session", "uid", "user", "seat", "leader", "class", "tty", "idle", "since");
+        if (!table)
+                return log_oom();
+
+        /* Right-align the first two fields (since they are numeric) */
+        (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100);
+        (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100);
+
+        (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+        if (use_ex)
+                r = list_sessions_table_add(table, reply);
+        else
+                r = list_sessions_table_add_fallback(table, reply, bus);
+        if (r < 0)
+                return r;
+
+        return list_table_print(table, "sessions");
 }
 
 static int list_users(int argc, char *argv[], void *userdata) {
@@ -246,8 +325,6 @@ static int list_users(int argc, char *argv[], void *userdata) {
 
         assert(argv);
 
-        pager_open(arg_pager_flags);
-
         r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r));
@@ -305,7 +382,7 @@ static int list_users(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        return show_table(table, "users");
+        return list_table_print(table, "users");
 }
 
 static int list_seats(int argc, char *argv[], void *userdata) {
@@ -317,8 +394,6 @@ static int list_seats(int argc, char *argv[], void *userdata) {
 
         assert(argv);
 
-        pager_open(arg_pager_flags);
-
         r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r));
@@ -351,7 +426,7 @@ static int list_seats(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        return show_table(table, "seats");
+        return list_table_print(table, "seats");
 }
 
 static int show_unit_cgroup(
@@ -1442,7 +1517,10 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --kill-whom=WHOM      Whom to send signal to\n"
                "  -s --signal=SIGNAL       Which signal to send\n"
                "  -n --lines=INTEGER       Number of journal entries to show\n"
-               "  -o --output=STRING       Change journal output mode (short, short-precise,\n"
+               "     --json=MODE           Generate JSON output for list-sessions/users/seats\n"
+               "                             (takes one of pretty, short, or off)\n"
+               "  -j                       Same as --json=pretty on tty, --json=short otherwise\n"
+               "  -o --output=MODE         Change journal output mode (short, short-precise,\n"
                "                             short-iso, short-iso-precise, short-full,\n"
                "                             short-monotonic, short-unix, short-delta,\n"
                "                             json, json-pretty, json-sse, json-seq, cat,\n"
@@ -1464,6 +1542,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VALUE,
                 ARG_NO_PAGER,
                 ARG_NO_LEGEND,
+                ARG_JSON,
                 ARG_KILL_WHOM,
                 ARG_NO_ASK_PASSWORD,
         };
@@ -1477,6 +1556,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "full",            no_argument,       NULL, 'l'                 },
                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
                 { "no-legend",       no_argument,       NULL, ARG_NO_LEGEND       },
+                { "json",            required_argument, NULL, ARG_JSON            },
                 { "kill-whom",       required_argument, NULL, ARG_KILL_WHOM       },
                 { "signal",          required_argument, NULL, 's'                 },
                 { "host",            required_argument, NULL, 'H'                 },
@@ -1492,7 +1572,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:j", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -1546,7 +1626,19 @@ static int parse_argv(int argc, char *argv[]) {
                         if (arg_output < 0)
                                 return log_error_errno(arg_output, "Unknown output '%s'.", optarg);
 
-                        if (OUTPUT_MODE_IS_JSON(arg_output))
+                        break;
+
+                case 'j':
+                        arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+                        arg_legend = false;
+                        break;
+
+                case ARG_JSON:
+                        r = parse_json_argument(optarg, &arg_json_format_flags);
+                        if (r <= 0)
+                                return r;
+
+                        if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
                                 arg_legend = false;
 
                         break;
index 138952da91da82424c6c4b3f5bd8cd20d44864a4..c86856806b591cd2df6e7485a52ba0356a142b36 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "alloc-util.h"
 #include "bus-error.h"
+#include "bus-unit-util.h"
 #include "bus-util.h"
 #include "conf-parser.h"
 #include "format-util.h"
@@ -166,15 +167,24 @@ int handle_action_get_enabled_sleep_actions(HandleActionSleepMask mask, char ***
         return 0;
 }
 
-HandleAction handle_action_sleep_select(HandleActionSleepMask mask) {
+HandleAction handle_action_sleep_select(Manager *m) {
+        assert(m);
 
         FOREACH_ARRAY(i, sleep_actions, ELEMENTSOF(sleep_actions)) {
-                HandleActionSleepMask a = 1U << *i;
+                HandleActionSleepMask action_mask = 1U << *i;
+                const HandleActionData *a;
+                _cleanup_free_ char *load_state = NULL;
+
+                if (!FLAGS_SET(m->handle_action_sleep_mask, action_mask))
+                        continue;
+
+                a = ASSERT_PTR(handle_action_lookup(*i));
 
-                if (!FLAGS_SET(mask, a))
+                if (sleep_supported(a->sleep_operation) <= 0)
                         continue;
 
-                if (handle_action_sleep_supported(*i))
+                (void) unit_load_state(m->bus, a->target, &load_state);
+                if (streq_ptr(load_state, "loaded"))
                         return *i;
         }
 
@@ -263,7 +273,7 @@ static int handle_action_sleep_execute(
         if (handle == HANDLE_SLEEP) {
                 HandleAction a;
 
-                a = handle_action_sleep_select(m->handle_action_sleep_mask);
+                a = handle_action_sleep_select(m);
                 if (a < 0)
                         return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                                  "None of the configured sleep operations are supported, ignoring.");
index 5ee86486ec3c652e1499d7f0304d74c4e4cd57ae..c78c18c5aad635c16198a5a64cdb817bed760a58 100644 (file)
@@ -70,7 +70,7 @@ struct HandleActionData {
 };
 
 int handle_action_get_enabled_sleep_actions(HandleActionSleepMask mask, char ***ret);
-HandleAction handle_action_sleep_select(HandleActionSleepMask mask);
+HandleAction handle_action_sleep_select(Manager *m);
 
 int manager_handle_action(
                 Manager *m,
index 99b9da6ba10802795c7118e347ef0e7a627094a1..3bb8527703f081668caaf2c98ff66a0144f97f4b 100644 (file)
@@ -141,9 +141,9 @@ int manager_get_session_from_creds(
         assert(m);
         assert(ret);
 
-        if (SEAT_IS_SELF(name)) /* the caller's own session */
+        if (SESSION_IS_SELF(name)) /* the caller's own session */
                 return get_sender_session(m, message, false, error, ret);
-        if (SEAT_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */
+        if (SESSION_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */
                 return get_sender_session(m, message, true, error, ret);
 
         session = hashmap_get(m->sessions, name);
@@ -591,6 +591,60 @@ static int method_list_sessions(sd_bus_message *message, void *userdata, sd_bus_
         return sd_bus_send(NULL, reply, NULL);
 }
 
+static int method_list_sessions_ex(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Manager *m = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        int r;
+
+        assert(message);
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(reply, 'a', "(sussussbto)");
+        if (r < 0)
+                return r;
+
+        Session *s;
+        HASHMAP_FOREACH(s, m->sessions) {
+                _cleanup_free_ char *path = NULL;
+                dual_timestamp idle_ts;
+                bool idle;
+
+                assert(s->user);
+
+                path = session_bus_path(s);
+                if (!path)
+                        return -ENOMEM;
+
+                r = session_get_idle_hint(s, &idle_ts);
+                if (r < 0)
+                        return r;
+                idle = r > 0;
+
+                r = sd_bus_message_append(reply, "(sussussbto)",
+                                          s->id,
+                                          (uint32_t) s->user->user_record->uid,
+                                          s->user->user_record->user_name,
+                                          s->seat ? s->seat->id : "",
+                                          (uint32_t) s->leader.pid,
+                                          session_class_to_string(s->class),
+                                          s->tty,
+                                          idle,
+                                          idle_ts.monotonic,
+                                          path);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_close_container(reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_send(NULL, reply, NULL);
+}
+
 static int method_list_users(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         Manager *m = ASSERT_PTR(userdata);
@@ -1365,10 +1419,10 @@ static int method_terminate_seat(sd_bus_message *message, void *userdata, sd_bus
 
 static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+        _cleanup_free_ struct passwd *pw = NULL;
         _cleanup_free_ char *cc = NULL;
         Manager *m = ASSERT_PTR(userdata);
         int r, b, interactive;
-        struct passwd *pw;
         const char *path;
         uint32_t uid, auth_uid;
 
@@ -1396,10 +1450,9 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu
         if (r < 0)
                 return r;
 
-        errno = 0;
-        pw = getpwuid(uid);
-        if (!pw)
-                return errno_or_else(ENOENT);
+        r = getpwuid_malloc(uid, &pw);
+        if (r < 0)
+                return r;
 
         r = bus_verify_polkit_async_full(
                         message,
@@ -1748,6 +1801,7 @@ static int execute_shutdown_or_sleep(
         int r;
 
         assert(m);
+        assert(!m->action_job);
         assert(a);
 
         if (a->inhibit_what == INHIBIT_SHUTDOWN)
@@ -1767,9 +1821,11 @@ static int execute_shutdown_or_sleep(
         if (r < 0)
                 goto error;
 
-        r = free_and_strdup(&m->action_job, p);
-        if (r < 0)
+        m->action_job = strdup(p);
+        if (!m->action_job) {
+                r = -ENOMEM;
                 goto error;
+        }
 
         m->delayed_action = a;
 
@@ -2073,7 +2129,7 @@ static int method_do_shutdown_or_sleep(
         if (action == HANDLE_SLEEP) {
                 HandleAction selected;
 
-                selected = handle_action_sleep_select(m->handle_action_sleep_mask);
+                selected = handle_action_sleep_select(m);
                 if (selected < 0)
                         return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
                                                 "None of the configured sleep operations are supported");
@@ -2125,6 +2181,12 @@ static int method_do_shutdown_or_sleep(
         if (r != 0)
                 return r;
 
+        if (m->delayed_action)
+                return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
+                                         "Action %s already in progress, refusing requested %s operation.",
+                                         handle_action_to_string(m->delayed_action->handle),
+                                         handle_action_to_string(a->handle));
+
         /* reset case we're shorting a scheduled shutdown */
         m->unlink_nologin = false;
         reset_scheduled_shutdown(m);
@@ -2401,8 +2463,9 @@ static int manager_scheduled_shutdown_handler(
 
         /* Don't allow multiple jobs being executed at the same time */
         if (m->delayed_action) {
-                r = -EALREADY;
-                log_error("Scheduled shutdown to %s failed: shutdown or sleep operation already in progress", a->target);
+                r = log_error_errno(SYNTHETIC_ERRNO(EALREADY),
+                                    "Scheduled shutdown to %s failed: shutdown or sleep operation already in progress.",
+                                    a->target);
                 goto error;
         }
 
@@ -2573,7 +2636,7 @@ static int method_can_shutdown_or_sleep(
                 sd_bus_error *error) {
 
         _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
-        bool multiple_sessions, challenge, blocked;
+        bool multiple_sessions, challenge, blocked, check_unit_state = true;
         const HandleActionData *a;
         const char *result = NULL;
         uid_t uid;
@@ -2586,10 +2649,12 @@ static int method_can_shutdown_or_sleep(
         if (action == HANDLE_SLEEP) {
                 HandleAction selected;
 
-                selected = handle_action_sleep_select(m->handle_action_sleep_mask);
+                selected = handle_action_sleep_select(m);
                 if (selected < 0)
                         return sd_bus_reply_method_return(message, "s", "na");
 
+                check_unit_state = false; /* Already handled by handle_action_sleep_select */
+
                 assert_se(a = handle_action_lookup(selected));
 
         } else if (HANDLE_ACTION_IS_SLEEP(action)) {
@@ -2623,7 +2688,7 @@ static int method_can_shutdown_or_sleep(
         multiple_sessions = r > 0;
         blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
 
-        if (a->target) {
+        if (check_unit_state && a->target) {
                 _cleanup_free_ char *load_state = NULL;
 
                 r = unit_load_state(m->bus, a->target, &load_state);
@@ -3626,6 +3691,11 @@ static const sd_bus_vtable manager_vtable[] = {
                                 SD_BUS_RESULT("a(susso)", sessions),
                                 method_list_sessions,
                                 SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("ListSessionsEx",
+                                SD_BUS_NO_ARGS,
+                                SD_BUS_RESULT("a(sussussbto)", sessions),
+                                method_list_sessions_ex,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD_WITH_ARGS("ListUsers",
                                 SD_BUS_NO_ARGS,
                                 SD_BUS_RESULT("a(uso)", users),
index e6e57ad79ee41ac5c529706b1c5e04c176ad0061..01709a2747ef13d0416aa3cef39273a1a7abeea1 100644 (file)
@@ -532,13 +532,8 @@ int user_stop(User *u, bool force) {
                 return 0;
         }
 
-        LIST_FOREACH(sessions_by_user, s, u->sessions) {
-                int k;
-
-                k = session_stop(s, force);
-                if (k < 0)
-                        r = k;
-        }
+        LIST_FOREACH(sessions_by_user, s, u->sessions)
+                RET_GATHER(r, session_stop(s, force));
 
         user_stop_service(u, force);
 
@@ -550,7 +545,7 @@ int user_stop(User *u, bool force) {
 }
 
 int user_finalize(User *u) {
-        int r = 0, k;
+        int r = 0;
 
         assert(u);
 
@@ -560,11 +555,8 @@ int user_finalize(User *u) {
         if (u->started)
                 log_debug("User %s logged out.", u->user_record->user_name);
 
-        LIST_FOREACH(sessions_by_user, s, u->sessions) {
-                k = session_finalize(s);
-                if (k < 0)
-                        r = k;
-        }
+        LIST_FOREACH(sessions_by_user, s, u->sessions)
+                RET_GATHER(r, session_finalize(s));
 
         /* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
          * are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
@@ -572,11 +564,8 @@ int user_finalize(User *u) {
          * cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because
          * a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up,
          * and do it only for normal users. */
-        if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) {
-                k = clean_ipc_by_uid(u->user_record->uid);
-                if (k < 0)
-                        r = k;
-        }
+        if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid))
+                RET_GATHER(r, clean_ipc_by_uid(u->user_record->uid));
 
         (void) unlink(u->state_file);
         user_add_to_gc_queue(u);
index 8ba094bcff18ce8e505b740582464c27ebeb9c32..e49496fce840be9deb22f9dbf3942b510b6d7ca8 100644 (file)
                        send_interface="org.freedesktop.login1.Manager"
                        send_member="ListSessions"/>
 
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="ListSessionsEx"/>
+
                 <allow send_destination="org.freedesktop.login1"
                        send_interface="org.freedesktop.login1.Manager"
                        send_member="ListUsers"/>
                        send_interface="org.freedesktop.login1.Manager"
                        send_member="SuspendThenHibernateWithFlags"/>
 
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="Sleep"/>
+
                 <allow send_destination="org.freedesktop.login1"
                        send_interface="org.freedesktop.login1.Manager"
                        send_member="CanPowerOff"/>
                        send_interface="org.freedesktop.login1.Manager"
                        send_member="CanSuspendThenHibernate"/>
 
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="CanSleep"/>
+
                 <allow send_destination="org.freedesktop.login1"
                        send_interface="org.freedesktop.login1.Manager"
                        send_member="ScheduleShutdown"/>
index d7814a7275dfb4f4ee96325036eb30ce04dc440b..db623a3eaa1a1029d251acb6e035e52611e67fd2 100644 (file)
@@ -802,13 +802,21 @@ typedef struct SessionContext {
         const char *runtime_max_sec;
 } SessionContext;
 
-static int create_session_message(sd_bus *bus, pam_handle_t *handle, const SessionContext *context, bool avoid_pidfd, sd_bus_message **ret) {
+static int create_session_message(
+                sd_bus *bus,
+                pam_handle_t *handle,
+                const SessionContext *context,
+                bool avoid_pidfd,
+                sd_bus_message **ret) {
+
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
-        int r, pidfd = -EBADFD;
+        _cleanup_close_ int pidfd = -EBADF;
+        int r;
 
         assert(bus);
         assert(handle);
         assert(context);
+        assert(ret);
 
         if (!avoid_pidfd) {
                 pidfd = pidfd_open(getpid_cached(), 0);
@@ -1066,39 +1074,39 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         r = create_session_message(bus,
                                    handle,
                                    &context,
-                                   false /* avoid_pidfd = */,
+                                   /* avoid_pidfd = */ false,
                                    &m);
         if (r < 0)
                 return pam_bus_log_create_error(handle, r);
 
         r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+        if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+                sd_bus_error_free(&error);
+                pam_debug_syslog(handle, debug,
+                                 "CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
+
+                m = sd_bus_message_unref(m);
+                r = create_session_message(bus,
+                                           handle,
+                                           &context,
+                                           /* avoid_pidfd = */ true,
+                                           &m);
+                if (r < 0)
+                        return pam_bus_log_create_error(handle, r);
+
+                r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+        }
         if (r < 0) {
                 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
+                        /* We are already in a session, don't do anything */
                         pam_debug_syslog(handle, debug,
                                          "Not creating session: %s", bus_error_message(&error, r));
-                        /* We are already in a session, don't do anything */
                         goto success;
-                } else if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
-                        pam_debug_syslog(handle, debug,
-                                         "CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
-
-                        m = sd_bus_message_unref(m);
-                        r = create_session_message(bus,
-                                                   handle,
-                                                   &context,
-                                                   true /* avoid_pidfd = */,
-                                                   &m);
-                        if (r < 0)
-                                return pam_bus_log_create_error(handle, r);
-
-                        sd_bus_error_free(&error);
-                        r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
-                }
-                if (r < 0) {
-                        pam_syslog(handle, LOG_ERR,
-                                   "Failed to create session: %s", bus_error_message(&error, r));
-                        return PAM_SESSION_ERR;
                 }
+
+                pam_syslog(handle, LOG_ERR,
+                           "Failed to create session: %s", bus_error_message(&error, r));
+                return PAM_SESSION_ERR;
         }
 
         r = sd_bus_message_read(reply,
index 6c2c2232fe3a6b46a908f52439409323f5a552ff..a7a53eacaf8861f49c585b3c1fdcf62169a77103 100644 (file)
@@ -232,7 +232,12 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd
                 if (streq(us, them))
                         return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name);
 
-                r = namespace_open(m->leader.pid, NULL, NULL, &netns_fd, NULL, NULL);
+                r = namespace_open(m->leader.pid,
+                                   /* ret_pidns_fd = */ NULL,
+                                   /* ret_mntns_fd = */ NULL,
+                                   &netns_fd,
+                                   /* ret_userns_fd = */ NULL,
+                                   /* ret_root_fd = */ NULL);
                 if (r < 0)
                         return r;
 
@@ -366,7 +371,12 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
                 _cleanup_fclose_ FILE *f = NULL;
                 pid_t child;
 
-                r = namespace_open(m->leader.pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
+                r = namespace_open(m->leader.pid,
+                                   &pidns_fd,
+                                   &mntns_fd,
+                                   /* ret_netns_fd = */ NULL,
+                                   /* ret_userns_fd = */ NULL,
+                                   &root_fd);
                 if (r < 0)
                         return r;
 
@@ -1069,7 +1079,12 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda
                 _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
                 pid_t child;
 
-                r = namespace_open(m->leader.pid, NULL, &mntns_fd, NULL, NULL, &root_fd);
+                r = namespace_open(m->leader.pid,
+                                   /* ret_pidns_fd = */ NULL,
+                                   &mntns_fd,
+                                   /* ret_netns_fd = */ NULL,
+                                   /* ret_userns_fd = */ NULL,
+                                   &root_fd);
                 if (r < 0)
                         return r;
 
index 9e909d1abce8bb39d47a3c477002eb48141bb5d3..97b4b928e768af45380a50d3f0660b73c56e40ec 100644 (file)
@@ -33,11 +33,6 @@ static TunTap* TUNTAP(NetDev *netdev) {
         }
 }
 
-static void *close_fd_ptr(void *p) {
-        safe_close(PTR_TO_FD(p));
-        return NULL;
-}
-
 DEFINE_PRIVATE_HASH_OPS_FULL(named_fd_hash_ops, char, string_hash_func, string_compare_func, free, void, close_fd_ptr);
 
 int manager_add_tuntap_fd(Manager *m, int fd, const char *name) {
index 57c3923c1b1764b755ba77c8ce64f010fe8be59c..0195dce1bf04c6654623661621e869dcb4b541ca 100644 (file)
@@ -1196,20 +1196,33 @@ static int wireguard_verify(NetDev *netdev, const char *filename) {
                         if (r < 0)
                                 return log_oom();
 
+                        /* For route_section_verify() below. */
+                        r = config_section_new(peer->section->filename, peer->section->line, &route->section);
+                        if (r < 0)
+                                return log_oom();
+
+                        route->source = NETWORK_CONFIG_SOURCE_STATIC;
                         route->family = ipmask->family;
                         route->dst = ipmask->ip;
                         route->dst_prefixlen = ipmask->cidr;
-                        route->scope = RT_SCOPE_UNIVERSE;
                         route->protocol = RTPROT_STATIC;
+                        route->protocol_set = true;
                         route->table = peer->route_table_set ? peer->route_table : w->route_table;
+                        route->table_set = true;
                         route->priority = peer->route_priority_set ? peer->route_priority : w->route_priority;
-                        if (route->priority == 0 && route->family == AF_INET6)
-                                route->priority = IP6_RT_PRIO_USER;
-                        route->source = NETWORK_CONFIG_SOURCE_STATIC;
+                        route->priority_set = true;
 
-                        r = set_ensure_consume(&w->routes, &route_hash_ops, TAKE_PTR(route));
+                        if (route_section_verify(route) < 0)
+                                continue;
+
+                        r = set_ensure_put(&w->routes, &route_hash_ops, route);
                         if (r < 0)
                                 return log_oom();
+                        if (r == 0)
+                                continue;
+
+                        route->wireguard = w;
+                        TAKE_PTR(route);
                 }
         }
 
index 95e22cd9e3981181e60a9611e20307b0472146a6..6f5100c6056a902b24553a85ddb32da9d9c96157 100644 (file)
@@ -104,9 +104,9 @@ static int check_netns_match(void) {
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to network service /run/systemd/netif/io.systemd.Network: %m");
 
-        r = varlink_call(vl, "io.systemd.Network.GetNamespaceId", NULL, &reply, NULL, 0);
+        r = varlink_call_and_log(vl, "io.systemd.Network.GetNamespaceId", /* parameters= */ NULL, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to issue GetNamespaceId() varlink call: %m");
+                return r;
 
         static const JsonDispatch dispatch_table[] = {
                 { "NamespaceId", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, 0, JSON_MANDATORY },
index e54c12236140ab37aba2ecccb0a2f8ff11eab9d2..631c64ec06e85db2971e86fd474aacffc9ef0228 100644 (file)
@@ -615,12 +615,12 @@ int address_dup(const Address *src, Address **ret) {
         dest->nft_set_context.n_sets = 0;
 
         if (src->family == AF_INET) {
-                r = free_and_strdup(&dest->label, src->label);
+                r = strdup_or_null(src->label, &dest->label);
                 if (r < 0)
                         return r;
         }
 
-        r = free_and_strdup(&dest->netlabel, src->netlabel);
+        r = strdup_or_null(src->netlabel, &dest->netlabel);
         if (r < 0)
                 return r;
 
@@ -1111,18 +1111,35 @@ static int address_set_netlink_message(const Address *address, sd_netlink_messag
         return 0;
 }
 
-static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) {
         int r;
 
         assert(m);
-        assert(link);
+        assert(rreq);
 
-        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+        Link *link = ASSERT_PTR(rreq->link);
+        Address *address = ASSERT_PTR(rreq->userdata);
+
+        if (link->state == LINK_STATE_LINGER)
                 return 0;
 
         r = sd_netlink_message_get_errno(m);
-        if (r < 0 && r != -EADDRNOTAVAIL)
-                log_link_message_warning_errno(link, m, r, "Could not drop address");
+        if (r < 0) {
+                log_link_message_full_errno(link, m,
+                                            (r == -EADDRNOTAVAIL || !address->link) ? LOG_DEBUG : LOG_WARNING,
+                                            r, "Could not drop address");
+
+                if (address->link) {
+                        /* If the address cannot be removed, then assume the address is already removed. */
+                        log_address_debug(address, "Forgetting", link);
+
+                        Request *req;
+                        if (address_get_request(link, address, &req) >= 0)
+                                address_enter_removed(req->userdata);
+
+                        (void) address_drop(address);
+                }
+        }
 
         return 1;
 }
@@ -1149,13 +1166,9 @@ int address_remove(Address *address, Link *link) {
         if (r < 0)
                 return log_link_warning_errno(link, r, "Could not set netlink attributes: %m");
 
-        r = netlink_call_async(link->manager->rtnl, NULL, m,
-                               address_remove_handler,
-                               link_netlink_destroy_callback, link);
+        r = link_remove_request_add(link, address, address, link->manager->rtnl, m, address_remove_handler);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Could not send rtnetlink message: %m");
-
-        link_ref(link);
+                return log_link_warning_errno(link, r, "Could not queue rtnetlink message: %m");
 
         address_enter_removing(address);
 
@@ -1180,7 +1193,7 @@ int address_remove_and_cancel(Address *address, Link *link) {
          * notification about the request, then explicitly remove the address. */
         if (address_get_request(link, address, &req) >= 0) {
                 waiting = req->waiting_reply;
-                request_detach(link->manager, req);
+                request_detach(req);
                 address_cancel_requesting(address);
         }
 
index 2158c773931ae4448aefcc66e790a07e7da8391a..b0e4afdc45fb950b22714fa9b7ed3dc1a7c87924 100644 (file)
@@ -265,7 +265,7 @@ static int dhcp_pd_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques
 
         assert(link);
 
-        r = route_configure_handler_internal(rtnl, m, link, "Failed to add prefix route for DHCP delegated subnet prefix");
+        r = route_configure_handler_internal(rtnl, m, link, route, "Failed to add prefix route for DHCP delegated subnet prefix");
         if (r <= 0)
                 return r;
 
@@ -300,13 +300,16 @@ static int dhcp_pd_request_route(Link *link, const struct in6_addr *prefix, usec
         route->priority = link->network->dhcp_pd_route_metric;
         route->lifetime_usec = lifetime_usec;
 
+        r = route_adjust_nexthops(route, link);
+        if (r < 0)
+                return r;
+
         if (route_get(NULL, link, route, &existing) < 0)
                 link->dhcp_pd_configured = false;
         else
                 route_unmark(existing);
 
-        r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages,
-                               dhcp_pd_route_handler, NULL);
+        r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler);
         if (r < 0)
                 return log_link_error_errno(link, r, "Failed to request DHCP-PD prefix route: %m");
 
@@ -625,7 +628,7 @@ static int dhcp4_unreachable_route_handler(sd_netlink *rtnl, sd_netlink_message
 
         assert(link);
 
-        r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv4 delegated prefix");
+        r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv4 delegated prefix");
         if (r <= 0)
                 return r;
 
@@ -641,7 +644,7 @@ static int dhcp6_unreachable_route_handler(sd_netlink *rtnl, sd_netlink_message
 
         assert(link);
 
-        r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv6 delegated prefix");
+        r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv6 delegated prefix");
         if (r <= 0)
                 return r;
 
@@ -695,12 +698,16 @@ static int dhcp_request_unreachable_route(
         route->priority = IP6_RT_PRIO_USER;
         route->lifetime_usec = lifetime_usec;
 
+        r = route_adjust_nexthops(route, link);
+        if (r < 0)
+                return r;
+
         if (route_get(link->manager, NULL, route, &existing) < 0)
                 *configured = false;
         else
                 route_unmark(existing);
 
-        r = link_request_route(link, TAKE_PTR(route), true, counter, callback, NULL);
+        r = link_request_route(link, route, counter, callback);
         if (r < 0)
                 return log_link_error_errno(link, r, "Failed to request unreachable route for DHCP delegated prefix %s: %m",
                                             IN6_ADDR_PREFIX_TO_STRING(addr, prefixlen));
@@ -782,20 +789,23 @@ static int dhcp4_pd_request_default_gateway_on_6rd_tunnel(Link *link, const stru
 
         route->source = NETWORK_CONFIG_SOURCE_DHCP_PD;
         route->family = AF_INET6;
-        route->gw_family = AF_INET6;
-        route->gw.in6.s6_addr32[3] = br_address->s_addr;
+        route->nexthop.family = AF_INET6;
+        route->nexthop.gw.in6.s6_addr32[3] = br_address->s_addr;
         route->scope = RT_SCOPE_UNIVERSE;
         route->protocol = RTPROT_DHCP;
         route->priority = IP6_RT_PRIO_USER;
         route->lifetime_usec = lifetime_usec;
 
+        r = route_adjust_nexthops(route, link);
+        if (r < 0)
+                return r;
+
         if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
                 link->dhcp_pd_configured = false;
         else
                 route_unmark(existing);
 
-        r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages,
-                               dhcp_pd_route_handler, NULL);
+        r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler);
         if (r < 0)
                 return log_link_debug_errno(link, r, "Failed to request default gateway for DHCP delegated prefix: %m");
 
index 8987b76c52b27d98a8abb237e89256c41f66f8c4..28702ff4469073d7c5b6f45e05a4a42e2670f8f5 100644 (file)
@@ -342,12 +342,9 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request
         assert(m);
         assert(link);
 
-        r = sd_netlink_message_get_errno(m);
-        if (r < 0 && r != -EEXIST) {
-                log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 route");
-                link_enter_failed(link);
-                return 1;
-        }
+        r = route_configure_handler_internal(rtnl, m, link, route, "Could not set DHCPv4 route");
+        if (r <= 0)
+                return r;
 
         r = dhcp4_check_ready(link);
         if (r < 0)
@@ -356,8 +353,7 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request
         return 1;
 }
 
-static int dhcp4_request_route(Route *in, Link *link) {
-        _cleanup_(route_freep) Route *route = in;
+static int dhcp4_request_route(Route *route, Link *link) {
         struct in_addr server;
         Route *existing;
         int r;
@@ -393,13 +389,16 @@ static int dhcp4_request_route(Route *in, Link *link) {
         if (r < 0)
                 return r;
 
+        r = route_adjust_nexthops(route, link);
+        if (r < 0)
+                return r;
+
         if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
                 link->dhcp4_configured = false;
         else
                 route_unmark(existing);
 
-        return link_request_route(link, TAKE_PTR(route), true, &link->dhcp4_messages,
-                                  dhcp4_route_handler, NULL);
+        return link_request_route(link, route, &link->dhcp4_messages, dhcp4_route_handler);
 }
 
 static bool link_prefixroute(Link *link) {
@@ -432,7 +431,7 @@ static int dhcp4_request_prefix_route(Link *link) {
         if (r < 0)
                 return r;
 
-        return dhcp4_request_route(TAKE_PTR(route), link);
+        return dhcp4_request_route(route, link);
 }
 
 static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw) {
@@ -457,15 +456,14 @@ static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw)
         route->prefsrc.in = address;
         route->scope = RT_SCOPE_LINK;
 
-        return dhcp4_request_route(TAKE_PTR(route), link);
+        return dhcp4_request_route(route, link);
 }
 
 static int dhcp4_request_route_auto(
-                Route *in,
+                Route *route,
                 Link *link,
                 const struct in_addr *gw) {
 
-        _cleanup_(route_freep) Route *route = in;
         struct in_addr address;
         int r;
 
@@ -485,8 +483,8 @@ static int dhcp4_request_route_auto(
                                        IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw));
 
                 route->scope = RT_SCOPE_HOST;
-                route->gw_family = AF_UNSPEC;
-                route->gw = IN_ADDR_NULL;
+                route->nexthop.family = AF_UNSPEC;
+                route->nexthop.gw = IN_ADDR_NULL;
                 route->prefsrc = IN_ADDR_NULL;
 
         } else if (in4_addr_equal(&route->dst.in, &address)) {
@@ -496,8 +494,8 @@ static int dhcp4_request_route_auto(
                                        IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw));
 
                 route->scope = RT_SCOPE_HOST;
-                route->gw_family = AF_UNSPEC;
-                route->gw = IN_ADDR_NULL;
+                route->nexthop.family = AF_UNSPEC;
+                route->nexthop.gw = IN_ADDR_NULL;
                 route->prefsrc.in = address;
 
         } else if (in4_addr_is_null(gw)) {
@@ -519,8 +517,8 @@ static int dhcp4_request_route_auto(
                 }
 
                 route->scope = RT_SCOPE_LINK;
-                route->gw_family = AF_UNSPEC;
-                route->gw = IN_ADDR_NULL;
+                route->nexthop.family = AF_UNSPEC;
+                route->nexthop.gw = IN_ADDR_NULL;
                 route->prefsrc.in = address;
 
         } else {
@@ -529,12 +527,12 @@ static int dhcp4_request_route_auto(
                         return r;
 
                 route->scope = RT_SCOPE_UNIVERSE;
-                route->gw_family = AF_INET;
-                route->gw.in = *gw;
+                route->nexthop.family = AF_INET;
+                route->nexthop.gw.in = *gw;
                 route->prefsrc.in = address;
         }
 
-        return dhcp4_request_route(TAKE_PTR(route), link);
+        return dhcp4_request_route(route, link);
 }
 
 static int dhcp4_request_classless_static_or_static_routes(Link *link) {
@@ -574,7 +572,7 @@ static int dhcp4_request_classless_static_or_static_routes(Link *link) {
                 if (r < 0)
                         return r;
 
-                r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw);
+                r = dhcp4_request_route_auto(route, link, &gw);
                 if (r < 0)
                         return r;
         }
@@ -622,11 +620,11 @@ static int dhcp4_request_default_gateway(Link *link) {
                 return r;
 
         /* Next, add a default gateway. */
-        route->gw_family = AF_INET;
-        route->gw.in = router;
+        route->nexthop.family = AF_INET;
+        route->nexthop.gw.in = router;
         route->prefsrc.in = address;
 
-        return dhcp4_request_route(TAKE_PTR(route), link);
+        return dhcp4_request_route(route, link);
 }
 
 static int dhcp4_request_semi_static_routes(Link *link) {
@@ -644,7 +642,7 @@ static int dhcp4_request_semi_static_routes(Link *link) {
                 if (!rt->gateway_from_dhcp_or_ra)
                         continue;
 
-                if (rt->gw_family != AF_INET)
+                if (rt->nexthop.family != AF_INET)
                         continue;
 
                 assert(rt->family == AF_INET);
@@ -662,13 +660,13 @@ static int dhcp4_request_semi_static_routes(Link *link) {
                 if (r < 0)
                         return r;
 
-                r = route_dup(rt, &route);
+                r = route_dup(rt, NULL, &route);
                 if (r < 0)
                         return r;
 
-                route->gw.in = gw;
+                route->nexthop.gw.in = gw;
 
-                r = dhcp4_request_route(TAKE_PTR(route), link);
+                r = dhcp4_request_route(route, link);
                 if (r < 0)
                         return r;
         }
@@ -711,7 +709,7 @@ static int dhcp4_request_routes_to_servers(
                 route->dst.in = *dst;
                 route->dst_prefixlen = 32;
 
-                r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw);
+                r = dhcp4_request_route_auto(route, link, &gw);
                 if (r < 0)
                         return r;
         }
index e62e57b7652d4587a8119f132f1d2bddef7312a0..b2bcc1bf75eeb80bea6da60ef838f7e5ec0e05fe 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <linux/nexthop.h>
 
+#include "dhcp-lease-internal.h"
 #include "dhcp-server-lease-internal.h"
 #include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.h"
@@ -237,7 +238,7 @@ static int route_append_json(Route *route, JsonVariant **array) {
                                 JSON_BUILD_PAIR_INTEGER("Family", route->family),
                                 JSON_BUILD_PAIR_IN_ADDR("Destination", &route->dst, route->family),
                                 JSON_BUILD_PAIR_UNSIGNED("DestinationPrefixLength", route->dst_prefixlen),
-                                JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->gw, route->gw_family),
+                                JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->nexthop.gw, route->nexthop.family),
                                 JSON_BUILD_PAIR_CONDITION(route->src_prefixlen > 0,
                                                           "Source", JSON_BUILD_IN_ADDR(&route->src, route->family)),
                                 JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("SourcePrefixLength", route->src_prefixlen),
@@ -1170,6 +1171,29 @@ static int dhcp_client_pd_append_json(Link *link, JsonVariant **v) {
         return json_variant_set_field_non_null(v, "6rdPrefix", array);
 }
 
+static int dhcp_client_private_options_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+        int r;
+
+        assert(link);
+        assert(v);
+
+        if (!link->dhcp_lease)
+                return 0;
+
+        LIST_FOREACH(options, option, link->dhcp_lease->private_options) {
+
+                r = json_variant_append_arrayb(
+                                &array,
+                                JSON_BUILD_OBJECT(
+                                               JSON_BUILD_PAIR_UNSIGNED("Option", option->tag),
+                                               JSON_BUILD_PAIR_HEX("PrivateOptionData", option->data, option->length)));
+                if (r < 0)
+                        return 0;
+        }
+        return json_variant_set_field_non_null(v, "PrivateOptions", array);
+}
+
 static int dhcp_client_append_json(Link *link, JsonVariant **v) {
         _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
         int r;
@@ -1188,6 +1212,10 @@ static int dhcp_client_append_json(Link *link, JsonVariant **v) {
         if (r < 0)
                 return r;
 
+        r = dhcp_client_private_options_append_json(link, &w);
+        if (r < 0)
+                return r;
+
         return json_variant_set_field_non_null(v, "DHCPv4Client", w);
 }
 
index 5dd2fc12f6439fc78833cf9a3f862738d2c1a449..49ed8f59bb1804c67d1eb46203e3edd8135fe3a7 100644 (file)
 #include "udev-util.h"
 #include "vrf.h"
 
+void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret) {
+        assert(link);
+        assert(ret);
+
+        if (link->network && operational_state_range_is_valid(&link->network->required_operstate_for_online))
+                *ret = link->network->required_operstate_for_online;
+        else if (link->iftype == ARPHRD_CAN)
+                *ret = (const LinkOperationalStateRange) {
+                        .min = LINK_OPERSTATE_CARRIER,
+                        .max = LINK_OPERSTATE_CARRIER,
+                };
+        else
+                *ret = LINK_OPERSTATE_RANGE_DEFAULT;
+}
+
 bool link_ipv6_enabled(Link *link) {
         assert(link);
 
@@ -381,6 +396,8 @@ int link_stop_engines(Link *link, bool may_keep_dhcp) {
 }
 
 void link_enter_failed(Link *link) {
+        int r;
+
         assert(link);
 
         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
@@ -390,7 +407,22 @@ void link_enter_failed(Link *link) {
 
         link_set_state(link, LINK_STATE_FAILED);
 
-        (void) link_stop_engines(link, false);
+        if (!ratelimit_below(&link->automatic_reconfigure_ratelimit)) {
+                log_link_warning(link, "The interface entered the failed state frequently, refusing to reconfigure it automatically.");
+                goto stop;
+        }
+
+        log_link_info(link, "Trying to reconfigure the interface.");
+        r = link_reconfigure(link, /* force = */ true);
+        if (r < 0) {
+                log_link_warning_errno(link, r, "Failed to reconfigure interface: %m");
+                goto stop;
+        }
+
+        return;
+
+stop:
+        (void) link_stop_engines(link, /* may_keep_dhcp = */ false);
 }
 
 void link_check_ready(Link *link) {
@@ -416,11 +448,9 @@ void link_check_ready(Link *link) {
         if (!link->activated)
                 return (void) log_link_debug(link, "%s(): link is not activated.", __func__);
 
-        if (link->iftype == ARPHRD_CAN) {
+        if (link->iftype == ARPHRD_CAN)
                 /* let's shortcut things for CAN which doesn't need most of checks below. */
-                link_set_state(link, LINK_STATE_CONFIGURED);
-                return;
-        }
+                goto ready;
 
         if (!link->stacked_netdevs_created)
                 return (void) log_link_debug(link, "%s(): stacked netdevs are not created.", __func__);
@@ -972,7 +1002,7 @@ static int link_drop_requests(Link *link) {
                                 ;
                         }
 
-                request_detach(link->manager, req);
+                request_detach(req);
         }
 
         return ret;
@@ -1830,12 +1860,16 @@ void link_update_operstate(Link *link, bool also_update_master) {
         else
                 operstate = LINK_OPERSTATE_ENSLAVED;
 
+        LinkOperationalStateRange req;
+        link_required_operstate_for_online(link, &req);
+
         /* Only determine online state for managed links with RequiredForOnline=yes */
         if (!link->network || !link->network->required_for_online)
                 online_state = _LINK_ONLINE_STATE_INVALID;
-        else if (operstate < link->network->required_operstate_for_online.min ||
-                 operstate > link->network->required_operstate_for_online.max)
+
+        else if (!operational_state_is_in_range(operstate, &req))
                 online_state = LINK_ONLINE_STATE_OFFLINE;
+
         else {
                 AddressFamily required_family = link->network->required_family_for_online;
                 bool needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4;
@@ -1846,14 +1880,14 @@ void link_update_operstate(Link *link, bool also_update_master) {
                  * to offline in the blocks below. */
                 online_state = LINK_ONLINE_STATE_ONLINE;
 
-                if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_DEGRADED) {
+                if (req.min >= LINK_OPERSTATE_DEGRADED) {
                         if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED)
                                 online_state = LINK_ONLINE_STATE_OFFLINE;
                         if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_DEGRADED)
                                 online_state = LINK_ONLINE_STATE_OFFLINE;
                 }
 
-                if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_ROUTABLE) {
+                if (req.min >= LINK_OPERSTATE_ROUTABLE) {
                         if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_ROUTABLE)
                                 online_state = LINK_ONLINE_STATE_OFFLINE;
                         if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_ROUTABLE)
@@ -2554,6 +2588,7 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
                 .n_ref = 1,
                 .state = LINK_STATE_PENDING,
                 .online_state = _LINK_ONLINE_STATE_INVALID,
+                .automatic_reconfigure_ratelimit = (const RateLimit) { .interval = 10 * USEC_PER_SEC, .burst = 5 },
                 .ifindex = ifindex,
                 .iftype = iftype,
                 .ifname = TAKE_PTR(ifname),
index cad9fa8410685fa20b968c66ef34d7623945012a..b5b1995361a77ad38d11a6d8d5332d16419f6218 100644 (file)
@@ -25,6 +25,7 @@
 #include "networkd-ipv6ll.h"
 #include "networkd-util.h"
 #include "ordered-set.h"
+#include "ratelimit.h"
 #include "resolve-util.h"
 #include "set.h"
 
@@ -106,6 +107,7 @@ typedef struct Link {
         LinkAddressState ipv4_address_state;
         LinkAddressState ipv6_address_state;
         LinkOnlineState online_state;
+        RateLimit automatic_reconfigure_ratelimit;
 
         unsigned static_address_messages;
         unsigned static_address_label_messages;
@@ -257,3 +259,5 @@ int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *message, Man
 
 int link_flags_to_string_alloc(uint32_t flags, char **ret);
 const char *kernel_operstate_to_string(int t) _const_;
+
+void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret);
index a8906f81c1ba606f0326a73979b31e6a5438d8cd..035537c869aa73ca33ecd94e060bc5fa250ee955 100644 (file)
@@ -279,6 +279,31 @@ static int property_get_namespace_id(
         return sd_bus_message_append(reply, "t", id);
 }
 
+static int property_get_namespace_nsid(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        uint32_t nsid = UINT32_MAX;
+        int r;
+
+        assert(bus);
+        assert(reply);
+
+        /* Returns our own "nsid", which is another ID for the network namespace, different from the inode
+         * number. */
+
+        r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid);
+        if (r < 0)
+                log_warning_errno(r, "Failed to query network nsid, ignoring: %m");
+
+        return sd_bus_message_append(reply, "u", nsid);
+}
+
 static const sd_bus_vtable manager_vtable[] = {
         SD_BUS_VTABLE_START(0),
 
@@ -289,6 +314,7 @@ static const sd_bus_vtable manager_vtable[] = {
         SD_BUS_PROPERTY("IPv6AddressState", "s", property_get_address_state, offsetof(Manager, ipv6_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("OnlineState", "s", property_get_online_state, offsetof(Manager, online_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("NamespaceId", "t", property_get_namespace_id, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("NamespaceNSID", "u", property_get_namespace_nsid, 0, 0),
 
         SD_BUS_METHOD_WITH_ARGS("ListLinks",
                                 SD_BUS_NO_ARGS,
index d9750d9b641f04133aaffc98cd64156bd3b36ac7..30aebb5d9e1a6ed621c667fbff3a504471b1f4da 100644 (file)
@@ -25,23 +25,35 @@ static int vl_method_get_states(Varlink *link, JsonVariant *parameters, VarlinkM
 }
 
 static int vl_method_get_namespace_id(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
-        uint64_t id;
+        uint64_t inode = 0;
+        uint32_t nsid = UINT32_MAX;
+        int r;
 
         assert(link);
 
         if (json_variant_elements(parameters) > 0)
                 return varlink_error_invalid_parameter(link, parameters);
 
+        /* Network namespaces have two identifiers: the inode number (which all namespace types have), and
+         * the "nsid" (aka the "cookie"), which only network namespaces know as a concept, and which is not
+         * assigned by default, but once it is, is fixed. Let's return both, to avoid any confusion which one
+         * this is. */
+
         struct stat st;
-        if (stat("/proc/self/ns/net", &st) < 0) {
+        if (stat("/proc/self/ns/net", &st) < 0)
                 log_warning_errno(errno, "Failed to stat network namespace, ignoring: %m");
-                id = 0;
-        } else
-                id = st.st_ino;
+        else
+                inode = st.st_ino;
+
+        r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid);
+        if (r < 0)
+                log_warning_errno(r, "Failed to query network nsid, ignoring: %m");
 
         return varlink_replyb(link,
                               JSON_BUILD_OBJECT(
-                                              JSON_BUILD_PAIR_UNSIGNED("NamespaceId", id)));
+                                              JSON_BUILD_PAIR_UNSIGNED("NamespaceId", inode),
+                                              JSON_BUILD_PAIR_CONDITION(nsid == UINT32_MAX, "NamespaceNSID", JSON_BUILD_NULL),
+                                              JSON_BUILD_PAIR_CONDITION(nsid != UINT32_MAX, "NamespaceNSID", JSON_BUILD_UNSIGNED(nsid))));
 }
 
 int manager_connect_varlink(Manager *m) {
index 8933fc49776d6053b37c412ab6f9d90d293d3daf..6f3d90d64c9debe56a4637a61d9fa97051784aad 100644 (file)
@@ -424,6 +424,7 @@ static int manager_connect_rtnl(Manager *m, int fd) {
 static int manager_post_handler(sd_event_source *s, void *userdata) {
         Manager *manager = ASSERT_PTR(userdata);
 
+        (void) manager_process_remove_requests(manager);
         (void) manager_process_requests(manager);
         (void) manager_clean_all(manager);
         return 0;
@@ -594,6 +595,7 @@ Manager* manager_free(Manager *m) {
                 (void) link_stop_engines(link, true);
 
         m->request_queue = ordered_set_free(m->request_queue);
+        m->remove_request_queue = ordered_set_free(m->remove_request_queue);
 
         m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref);
         m->new_wlan_ifindices = set_free(m->new_wlan_ifindices);
index a2edfd0e79c0c8c807bca728e1e243271b6e8f4a..a97ae8ea213e67149a0d902cab6a2381a40b847d 100644 (file)
@@ -102,6 +102,7 @@ struct Manager {
 
         bool request_queued;
         OrderedSet *request_queue;
+        OrderedSet *remove_request_queue;
 
         Hashmap *tuntap_fds_by_name;
 };
index 7bb2825b3c2d745171293f91c2855749800467bf..ca8bbb2d5ef6766b1c6ddd8f6e4980fc25ef693b 100644 (file)
@@ -139,7 +139,7 @@ static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request
 
         assert(link);
 
-        r = route_configure_handler_internal(rtnl, m, link, "Could not set NDisc route");
+        r = route_configure_handler_internal(rtnl, m, link, route, "Could not set NDisc route");
         if (r <= 0)
                 return r;
 
@@ -172,8 +172,7 @@ static void ndisc_set_route_priority(Link *link, Route *route) {
         }
 }
 
-static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) {
-        _cleanup_(route_freep) Route *route = in;
+static int ndisc_request_route(Route *route, Link *link, sd_ndisc_router *rt) {
         struct in6_addr router;
         uint8_t hop_limit = 0;
         uint32_t mtu = 0;
@@ -218,10 +217,13 @@ static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) {
         if (r < 0)
                 return r;
 
+        r = route_adjust_nexthops(route, link);
+        if (r < 0)
+                return r;
+
         is_new = route_get(NULL, link, route, NULL) < 0;
 
-        r = link_request_route(link, TAKE_PTR(route), true, &link->ndisc_messages,
-                               ndisc_route_handler, NULL);
+        r = link_request_route(link, route, &link->ndisc_messages, ndisc_route_handler);
         if (r < 0)
                 return r;
         if (r > 0 && is_new)
@@ -321,11 +323,11 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
 
                 route->family = AF_INET6;
                 route->pref = preference;
-                route->gw_family = AF_INET6;
-                route->gw.in6 = gateway;
+                route->nexthop.family = AF_INET6;
+                route->nexthop.gw.in6 = gateway;
                 route->lifetime_usec = lifetime_usec;
 
-                r = ndisc_request_route(TAKE_PTR(route), link, rt);
+                r = ndisc_request_route(route, link, rt);
                 if (r < 0)
                         return log_link_warning_errno(link, r, "Could not request default route: %m");
         }
@@ -337,19 +339,19 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
                 if (!route_gw->gateway_from_dhcp_or_ra)
                         continue;
 
-                if (route_gw->gw_family != AF_INET6)
+                if (route_gw->nexthop.family != AF_INET6)
                         continue;
 
-                r = route_dup(route_gw, &route);
+                r = route_dup(route_gw, NULL, &route);
                 if (r < 0)
                         return r;
 
-                route->gw.in6 = gateway;
+                route->nexthop.gw.in6 = gateway;
                 if (!route->pref_set)
                         route->pref = preference;
                 route->lifetime_usec = lifetime_usec;
 
-                r = ndisc_request_route(TAKE_PTR(route), link, rt);
+                r = ndisc_request_route(route, link, rt);
                 if (r < 0)
                         return log_link_warning_errno(link, r, "Could not request gateway: %m");
         }
@@ -391,6 +393,42 @@ static int ndisc_router_process_icmp6_ratelimit(Link *link, sd_ndisc_router *rt)
         return 0;
 }
 
+static int ndisc_router_process_retransmission_time(Link *link, sd_ndisc_router *rt) {
+        usec_t retrans_time, msec;
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(rt);
+
+        if (!link->network->ipv6_accept_ra_use_retransmission_time)
+                return 0;
+
+        r = sd_ndisc_router_get_retransmission_time(rt, &retrans_time);
+        if (r < 0) {
+                log_link_debug_errno(link, r, "Failed to get retransmission time from RA, ignoring: %m");
+                return 0;
+        }
+
+        /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */
+        if (!timestamp_is_set(retrans_time))
+                return 0;
+
+        msec = DIV_ROUND_UP(retrans_time, USEC_PER_MSEC);
+        if (msec <= 0 || msec > UINT32_MAX) {
+                log_link_debug(link, "Failed to get retransmission time from RA - out of range (%"PRIu64"), ignoring", msec);
+                return 0;
+        }
+
+        /* Set the retransmission time for Neigbor Solicitations. */
+        r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", (uint32_t) msec);
+        if (r < 0)
+                log_link_warning_errno(
+                        link, r, "Failed to apply neighbor retransmission time (%"PRIu64"), ignoring: %m", msec);
+
+        return 0;
+}
+
 static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
         usec_t lifetime_valid_usec, lifetime_preferred_usec;
         _cleanup_set_free_ Set *addresses = NULL;
@@ -499,7 +537,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
         route->pref = preference;
         route->lifetime_usec = lifetime_usec;
 
-        r = ndisc_request_route(TAKE_PTR(route), link, rt);
+        r = ndisc_request_route(route, link, rt);
         if (r < 0)
                 return log_link_warning_errno(link, r, "Could not request prefix route: %m");
 
@@ -626,13 +664,13 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
 
         route->family = AF_INET6;
         route->pref = preference;
-        route->gw.in6 = gateway;
-        route->gw_family = AF_INET6;
+        route->nexthop.gw.in6 = gateway;
+        route->nexthop.family = AF_INET6;
         route->dst.in6 = dst;
         route->dst_prefixlen = prefixlen;
         route->lifetime_usec = lifetime_usec;
 
-        r = ndisc_request_route(TAKE_PTR(route), link, rt);
+        r = ndisc_request_route(route, link, rt);
         if (r < 0)
                 return log_link_warning_errno(link, r, "Could not request additional route: %m");
 
@@ -1352,6 +1390,10 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
         if (r < 0)
                 return r;
 
+        r = ndisc_router_process_retransmission_time(link, rt);
+        if (r < 0)
+                return r;
+
         r = ndisc_router_process_options(link, rt);
         if (r < 0)
                 return r;
index b9c97841b0e171c15f3013479770aa5b5d8e212a..d30282fe1ee547e25e9ac5c70400f4b85b5e28b9 100644 (file)
@@ -399,19 +399,36 @@ int link_request_static_neighbors(Link *link) {
         return 0;
 }
 
-static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) {
         int r;
 
         assert(m);
-        assert(link);
+        assert(rreq);
 
-        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
-                return 1;
+        Link *link = ASSERT_PTR(rreq->link);
+        Neighbor *neighbor = ASSERT_PTR(rreq->userdata);
+
+        if (link->state == LINK_STATE_LINGER)
+                return 0;
 
         r = sd_netlink_message_get_errno(m);
-        if (r < 0 && r != -ESRCH)
+        if (r < 0) {
                 /* Neighbor may not exist because it already got deleted, ignore that. */
-                log_link_message_warning_errno(link, m, r, "Could not remove neighbor");
+                log_link_message_full_errno(link, m,
+                                            (r == -ESRCH || !neighbor->link) ? LOG_DEBUG : LOG_WARNING,
+                                            r, "Could not remove neighbor");
+
+                if (neighbor->link) {
+                        /* If the neighbor cannot be removed, then assume the neighbor is already removed. */
+                        log_neighbor_debug(neighbor, "Forgetting", link);
+
+                        Request *req;
+                        if (neighbor_get_request(link, neighbor, &req) >= 0)
+                                neighbor_enter_removed(req->userdata);
+
+                        neighbor_detach(neighbor);
+                }
+        }
 
         return 1;
 }
@@ -436,12 +453,9 @@ int neighbor_remove(Neighbor *neighbor, Link *link) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m");
 
-        r = netlink_call_async(link->manager->rtnl, NULL, m, neighbor_remove_handler,
-                               link_netlink_destroy_callback, link);
+        r = link_remove_request_add(link, neighbor, neighbor, link->manager->rtnl, m, neighbor_remove_handler);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
-
-        link_ref(link);
+                return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m");
 
         neighbor_enter_removing(neighbor);
         return 0;
index fd1f26c79803c015d4a9abdb3b5062d9b9ba725e..ce3745093808c0d24ca69faaa1e42aacf9097da4 100644 (file)
@@ -131,6 +131,7 @@ Network.IPv6AcceptRA,                        config_parse_tristate,
 Network.IPv6AcceptRouterAdvertisements,      config_parse_tristate,                                    0,                             offsetof(Network, ipv6_accept_ra)
 Network.IPv6DuplicateAddressDetection,       config_parse_int,                                         0,                             offsetof(Network, ipv6_dad_transmits)
 Network.IPv6HopLimit,                        config_parse_uint8,                                       0,                             offsetof(Network, ipv6_hop_limit)
+Network.IPv6RetransmissionTimeSec,           config_parse_sec,                                         0,                             offsetof(Network, ipv6_retransmission_time)
 Network.IPv6ProxyNDP,                        config_parse_tristate,                                    0,                             offsetof(Network, ipv6_proxy_ndp)
 Network.IPv6MTUBytes,                        config_parse_mtu,                                         AF_INET6,                      offsetof(Network, ipv6_mtu)
 Network.IPv4AcceptLocal,                     config_parse_tristate,                                    0,                             offsetof(Network, ipv4_accept_local)
@@ -180,6 +181,7 @@ RoutingPolicyRule.IPProtocol,                config_parse_routing_policy_rule_ip
 RoutingPolicyRule.SourcePort,                config_parse_routing_policy_rule_port_range,              0,                             0
 RoutingPolicyRule.DestinationPort,           config_parse_routing_policy_rule_port_range,              0,                             0
 RoutingPolicyRule.InvertRule,                config_parse_routing_policy_rule_invert,                  0,                             0
+RoutingPolicyRule.L3MasterDevice,            config_parse_routing_policy_rule_l3mdev,                  0,                             0
 RoutingPolicyRule.Family,                    config_parse_routing_policy_rule_family,                  0,                             0
 RoutingPolicyRule.User,                      config_parse_routing_policy_rule_uid_range,               0,                             0
 RoutingPolicyRule.SuppressInterfaceGroup,    config_parse_routing_policy_rule_suppress_ifgroup,        0,                             0
@@ -296,6 +298,7 @@ IPv6AcceptRA.UseDNS,                         config_parse_bool,
 IPv6AcceptRA.UseDomains,                     config_parse_ipv6_accept_ra_use_domains,                  0,                             offsetof(Network, ipv6_accept_ra_use_domains)
 IPv6AcceptRA.UseMTU,                         config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_mtu)
 IPv6AcceptRA.UseHopLimit,                    config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_hop_limit)
+IPv6AcceptRA.UseRetransmissionTime,          config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_retransmission_time)
 IPv6AcceptRA.UseICMP6RateLimit,              config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_icmp6_ratelimit)
 IPv6AcceptRA.DHCPv6Client,                   config_parse_ipv6_accept_ra_start_dhcp6_client,           0,                             offsetof(Network, ipv6_accept_ra_start_dhcp6_client)
 IPv6AcceptRA.RouteTable,                     config_parse_dhcp_or_ra_route_table,                      AF_INET6,                      0
index 2d5c847a6a66c5620341d16b5838a4fa2bb25861..16c679b34388e640106c9b2c05f1884f709c6826 100644 (file)
@@ -372,7 +372,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
                 .n_ref = 1,
 
                 .required_for_online = -1,
-                .required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT,
+                .required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID,
                 .activation_policy = _ACTIVATION_POLICY_INVALID,
                 .group = -1,
                 .arp = -1,
@@ -483,6 +483,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
                 .ipv6_accept_ra_use_onlink_prefix = true,
                 .ipv6_accept_ra_use_mtu = true,
                 .ipv6_accept_ra_use_hop_limit = true,
+                .ipv6_accept_ra_use_retransmission_time = true,
                 .ipv6_accept_ra_use_icmp6_ratelimit = true,
                 .ipv6_accept_ra_route_table = RT_TABLE_MAIN,
                 .ipv6_accept_ra_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH,
@@ -1213,8 +1214,6 @@ int config_parse_required_for_online(
                 void *userdata) {
 
         Network *network = ASSERT_PTR(userdata);
-        LinkOperationalStateRange range;
-        bool required = true;
         int r;
 
         assert(filename);
@@ -1223,11 +1222,11 @@ int config_parse_required_for_online(
 
         if (isempty(rvalue)) {
                 network->required_for_online = -1;
-                network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT;
+                network->required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID;
                 return 0;
         }
 
-        r = parse_operational_state_range(rvalue, &range);
+        r = parse_operational_state_range(rvalue, &network->required_operstate_for_online);
         if (r < 0) {
                 r = parse_boolean(rvalue);
                 if (r < 0) {
@@ -1237,13 +1236,12 @@ int config_parse_required_for_online(
                         return 0;
                 }
 
-                required = r;
-                range = LINK_OPERSTATE_RANGE_DEFAULT;
+                network->required_for_online = r;
+                network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT;
+                return 0;
         }
 
-        network->required_for_online = required;
-        network->required_operstate_for_online = range;
-
+        network->required_for_online = true;
         return 0;
 }
 
index 1d7a7da798214977de3a4d6391b504334732ba62..3ab115c3b9b52abdeb77c1562afc18f497186222 100644 (file)
@@ -324,6 +324,7 @@ struct Network {
         int ipv4_route_localnet;
         int ipv6_dad_transmits;
         uint8_t ipv6_hop_limit;
+        usec_t ipv6_retransmission_time;
         int proxy_arp;
         int proxy_arp_pvlan;
         uint32_t ipv6_mtu;
@@ -341,6 +342,7 @@ struct Network {
         bool ipv6_accept_ra_use_onlink_prefix;
         bool ipv6_accept_ra_use_mtu;
         bool ipv6_accept_ra_use_hop_limit;
+        bool ipv6_accept_ra_use_retransmission_time;
         bool ipv6_accept_ra_use_icmp6_ratelimit;
         bool ipv6_accept_ra_quickack;
         bool ipv6_accept_ra_use_captive_portal;
index bc2eb7be40959e4e495a1acc7c083991fca57859..cf62e0e82dc666450a42faf5bf2d87ec4c1bc980 100644 (file)
 #include "stdio-util.h"
 #include "string-util.h"
 
+static void nexthop_detach_from_group_members(NextHop *nexthop) {
+        assert(nexthop);
+        assert(nexthop->manager);
+        assert(nexthop->id > 0);
+
+        struct nexthop_grp *nhg;
+        HASHMAP_FOREACH(nhg, nexthop->group) {
+                NextHop *nh;
+
+                if (nexthop_get_by_id(nexthop->manager, nhg->id, &nh) < 0)
+                        continue;
+
+                set_remove(nh->nexthops, UINT32_TO_PTR(nexthop->id));
+        }
+}
+
+static void nexthop_attach_to_group_members(NextHop *nexthop) {
+        int r;
+
+        assert(nexthop);
+        assert(nexthop->manager);
+        assert(nexthop->id > 0);
+
+        struct nexthop_grp *nhg;
+        HASHMAP_FOREACH(nhg, nexthop->group) {
+                NextHop *nh;
+
+                r = nexthop_get_by_id(nexthop->manager, nhg->id, &nh);
+                if (r < 0) {
+                        if (nexthop->manager->manage_foreign_nexthops)
+                                log_debug_errno(r, "Nexthop (id=%"PRIu32") has unknown group member (%"PRIu32"), ignoring.",
+                                                nexthop->id, nhg->id);
+                        continue;
+                }
+
+                r = set_ensure_put(&nh->nexthops, NULL, UINT32_TO_PTR(nexthop->id));
+                if (r < 0)
+                        log_debug_errno(r, "Failed to save nexthop ID (%"PRIu32") to group member (%"PRIu32"), ignoring: %m",
+                                        nexthop->id, nhg->id);
+        }
+}
+
 static NextHop* nexthop_detach_impl(NextHop *nexthop) {
         assert(nexthop);
         assert(!nexthop->manager || !nexthop->network);
@@ -31,6 +73,9 @@ static NextHop* nexthop_detach_impl(NextHop *nexthop) {
 
         if (nexthop->manager) {
                 assert(nexthop->id > 0);
+
+                nexthop_detach_from_group_members(nexthop);
+
                 hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
                 nexthop->manager = NULL;
                 return nexthop;
@@ -51,6 +96,7 @@ static NextHop* nexthop_free(NextHop *nexthop) {
 
         config_section_free(nexthop->section);
         hashmap_free_free(nexthop->group);
+        set_free(nexthop->nexthops);
 
         return mfree(nexthop);
 }
@@ -436,19 +482,57 @@ static void log_nexthop_debug(const NextHop *nexthop, const char *str, Manager *
                        yes_no(nexthop->blackhole), strna(group), strna(flags));
 }
 
-static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int nexthop_remove_dependents(NextHop *nexthop, Manager *manager) {
+        int r = 0;
+
+        assert(nexthop);
+        assert(manager);
+
+        /* If a nexthop is removed, the kernel silently removes nexthops that depend on the
+         * removed nexthop. Let's remove them for safety (though, they are already removed in the kernel,
+         * hence that should fail), and forget them. */
+
+        void *id;
+        SET_FOREACH(id, nexthop->nexthops) {
+                NextHop *nh;
+
+                if (nexthop_get_by_id(manager, PTR_TO_UINT32(id), &nh) < 0)
+                        continue;
+
+                RET_GATHER(r, nexthop_remove(nh, manager));
+        }
+
+        return r;
+}
+
+static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) {
         int r;
 
         assert(m);
+        assert(rreq);
 
-        /* link may be NULL. */
-
-        if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
-                return 1;
+        Manager *manager = ASSERT_PTR(rreq->manager);
+        NextHop *nexthop = ASSERT_PTR(rreq->userdata);
 
         r = sd_netlink_message_get_errno(m);
-        if (r < 0 && r != -ENOENT)
-                log_link_message_warning_errno(link, m, r, "Could not drop nexthop, ignoring");
+        if (r < 0) {
+                log_message_full_errno(m,
+                                       (r == -ENOENT || !nexthop->manager) ? LOG_DEBUG : LOG_WARNING,
+                                       r, "Could not drop nexthop, ignoring");
+
+                (void) nexthop_remove_dependents(nexthop, manager);
+
+                if (nexthop->manager) {
+                        /* If the nexthop cannot be removed, then assume the nexthop is already removed. */
+                        log_nexthop_debug(nexthop, "Forgetting", manager);
+
+                        Request *req;
+                        if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0)
+                                nexthop_enter_removed(req->userdata);
+
+                        nexthop_detach(nexthop);
+                }
+        }
 
         return 1;
 }
@@ -475,12 +559,9 @@ int nexthop_remove(NextHop *nexthop, Manager *manager) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m");
 
-        r = netlink_call_async(manager->rtnl, NULL, m, nexthop_remove_handler,
-                               link ? link_netlink_destroy_callback : NULL, link);
+        r = manager_remove_request_add(manager, nexthop, nexthop, manager->rtnl, m, nexthop_remove_handler);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
-
-        link_ref(link); /* link may be NULL, link_ref() is OK with that */
+                return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m");
 
         nexthop_enter_removing(nexthop);
         return 0;
@@ -851,15 +932,22 @@ void link_foreignize_nexthops(Link *link) {
         }
 }
 
-static int nexthop_update_group(NextHop *nexthop, const struct nexthop_grp *group, size_t size) {
+static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) {
         _cleanup_hashmap_free_free_ Hashmap *h = NULL;
-        size_t n_group;
+        _cleanup_free_ struct nexthop_grp *group = NULL;
+        size_t size = 0, n_group;
         int r;
 
         assert(nexthop);
-        assert(group || size == 0);
+        assert(message);
+
+        r = sd_netlink_message_read_data(message, NHA_GROUP, &size, (void**) &group);
+        if (r < 0 && r != -ENODATA)
+                return log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
+
+        nexthop_detach_from_group_members(nexthop);
 
-        if (size == 0 || size % sizeof(struct nexthop_grp) != 0)
+        if (size % sizeof(struct nexthop_grp) != 0)
                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "rtnl: received nexthop message with invalid nexthop group size, ignoring.");
 
@@ -898,12 +986,12 @@ static int nexthop_update_group(NextHop *nexthop, const struct nexthop_grp *grou
 
         hashmap_free_free(nexthop->group);
         nexthop->group = TAKE_PTR(h);
+
+        nexthop_attach_to_group_members(nexthop);
         return 0;
 }
 
 int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
-        _cleanup_free_ void *raw_group = NULL;
-        size_t raw_group_size;
         uint16_t type;
         uint32_t id, ifindex;
         NextHop *nexthop = NULL;
@@ -951,6 +1039,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
                 if (nexthop) {
                         nexthop_enter_removed(nexthop);
                         log_nexthop_debug(nexthop, "Forgetting removed", m);
+                        (void) nexthop_remove_dependents(nexthop, m);
                         nexthop_detach(nexthop);
                 } else
                         log_nexthop_debug(&(const NextHop) { .id = id }, "Kernel removed unknown", m);
@@ -991,13 +1080,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
         if (r < 0)
                 log_debug_errno(r, "rtnl: could not get nexthop flags, ignoring: %m");
 
-        r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group);
-        if (r == -ENODATA)
-                nexthop->group = hashmap_free_free(nexthop->group);
-        else if (r < 0)
-                log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
-        else
-                (void) nexthop_update_group(nexthop, raw_group, raw_group_size);
+        (void) nexthop_update_group(nexthop, message);
 
         if (nexthop->family != AF_UNSPEC) {
                 r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, nexthop->family, &nexthop->gw);
index bc2bacfa40da3db931d69784a3c59dd17112e907..74b23bd77203f0349e0f9ea98248256c1ba476ee 100644 (file)
@@ -26,15 +26,23 @@ typedef struct NextHop {
 
         unsigned n_ref;
 
-        uint8_t protocol;
-        int ifindex;
-        uint32_t id;
-        bool blackhole;
+        /* struct nhmsg */
         int family;
-        union in_addr_union gw;
+        uint8_t protocol;
         uint8_t flags;
-        int onlink; /* Only used in conf parser and nexthop_section_verify(). */
-        Hashmap *group;
+
+        /* attributes */
+        uint32_t id; /* NHA_ID */
+        Hashmap *group; /* NHA_GROUP */
+        bool blackhole; /* NHA_BLACKHOLE */
+        int ifindex; /* NHA_OIF */
+        union in_addr_union gw; /* NHA_GATEWAY */
+
+        /* Only used in conf parser and nexthop_section_verify(). */
+        int onlink;
+
+        /* For managing nexthops that depend on this nexthop. */
+        Set *nexthops;
 } NextHop;
 
 NextHop* nexthop_ref(NextHop *nexthop);
index 1678510d522950007028c6086c7d728677c65848..f1dd6b44d251cdc71897ab014900e04593b28138 100644 (file)
@@ -9,14 +9,28 @@
 
 #define REPLY_CALLBACK_COUNT_THRESHOLD 128
 
+static Request* request_detach_impl(Request *req) {
+        assert(req);
+
+        if (!req->manager)
+                return NULL;
+
+        ordered_set_remove(req->manager->request_queue, req);
+        req->manager = NULL;
+        return req;
+}
+
+void request_detach(Request *req) {
+        request_unref(request_detach_impl(req));
+}
+
 static Request *request_free(Request *req) {
         if (!req)
                 return NULL;
 
         /* To prevent from triggering assertions in the hash and compare functions, remove this request
          * from the set before freeing userdata below. */
-        if (req->manager)
-                ordered_set_remove(req->manager->request_queue, req);
+        request_detach_impl(req);
 
         if (req->free_func)
                 req->free_func(req->userdata);
@@ -31,26 +45,10 @@ static Request *request_free(Request *req) {
 
 DEFINE_TRIVIAL_REF_UNREF_FUNC(Request, request, request_free);
 
-void request_detach(Manager *manager, Request *req) {
-        assert(manager);
-
-        if (!req)
-                return;
-
-        req = ordered_set_remove(manager->request_queue, req);
-        if (!req)
-                return;
-
-        req->manager = NULL;
-        request_unref(req);
-}
-
 static void request_destroy_callback(Request *req) {
         assert(req);
 
-        if (req->manager)
-                request_detach(req->manager, req);
-
+        request_detach(req);
         request_unref(req);
 }
 
@@ -60,7 +58,7 @@ static void request_hash_func(const Request *req, struct siphash *state) {
 
         siphash24_compress_typesafe(req->type, state);
 
-        if (req->type != REQUEST_TYPE_NEXTHOP) {
+        if (!IN_SET(req->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) {
                 siphash24_compress_boolean(req->link, state);
                 if (req->link)
                         siphash24_compress_typesafe(req->link->ifindex, state);
@@ -83,7 +81,7 @@ static int request_compare_func(const struct Request *a, const struct Request *b
         if (r != 0)
                 return r;
 
-        if (a->type != REQUEST_TYPE_NEXTHOP) {
+        if (!IN_SET(a->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) {
                 r = CMP(!!a->link, !!b->link);
                 if (r != 0)
                         return r;
@@ -114,7 +112,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
                 Request,
                 request_hash_func,
                 request_compare_func,
-                request_unref);
+                request_detach);
 
 static int request_new(
                 Manager *manager,
@@ -224,45 +222,47 @@ int manager_process_requests(Manager *manager) {
 
         assert(manager);
 
+        /* Process only when no remove request is queued. */
+        if (!ordered_set_isempty(manager->remove_request_queue))
+                return 0;
+
         manager->request_queued = false;
 
         ORDERED_SET_FOREACH(req, manager->request_queue) {
-                _cleanup_(link_unrefp) Link *link = link_ref(req->link);
-
-                assert(req->process);
-
                 if (req->waiting_reply)
-                        continue; /* Waiting for netlink reply. */
+                        continue; /* Already processed, and waiting for netlink reply. */
 
                 /* 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;
-
-                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;
-                }
+                        break;
 
-                /* 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);
+                /* Avoid the request and link freed by req->process() and request_detach(). */
+                _unused_ _cleanup_(request_unrefp) Request *req_unref = request_ref(req);
+                _cleanup_(link_unrefp) Link *link = link_ref(req->link);
 
-                if (r < 0 && link) {
-                        link_enter_failed(link);
-                        /* link_enter_failed() may remove multiple requests,
-                         * hence we need to exit from the loop. */
-                        break;
+                assert(req->process);
+                r = req->process(req, link, req->userdata);
+                if (r < 0) {
+                        request_detach(req);
+
+                        if (link) {
+                                link_enter_failed(link);
+                                /* link_enter_failed() may detach multiple requests from the queue.
+                                 * Hence, we need to exit from the loop. */
+                                break;
+                        }
                 }
+                if (r > 0 && !req->waiting_reply)
+                        /* 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. */
+                        request_detach(req);
 
                 if (manager->request_queued)
-                        break;
+                        break; /* New request is queued. Exit from the loop. */
         }
 
         return 0;
@@ -339,3 +339,110 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = {
 };
 
 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(request_type, RequestType);
+
+static RemoveRequest* remove_request_free(RemoveRequest *req) {
+        if (!req)
+                return NULL;
+
+        if (req->manager)
+                ordered_set_remove(req->manager->remove_request_queue, req);
+
+        if (req->unref_func)
+                req->unref_func(req->userdata);
+
+        link_unref(req->link);
+        sd_netlink_unref(req->netlink);
+        sd_netlink_message_unref(req->message);
+
+        return mfree(req);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(RemoveRequest*, remove_request_free);
+DEFINE_TRIVIAL_DESTRUCTOR(remove_request_destroy_callback, RemoveRequest, remove_request_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+                remove_request_hash_ops,
+                void,
+                trivial_hash_func,
+                trivial_compare_func,
+                remove_request_free);
+
+int remove_request_add(
+                Manager *manager,
+                Link *link,
+                void *userdata,
+                mfree_func_t unref_func,
+                sd_netlink *netlink,
+                sd_netlink_message *message,
+                remove_request_netlink_handler_t netlink_handler) {
+
+        _cleanup_(remove_request_freep) RemoveRequest *req = NULL;
+        int r;
+
+        assert(manager);
+        assert(userdata);
+        assert(netlink);
+        assert(message);
+
+        req = new(RemoveRequest, 1);
+        if (!req)
+                return -ENOMEM;
+
+        *req = (RemoveRequest) {
+                .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */
+                .userdata = userdata,
+                .netlink = sd_netlink_ref(netlink),
+                .message = sd_netlink_message_ref(message),
+                .netlink_handler = netlink_handler,
+        };
+
+        r = ordered_set_ensure_put(&manager->remove_request_queue, &remove_request_hash_ops, req);
+        if (r < 0)
+                return r;
+        assert(r > 0);
+
+        req->manager = manager;
+        req->unref_func = unref_func;
+
+        TAKE_PTR(req);
+        return 0;
+}
+
+int manager_process_remove_requests(Manager *manager) {
+        RemoveRequest *req;
+        int r;
+
+        assert(manager);
+
+        while ((req = ordered_set_first(manager->remove_request_queue))) {
+
+                /* Do not make the reply callback queue in sd-netlink full. */
+                if (netlink_get_reply_callback_count(req->netlink) >= REPLY_CALLBACK_COUNT_THRESHOLD)
+                        return 0;
+
+                r = netlink_call_async(
+                                req->netlink, NULL, req->message,
+                                req->netlink_handler,
+                                remove_request_destroy_callback,
+                                req);
+                if (r < 0) {
+                        _cleanup_(link_unrefp) Link *link = link_ref(req->link);
+
+                        log_link_warning_errno(link, r, "Failed to call netlink message: %m");
+
+                        /* First free the request. */
+                        remove_request_free(req);
+
+                        /* Then, make the link enter the failed state. */
+                        if (link)
+                                link_enter_failed(link);
+
+                } else {
+                        /* On success, netlink needs to be unref()ed. Otherwise, the netlink and remove
+                         * request may not freed on shutting down. */
+                        req->netlink = sd_netlink_unref(req->netlink);
+                        ordered_set_remove(manager->remove_request_queue, req);
+                }
+        }
+
+        return 0;
+}
index 21fa7d9453475ae1182b4f31b20254e9c4ab0a0c..bdedc775377b9c56ed1df117cc01032757fe4642 100644 (file)
@@ -88,7 +88,7 @@ Request *request_ref(Request *req);
 Request *request_unref(Request *req);
 DEFINE_TRIVIAL_CLEANUP_FUNC(Request*, request_unref);
 
-void request_detach(Manager *manager, Request *req);
+void request_detach(Request *req);
 
 int netdev_queue_request(
                 NetDev *netdev,
@@ -139,3 +139,52 @@ int manager_process_requests(Manager *manager);
 int request_call_netlink_async(sd_netlink *nl, sd_netlink_message *m, Request *req);
 
 const char* request_type_to_string(RequestType t) _const_;
+
+typedef struct RemoveRequest RemoveRequest;
+typedef int (*remove_request_netlink_handler_t)(sd_netlink *nl, sd_netlink_message *m, RemoveRequest *req);
+
+struct RemoveRequest {
+        Manager *manager;
+        Link *link;
+        void *userdata; /* e.g. Address */
+        mfree_func_t unref_func; /* e.g. address_unref() */
+        sd_netlink *netlink;
+        sd_netlink_message *message;
+        remove_request_netlink_handler_t netlink_handler;
+};
+
+int remove_request_add(
+                Manager *manager,
+                Link *link,
+                void *userdata, /* This is unref()ed when the call failed. */
+                mfree_func_t unref_func,
+                sd_netlink *netlink,
+                sd_netlink_message *message,
+                remove_request_netlink_handler_t netlink_handler);
+
+#define _remove_request_add(manager, link, data, name, nl, m, handler)  \
+        ({                                                              \
+                typeof(*data) *_data = (data);                          \
+                int _r;                                                 \
+                                                                        \
+                _r = remove_request_add(manager, link, _data,           \
+                                        (mfree_func_t) name##_unref,    \
+                                        nl, m, handler);                \
+                if (_r >= 0)                                            \
+                        name##_ref(_data);                              \
+                _r;                                                     \
+        })
+
+
+#define link_remove_request_add(link, data, name, nl, m, handler)       \
+        ({                                                              \
+                Link *_link = (link);                                   \
+                                                                        \
+                _remove_request_add(_link->manager, _link, data, name,  \
+                                    nl, m, handler);                    \
+        })
+
+#define manager_remove_request_add(manager, data, name, nl, m, handler) \
+        _remove_request_add(manager, NULL, data, name, nl, m, handler)
+
+int manager_process_remove_requests(Manager *manager);
index 67841cb67ea41e64b20b9c1da6123fa1092ce5a3..b27b3c12948e81220e17e0ff980b453b6a243077 100644 (file)
@@ -39,7 +39,7 @@ int route_metric_copy(const RouteMetric *src, RouteMetric *dest) {
         } else
                 dest->metrics_set = NULL;
 
-        return free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo);
+        return strdup_or_null(src->tcp_congestion_control_algo, &dest->tcp_congestion_control_algo);
 }
 
 void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) {
index fbb8ee2d68dfaf5d9562d5b1b78fd17423365e7d..5f1da286046456c1215c70a5af671a897c6fed13 100644 (file)
 #include "alloc-util.h"
 #include "extract-word.h"
 #include "netlink-util.h"
+#include "networkd-network.h"
+#include "networkd-nexthop.h"
 #include "networkd-route.h"
 #include "networkd-route-nexthop.h"
+#include "networkd-route-util.h"
 #include "parse-util.h"
 #include "string-util.h"
 
+static void route_nexthop_done(RouteNextHop *nh) {
+        assert(nh);
+
+        free(nh->ifname);
+}
+
+RouteNextHop* route_nexthop_free(RouteNextHop *nh) {
+        if (!nh)
+                return NULL;
+
+        route_nexthop_done(nh);
+
+        return mfree(nh);
+}
+
+void route_nexthops_done(Route *route) {
+        assert(route);
+
+        route_nexthop_done(&route->nexthop);
+        ordered_set_free(route->nexthops);
+}
+
+static void route_nexthop_hash_func_full(const RouteNextHop *nh, struct siphash *state, bool with_weight) {
+        assert(nh);
+        assert(state);
+
+        /* See nh_comp() in net/ipv4/fib_semantics.c of the kernel. */
+
+        siphash24_compress_typesafe(nh->family, state);
+        if (!IN_SET(nh->family, AF_INET, AF_INET6))
+                return;
+
+        in_addr_hash_func(&nh->gw, nh->family, state);
+        if (with_weight)
+                siphash24_compress_typesafe(nh->weight, state);
+        siphash24_compress_typesafe(nh->ifindex, state);
+        if (nh->ifindex == 0)
+                siphash24_compress_string(nh->ifname, state); /* For Network or Request object. */
+}
+
+static int route_nexthop_compare_func_full(const RouteNextHop *a, const RouteNextHop *b, bool with_weight) {
+        int r;
+
+        assert(a);
+        assert(b);
+
+        r = CMP(a->family, b->family);
+        if (r != 0)
+                return r;
+
+        if (!IN_SET(a->family, AF_INET, AF_INET6))
+                return 0;
+
+        r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
+        if (r != 0)
+                return r;
+
+        if (with_weight) {
+                r = CMP(a->weight, b->weight);
+                if (r != 0)
+                        return r;
+        }
+
+        r = CMP(a->ifindex, b->ifindex);
+        if (r != 0)
+                return r;
+
+        if (a->ifindex == 0) {
+                r = strcmp_ptr(a->ifname, b->ifname);
+                if (r != 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static void route_nexthop_hash_func(const RouteNextHop *nh, struct siphash *state) {
+        route_nexthop_hash_func_full(nh, state, /* with_weight = */ true);
+}
+
+static int route_nexthop_compare_func(const RouteNextHop *a, const RouteNextHop *b) {
+        return route_nexthop_compare_func_full(a, b, /* with_weight = */ true);
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+        route_nexthop_hash_ops,
+        RouteNextHop,
+        route_nexthop_hash_func,
+        route_nexthop_compare_func,
+        route_nexthop_free);
+
+static size_t route_n_nexthops(const Route *route) {
+        if (route->nexthop_id != 0 || route_type_is_reject(route))
+                return 0;
+
+        if (ordered_set_isempty(route->nexthops))
+                return 1;
+
+        return ordered_set_size(route->nexthops);
+}
+
+void route_nexthops_hash_func(const Route *route, struct siphash *state) {
+        assert(route);
+
+        size_t nhs = route_n_nexthops(route);
+        siphash24_compress_typesafe(nhs, state);
+
+        switch (nhs) {
+        case 0:
+                siphash24_compress_typesafe(route->nexthop_id, state);
+                return;
+
+        case 1:
+                route_nexthop_hash_func_full(&route->nexthop, state, /* with_weight = */ false);
+                return;
+
+        default: {
+                RouteNextHop *nh;
+                ORDERED_SET_FOREACH(nh, route->nexthops)
+                        route_nexthop_hash_func(nh, state);
+        }}
+}
+
+int route_nexthops_compare_func(const Route *a, const Route *b) {
+        int r;
+
+        assert(a);
+        assert(b);
+
+        size_t a_nhs = route_n_nexthops(a);
+        size_t b_nhs = route_n_nexthops(b);
+        r = CMP(a_nhs, b_nhs);
+        if (r != 0)
+                return r;
+
+        switch (a_nhs) {
+        case 0:
+                return CMP(a->nexthop_id, b->nexthop_id);
+
+        case 1:
+                return route_nexthop_compare_func_full(&a->nexthop, &b->nexthop, /* with_weight = */ false);
+
+        default: {
+                RouteNextHop *nh;
+                ORDERED_SET_FOREACH(nh, a->nexthops) {
+                        r = CMP(nh, (RouteNextHop*) ordered_set_get(a->nexthops, nh));
+                        if (r != 0)
+                                return r;
+                }
+                return 0;
+        }}
+}
+
+static int route_nexthop_copy(const RouteNextHop *src, RouteNextHop *dest) {
+        assert(src);
+        assert(dest);
+
+        *dest = *src;
+
+        /* unset pointer copied in the above. */
+        dest->ifname = NULL;
+
+        return strdup_or_null(src->ifindex > 0 ? NULL : src->ifname, &dest->ifname);
+}
+
+static int route_nexthop_dup(const RouteNextHop *src, RouteNextHop **ret) {
+        _cleanup_(route_nexthop_freep) RouteNextHop *dest = NULL;
+        int r;
+
+        assert(src);
+        assert(ret);
+
+        dest = new(RouteNextHop, 1);
+        if (!dest)
+                return -ENOMEM;
+
+        r = route_nexthop_copy(src, dest);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(dest);
+        return 0;
+}
+
+int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) {
+        int r;
+
+        assert(src);
+        assert(dest);
+
+        if (src->nexthop_id != 0 || route_type_is_reject(src))
+                return 0;
+
+        if (nh)
+                return route_nexthop_copy(nh, &dest->nexthop);
+
+        if (ordered_set_isempty(src->nexthops))
+                return route_nexthop_copy(&src->nexthop, &dest->nexthop);
+
+        ORDERED_SET_FOREACH(nh, src->nexthops) {
+                _cleanup_(route_nexthop_freep) RouteNextHop *nh_dup = NULL;
+
+                r = route_nexthop_dup(nh, &nh_dup);
+                if (r < 0)
+                        return r;
+
+                r = ordered_set_ensure_put(&dest->nexthops, &route_nexthop_hash_ops, nh_dup);
+                if (r < 0)
+                        return r;
+                assert(r > 0);
+
+                TAKE_PTR(nh_dup);
+        }
+
+        return 0;
+}
+
+static bool multipath_routes_needs_adjust(const Route *route) {
+        assert(route);
+
+        RouteNextHop *nh;
+        ORDERED_SET_FOREACH(nh, route->nexthops)
+                if (route->nexthop.ifindex == 0)
+                        return true;
+
+        return false;
+}
+
+bool route_nexthops_needs_adjust(const Route *route) {
+        assert(route);
+
+        if (route->nexthop_id != 0)
+                /* At this stage, the nexthop may not be configured, or may be under reconfiguring.
+                 * Hence, we cannot know if the nexthop is blackhole or not. */
+                return route->type != RTN_BLACKHOLE;
+
+        if (route_type_is_reject(route))
+                return false;
+
+        if (ordered_set_isempty(route->nexthops))
+                return route->nexthop.ifindex == 0;
+
+        return multipath_routes_needs_adjust(route);
+}
+
+static bool route_nexthop_set_ifindex(RouteNextHop *nh, Link *link) {
+        assert(nh);
+        assert(link);
+        assert(link->manager);
+
+        if (nh->ifindex > 0) {
+                nh->ifname = mfree(nh->ifname);
+                return false;
+        }
+
+        /* If an interface name is specified, use it. Otherwise, use the interface that requests this route. */
+        if (nh->ifname && link_get_by_name(link->manager, nh->ifname, &link) < 0)
+                return false;
+
+        nh->ifindex = link->ifindex;
+        nh->ifname = mfree(nh->ifname);
+        return true; /* updated */
+}
+
+int route_adjust_nexthops(Route *route, Link *link) {
+        int r;
+
+        assert(route);
+        assert(link);
+        assert(link->manager);
+
+        /* When an IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel sends
+         * RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type fib_rt_info::type
+         * may not be blackhole. Thus, we cannot know the internal value. Moreover, on route removal, the
+         * matching is done with the hidden value if we set non-zero type in RTM_DELROUTE message. So,
+         * here let's set route type to BLACKHOLE when the nexthop is blackhole. */
+        if (route->nexthop_id != 0) {
+                NextHop *nexthop;
+
+                r = nexthop_is_ready(link->manager, route->nexthop_id, &nexthop);
+                if (r <= 0)
+                        return r; /* r == 0 means the nexthop is under (re-)configuring.
+                                   * We cannot use the currently remembered information. */
+
+                if (!nexthop->blackhole)
+                        return false;
+
+                if (route->type == RTN_BLACKHOLE)
+                        return false;
+
+                route->type = RTN_BLACKHOLE;
+                return true; /* updated */
+        }
+
+        if (route_type_is_reject(route))
+                return false;
+
+        if (ordered_set_isempty(route->nexthops))
+                return route_nexthop_set_ifindex(&route->nexthop, link);
+
+        if (!multipath_routes_needs_adjust(route))
+                return false;
+
+        _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL;
+        for (;;) {
+                _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
+
+                nh = ordered_set_steal_first(route->nexthops);
+                if (!nh)
+                        break;
+
+                (void) route_nexthop_set_ifindex(nh, link);
+
+                r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh);
+                if (r == -EEXIST)
+                        continue; /* Duplicated? Let's drop the nexthop. */
+                if (r < 0)
+                        return r;
+                assert(r > 0);
+
+                TAKE_PTR(nh);
+        }
+
+        ordered_set_free(route->nexthops);
+        route->nexthops = TAKE_PTR(nexthops);
+        return true; /* updated */
+}
+
+int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret) {
+        assert(manager);
+        assert(nh);
+
+        if (nh->ifindex > 0)
+                return link_get_by_index(manager, nh->ifindex, ret);
+        if (nh->ifname)
+                return link_get_by_name(manager, nh->ifname, ret);
+
+        return -ENOENT;
+}
+
+static bool route_nexthop_is_ready_to_configure(const RouteNextHop *nh, Manager *manager, bool onlink) {
+        Link *link;
+
+        assert(nh);
+        assert(manager);
+
+        if (route_nexthop_get_link(manager, nh, &link) < 0)
+                return false;
+
+        if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ true))
+                return false;
+
+        /* If the interface is not managed by us, we request that the interface has carrier.
+         * That is, ConfigureWithoutCarrier=no is the default even for unamanaged interfaces. */
+        if (!link->network && !link_has_carrier(link))
+                return false;
+
+        return gateway_is_ready(link, onlink, nh->family, &nh->gw);
+}
+
+int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager) {
+        int r;
+
+        assert(route);
+        assert(manager);
+
+        if (route->nexthop_id != 0) {
+                struct nexthop_grp *nhg;
+                NextHop *nh;
+
+                r = nexthop_is_ready(manager, route->nexthop_id, &nh);
+                if (r <= 0)
+                        return r;
+
+                HASHMAP_FOREACH(nhg, nh->group) {
+                        r = nexthop_is_ready(manager, nhg->id, NULL);
+                        if (r <= 0)
+                                return r;
+                }
+
+                return true;
+        }
+
+        if (route_type_is_reject(route))
+                return true;
+
+        if (ordered_set_isempty(route->nexthops))
+                return route_nexthop_is_ready_to_configure(&route->nexthop, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK));
+
+        RouteNextHop *nh;
+        ORDERED_SET_FOREACH(nh, route->nexthops)
+                if (!route_nexthop_is_ready_to_configure(nh, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK)))
+                        return false;
+
+        return true;
+}
+
+int route_nexthops_to_string(const Route *route, char **ret) {
+        _cleanup_free_ char *buf = NULL;
+        int r;
+
+        assert(route);
+        assert(ret);
+
+        if (route->nexthop_id != 0) {
+                if (asprintf(&buf, "nexthop: %"PRIu32, route->nexthop_id) < 0)
+                        return -ENOMEM;
+
+                *ret = TAKE_PTR(buf);
+                return 0;
+        }
+
+        if (route_type_is_reject(route)) {
+                buf = strdup("gw: n/a");
+                if (!buf)
+                        return -ENOMEM;
+
+                *ret = TAKE_PTR(buf);
+                return 0;
+        }
+
+        if (ordered_set_isempty(route->nexthops)) {
+                if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw))
+                        buf = strjoin("gw: ", IN_ADDR_TO_STRING(route->nexthop.family, &route->nexthop.gw));
+                else if (route->gateway_from_dhcp_or_ra) {
+                        if (route->nexthop.family == AF_INET)
+                                buf = strdup("gw: _dhcp4");
+                        else if (route->nexthop.family == AF_INET6)
+                                buf = strdup("gw: _ipv6ra");
+                        else
+                                buf = strdup("gw: _dhcp");
+                } else
+                        buf = strdup("gw: n/a");
+                if (!buf)
+                        return -ENOMEM;
+
+                *ret = TAKE_PTR(buf);
+                return 0;
+        }
+
+        RouteNextHop *nh;
+        ORDERED_SET_FOREACH(nh, route->nexthops) {
+                const char *s = in_addr_is_set(nh->family, &nh->gw) ? IN_ADDR_TO_STRING(nh->family, &nh->gw) : NULL;
+
+                if (nh->ifindex > 0)
+                        r = strextendf_with_separator(&buf, ",", "%s@%i:%"PRIu32, strempty(s), nh->ifindex, nh->weight + 1);
+                else if (nh->ifname)
+                        r = strextendf_with_separator(&buf, ",", "%s@%s:%"PRIu32, strempty(s), nh->ifname, nh->weight + 1);
+                else
+                        r = strextendf_with_separator(&buf, ",", "%s:%"PRIu32, strempty(s), nh->weight + 1);
+                if (r < 0)
+                        return r;
+        }
+
+        char *p = strjoin("gw: ", strna(buf));
+        if (!p)
+                return -ENOMEM;
+
+        *ret = p;
+        return 0;
+}
+
+static int append_nexthop_one(const Route *route, const RouteNextHop *nh, struct rtattr **rta, size_t offset) {
+        struct rtnexthop *rtnh;
+        struct rtattr *new_rta;
+        int r;
+
+        assert(route);
+        assert(IN_SET(route->family, AF_INET, AF_INET6));
+        assert(nh);
+        assert(rta);
+        assert(*rta);
+
+        new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop)));
+        if (!new_rta)
+                return -ENOMEM;
+        *rta = new_rta;
+
+        rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
+        *rtnh = (struct rtnexthop) {
+                .rtnh_len = sizeof(*rtnh),
+                .rtnh_ifindex = nh->ifindex,
+                .rtnh_hops = nh->weight,
+        };
+
+        (*rta)->rta_len += sizeof(struct rtnexthop);
+
+        if (nh->family == route->family) {
+                r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family));
+                if (r < 0)
+                        goto clear;
+
+                rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
+                rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family));
+
+        } else if (nh->family == AF_INET6) {
+                assert(route->family == AF_INET);
+
+                r = rtattr_append_attribute(rta, RTA_VIA,
+                                            &(RouteVia) {
+                                                    .family = nh->family,
+                                                    .address = nh->gw,
+                                            }, sizeof(RouteVia));
+                if (r < 0)
+                        goto clear;
+
+                rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
+                rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia));
+
+        } else if (nh->family == AF_INET)
+                assert_not_reached();
+
+        return 0;
+
+clear:
+        (*rta)->rta_len -= sizeof(struct rtnexthop);
+        return r;
+}
+
+static int netlink_message_append_multipath_route(const Route *route, sd_netlink_message *message) {
+        _cleanup_free_ struct rtattr *rta = NULL;
+        size_t offset;
+        int r;
+
+        assert(route);
+        assert(message);
+
+        rta = new(struct rtattr, 1);
+        if (!rta)
+                return -ENOMEM;
+
+        *rta = (struct rtattr) {
+                .rta_type = RTA_MULTIPATH,
+                .rta_len = RTA_LENGTH(0),
+        };
+        offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta;
+
+        if (ordered_set_isempty(route->nexthops)) {
+                r = append_nexthop_one(route, &route->nexthop, &rta, offset);
+                if (r < 0)
+                        return r;
+
+        } else {
+                RouteNextHop *nh;
+                ORDERED_SET_FOREACH(nh, route->nexthops) {
+                        struct rtnexthop *rtnh;
+
+                        r = append_nexthop_one(route, nh, &rta, offset);
+                        if (r < 0)
+                                return r;
+
+                        rtnh = (struct rtnexthop *)((uint8_t *) rta + offset);
+                        offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta;
+                }
+        }
+
+        return sd_netlink_message_append_data(message, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta));
+}
+
+int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message) {
+        int r;
+
+        assert(route);
+        assert(IN_SET(route->family, AF_INET, AF_INET6));
+        assert(message);
+
+        if (route->nexthop_id != 0)
+                return sd_netlink_message_append_u32(message, RTA_NH_ID, route->nexthop_id);
+
+        if (route_type_is_reject(route))
+                return 0;
+
+        /* We request IPv6 multipath routes separately. Even though, if weight is non-zero, we need to use
+         * RTA_MULTIPATH, as we have no way to specify the weight of the nexthop. */
+        if (ordered_set_isempty(route->nexthops) && route->nexthop.weight == 0) {
+
+                if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) {
+                        if (route->nexthop.family == route->family)
+                                r = netlink_message_append_in_addr_union(message, RTA_GATEWAY, route->nexthop.family, &route->nexthop.gw);
+                        else {
+                                assert(route->family == AF_INET);
+                                r = sd_netlink_message_append_data(message, RTA_VIA,
+                                                                   &(const RouteVia) {
+                                                                           .family = route->nexthop.family,
+                                                                           .address = route->nexthop.gw,
+                                                                   }, sizeof(RouteVia));
+                        }
+                        if (r < 0)
+                                return r;
+                }
+
+                assert(route->nexthop.ifindex > 0);
+                return sd_netlink_message_append_u32(message, RTA_OIF, route->nexthop.ifindex);
+        }
+
+        return netlink_message_append_multipath_route(route, message);
+}
+
+static int route_parse_nexthops(Route *route, const struct rtnexthop *rtnh, size_t size) {
+        _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL;
+        int r;
+
+        assert(route);
+        assert(IN_SET(route->family, AF_INET, AF_INET6));
+        assert(rtnh);
+
+        if (size < sizeof(struct rtnexthop))
+                return -EBADMSG;
+
+        for (; size >= sizeof(struct rtnexthop); ) {
+                _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
+
+                if (NLMSG_ALIGN(rtnh->rtnh_len) > size)
+                        return -EBADMSG;
+
+                if (rtnh->rtnh_len < sizeof(struct rtnexthop))
+                        return -EBADMSG;
+
+                nh = new(RouteNextHop, 1);
+                if (!nh)
+                        return -ENOMEM;
+
+                *nh = (RouteNextHop) {
+                        .ifindex = rtnh->rtnh_ifindex,
+                        .weight = rtnh->rtnh_hops,
+                };
+
+                if (rtnh->rtnh_len > sizeof(struct rtnexthop)) {
+                        size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop);
+                        bool have_gw = false;
+
+                        for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+
+                                switch (attr->rta_type) {
+                                case RTA_GATEWAY:
+                                        if (have_gw)
+                                                return -EBADMSG;
+
+                                        if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(route->family)))
+                                                return -EBADMSG;
+
+                                        nh->family = route->family;
+                                        memcpy(&nh->gw, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(nh->family));
+                                        have_gw = true;
+                                        break;
+
+                                case RTA_VIA:
+                                        if (have_gw)
+                                                return -EBADMSG;
+
+                                        if (route->family != AF_INET)
+                                                return -EBADMSG;
+
+                                        if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia)))
+                                                return -EBADMSG;
+
+                                        RouteVia *via = RTA_DATA(attr);
+                                        if (via->family != AF_INET6)
+                                                return -EBADMSG;
+
+                                        nh->family = via->family;
+                                        nh->gw = via->address;
+                                        have_gw = true;
+                                        break;
+                                }
+                        }
+                }
+
+                r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh);
+                assert(r != 0);
+                if (r > 0)
+                        TAKE_PTR(nh);
+                else if (r != -EEXIST)
+                        return r;
+
+                size -= NLMSG_ALIGN(rtnh->rtnh_len);
+                rtnh = RTNH_NEXT(rtnh);
+        }
+
+        ordered_set_free(route->nexthops);
+        route->nexthops = TAKE_PTR(nexthops);
+        return 0;
+}
+
+int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message) {
+        int r;
+
+        assert(route);
+        assert(message);
+
+        r = sd_netlink_message_read_u32(message, RTA_NH_ID, &route->nexthop_id);
+        if (r < 0 && r != -ENODATA)
+                return log_warning_errno(r, "rtnl: received route message with invalid nexthop id, ignoring: %m");
+
+        if (route->nexthop_id != 0 || route_type_is_reject(route))
+                /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's
+                 * fib6_nh_init() in net/ipv6/route.c. However, we'd like to make it consistent with IPv4
+                 * routes. Hence, skip reading of RTA_OIF. */
+                return 0;
+
+        uint32_t ifindex;
+        r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex);
+        if (r >= 0)
+                route->nexthop.ifindex = (int) ifindex;
+        else if (r != -ENODATA)
+                return log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m");
+
+        if (route->nexthop.ifindex > 0) {
+                r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, route->family, &route->nexthop.gw);
+                if (r >= 0) {
+                        route->nexthop.family = route->family;
+                        return 0;
+                }
+                if (r != -ENODATA)
+                        return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m");
+
+                if (route->family != AF_INET)
+                        return 0;
+
+                RouteVia via;
+                r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via);
+                if (r >= 0) {
+                        route->nexthop.family = via.family;
+                        route->nexthop.gw = via.address;
+                        return 0;
+                }
+                if (r != -ENODATA)
+                        return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m");
+
+                return 0;
+        }
+
+        size_t rta_len;
+        _cleanup_free_ void *rta = NULL;
+        r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta);
+        if (r == -ENODATA)
+                return 0;
+        if (r < 0)
+                return log_warning_errno(r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m");
+
+        r = route_parse_nexthops(route, rta, rta_len);
+        if (r < 0)
+                return log_warning_errno(r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m");
+
+        return 0;
+}
+
+int route_section_verify_nexthops(Route *route) {
+        assert(route);
+        assert(route->section);
+
+        if (route->gateway_from_dhcp_or_ra) {
+                assert(route->network);
+
+                if (route->nexthop.family == AF_UNSPEC)
+                        /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */
+                        switch (route->family) {
+                        case AF_UNSPEC:
+                                log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
+                                            "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".",
+                                            route->section->filename, route->section->line);
+
+                                route->nexthop.family = route->family = AF_INET;
+                                break;
+                        case AF_INET:
+                        case AF_INET6:
+                                log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
+                                            "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.",
+                                            route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra");
+
+                                route->nexthop.family = route->family;
+                                break;
+                        default:
+                                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                         "%s: Invalid route family. Ignoring [Route] section from line %u.",
+                                                         route->section->filename, route->section->line);
+                        }
+
+                if (route->nexthop.family == AF_INET && !FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4))
+                        return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                 "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. "
+                                                 "Ignoring [Route] section from line %u.",
+                                                 route->section->filename, route->section->line);
+
+                if (route->nexthop.family == AF_INET6 && !route->network->ipv6_accept_ra)
+                        return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                 "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. "
+                                                 "Ignoring [Route] section from line %u.",
+                                                 route->section->filename, route->section->line);
+        }
+
+        /* When only Gateway= is specified, assume the route family based on the Gateway address. */
+        if (route->family == AF_UNSPEC)
+                route->family = route->nexthop.family;
+
+        if (route->family == AF_UNSPEC) {
+                assert(route->section);
+
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                         "%s: Route section without Gateway=, Destination=, Source=, "
+                                         "or PreferredSource= field configured. "
+                                         "Ignoring [Route] section from line %u.",
+                                         route->section->filename, route->section->line);
+        }
+
+        if (route->gateway_onlink < 0 && in_addr_is_set(route->nexthop.family, &route->nexthop.gw) &&
+            route->network && ordered_hashmap_isempty(route->network->addresses_by_section)) {
+                /* If no address is configured, in most cases the gateway cannot be reachable.
+                 * TODO: we may need to improve the condition above. */
+                log_warning("%s: Gateway= without static address configured. "
+                            "Enabling GatewayOnLink= option.",
+                            route->section->filename);
+                route->gateway_onlink = true;
+        }
+
+        if (route->gateway_onlink >= 0)
+                SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink);
+
+        if (route->family == AF_INET6) {
+                if (route->nexthop.family == AF_INET)
+                        return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                 "%s: IPv4 gateway is configured for IPv6 route. "
+                                                 "Ignoring [Route] section from line %u.",
+                                                 route->section->filename, route->section->line);
+
+                RouteNextHop *nh;
+                ORDERED_SET_FOREACH(nh, route->nexthops)
+                        if (nh->family == AF_INET)
+                                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                         "%s: IPv4 multipath route is specified for IPv6 route. "
+                                                         "Ignoring [Route] section from line %u.",
+                                                         route->section->filename, route->section->line);
+        }
+
+        if (route->nexthop_id != 0 &&
+            (route->gateway_from_dhcp_or_ra ||
+             in_addr_is_set(route->nexthop.family, &route->nexthop.gw) ||
+             !ordered_set_isempty(route->nexthops)))
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                         "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. "
+                                         "Ignoring [Route] section from line %u.",
+                                         route->section->filename, route->section->line);
+
+        if (route_type_is_reject(route) &&
+            (route->gateway_from_dhcp_or_ra ||
+             in_addr_is_set(route->nexthop.family, &route->nexthop.gw) ||
+             !ordered_set_isempty(route->nexthops)))
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                         "%s: reject type route cannot be specified with Gateway= or MultiPathRoute=. "
+                                         "Ignoring [Route] section from line %u.",
+                                         route->section->filename, route->section->line);
+
+        if ((route->gateway_from_dhcp_or_ra ||
+             in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) &&
+            !ordered_set_isempty(route->nexthops))
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                         "%s: Gateway= cannot be specified with MultiPathRoute=. "
+                                         "Ignoring [Route] section from line %u.",
+                                         route->section->filename, route->section->line);
+
+        if (ordered_set_size(route->nexthops) == 1) {
+                _cleanup_(route_nexthop_freep) RouteNextHop *nh = ordered_set_steal_first(route->nexthops);
+
+                route_nexthop_done(&route->nexthop);
+                route->nexthop = TAKE_STRUCT(*nh);
+
+                assert(ordered_set_isempty(route->nexthops));
+                route->nexthops = ordered_set_free(route->nexthops);
+        }
+
+        return 0;
+}
+
+int config_parse_gateway(
+                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;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (streq(section, "Network")) {
+                /* we are not in an Route section, so use line number instead */
+                r = route_new_static(network, filename, 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;
+                }
+        } else {
+                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->gateway_from_dhcp_or_ra = false;
+                        route->nexthop.family = AF_UNSPEC;
+                        route->nexthop.gw = IN_ADDR_NULL;
+                        TAKE_PTR(route);
+                        return 0;
+                }
+
+                if (streq(rvalue, "_dhcp")) {
+                        route->gateway_from_dhcp_or_ra = true;
+                        route->nexthop.family = AF_UNSPEC;
+                        route->nexthop.gw = IN_ADDR_NULL;
+                        TAKE_PTR(route);
+                        return 0;
+                }
+
+                if (streq(rvalue, "_dhcp4")) {
+                        route->gateway_from_dhcp_or_ra = true;
+                        route->nexthop.family = AF_INET;
+                        route->nexthop.gw = IN_ADDR_NULL;
+                        TAKE_PTR(route);
+                        return 0;
+                }
+
+                if (streq(rvalue, "_ipv6ra")) {
+                        route->gateway_from_dhcp_or_ra = true;
+                        route->nexthop.family = AF_INET6;
+                        route->nexthop.gw = IN_ADDR_NULL;
+                        TAKE_PTR(route);
+                        return 0;
+                }
+        }
+
+        r = in_addr_from_string_auto(rvalue, &route->nexthop.family, &route->nexthop.gw);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
+                return 0;
+        }
+
+        route->gateway_from_dhcp_or_ra = false;
+        TAKE_PTR(route);
+        return 0;
+}
+
+int config_parse_route_gateway_onlink(
+                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;
+        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;
+        }
+
+        r = config_parse_tristate(unit, filename, line, section, section_line, lvalue, ltype, rvalue,
+                                  &route->gateway_onlink, network);
+        if (r <= 0)
+                return r;
+
+        TAKE_PTR(route);
+        return 0;
+}
+
 int config_parse_route_nexthop(
                 const char *unit,
                 const char *filename,
@@ -75,14 +1078,13 @@ int config_parse_multipath_route(
                 void *data,
                 void *userdata) {
 
-        _cleanup_(multipath_route_freep) MultipathRoute *m = NULL;
+        _cleanup_(route_nexthop_freep) RouteNextHop *nh = 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;
+        int r;
 
         assert(filename);
         assert(section);
@@ -100,13 +1102,13 @@ int config_parse_multipath_route(
         }
 
         if (isempty(rvalue)) {
-                route->multipath_routes = ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free);
+                route->nexthops = ordered_set_free(route->nexthops);
                 TAKE_PTR(route);
                 return 0;
         }
 
-        m = new0(MultipathRoute, 1);
-        if (!m)
+        nh = new0(RouteNextHop, 1);
+        if (!nh)
                 return log_oom();
 
         p = rvalue;
@@ -125,7 +1127,7 @@ int config_parse_multipath_route(
 
                 r = parse_ifindex(dev);
                 if (r > 0)
-                        m->ifindex = r;
+                        nh->ifindex = r;
                 else {
                         if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) {
                                 log_syntax(unit, LOG_WARNING, filename, line, 0,
@@ -133,23 +1135,21 @@ int config_parse_multipath_route(
                                 return 0;
                         }
 
-                        m->ifname = strdup(dev);
-                        if (!m->ifname)
+                        nh->ifname = strdup(dev);
+                        if (!nh->ifname)
                                 return log_oom();
                 }
         }
 
-        r = in_addr_from_string_auto(word, &family, &a);
+        r = in_addr_from_string_auto(word, &nh->family, &nh->gw);
         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);
+                r = safe_atou32(p, &nh->weight);
                 if (r < 0) {
                         log_syntax(unit, LOG_WARNING, filename, line, r,
                                    "Invalid multipath route weight, ignoring assignment: %s", p);
@@ -159,15 +1159,15 @@ int config_parse_multipath_route(
                  * 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) {
+                if (nh->weight == 0 || nh->weight > 256) {
                         log_syntax(unit, LOG_WARNING, filename, line, 0,
                                    "Invalid multipath route weight, ignoring assignment: %s", p);
                         return 0;
                 }
-                m->weight--;
+                nh->weight--;
         }
 
-        r = ordered_set_ensure_put(&route->multipath_routes, NULL, m);
+        r = ordered_set_ensure_put(&route->nexthops, &route_nexthop_hash_ops, nh);
         if (r == -ENOMEM)
                 return log_oom();
         if (r < 0) {
@@ -176,7 +1176,7 @@ int config_parse_multipath_route(
                 return 0;
         }
 
-        TAKE_PTR(m);
+        TAKE_PTR(nh);
         TAKE_PTR(route);
         return 0;
 }
index ae07696e5a7ba2e85e86b9f16d91d55bef5b661e..5e4602d3cd369d3808b68f2cb6a524192a651b61 100644 (file)
@@ -1,7 +1,54 @@
 /* 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 "in-addr-util.h"
+#include "macro.h"
+#include "siphash24.h"
+
+typedef struct Link Link;
+typedef struct Manager Manager;
+typedef struct Route Route;
+
+typedef struct RouteNextHop {
+        int family; /* used in RTA_VIA (IPv4 only) */
+        union in_addr_union gw; /* RTA_GATEWAY or RTA_VIA (IPv4 only) */
+        uint32_t weight; /* rtnh_hops */
+        int ifindex; /* RTA_OIF(u32) or rtnh_ifindex */
+        char *ifname; /* only used by Route object owned by Network object */
+        /* unsupported attributes: RTA_FLOW (IPv4 only), RTA_ENCAP_TYPE, RTA_ENCAP. */
+} RouteNextHop;
+
+#define ROUTE_NEXTHOP_NULL ((const RouteNextHop) {})
+
+RouteNextHop* route_nexthop_free(RouteNextHop *nh);
+DEFINE_TRIVIAL_CLEANUP_FUNC(RouteNextHop*, route_nexthop_free);
+
+void route_nexthops_done(Route *route);
+
+void route_nexthops_hash_func(const Route *route, struct siphash *state);
+int route_nexthops_compare_func(const Route *a, const Route *b);
+
+int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest);
+bool route_nexthops_needs_adjust(const Route *route);
+int route_adjust_nexthops(Route *route, Link *link);
+
+int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret);
+int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager);
+
+int route_nexthops_to_string(const Route *route, char **ret);
+
+int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message);
+int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message);
+
+int route_section_verify_nexthops(Route *route);
 
+CONFIG_PARSER_PROTOTYPE(config_parse_gateway);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_gateway_onlink);
 CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop);
 CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route);
index d49a0b95124a2cac1295f809c372eea12085b09f..fde7dfa5d42e52c8b1bd03f130836de10a805547 100644 (file)
@@ -39,6 +39,12 @@ unsigned routes_max(void) {
         return cached;
 }
 
+bool route_type_is_reject(const Route *route) {
+        assert(route);
+
+        return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW);
+}
+
 static bool route_lifetime_is_valid(const Route *route) {
         assert(route);
 
@@ -68,7 +74,7 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) {
                         continue;
                 if (route->scope != RT_SCOPE_UNIVERSE)
                         continue;
-                if (!in_addr_is_set(route->gw_family, &route->gw))
+                if (!in_addr_is_set(route->nexthop.family, &route->nexthop.gw))
                         continue;
 
                 /* Found a default gateway. */
@@ -77,7 +83,7 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) {
 
                 /* If we have already found another gw, then let's compare their weight and priority. */
                 if (*gw) {
-                        if (route->gw_weight > (*gw)->gw_weight)
+                        if (route->nexthop.weight > (*gw)->nexthop.weight)
                                 continue;
                         if (route->priority >= (*gw)->priority)
                                 continue;
index f326888c9347dd2c9d87ed58966264a054ba26dc..eba823a2d0d5c3a1d23c73f507e4aca46acdb37b 100644 (file)
@@ -13,6 +13,8 @@ typedef struct Route Route;
 
 unsigned routes_max(void);
 
+bool route_type_is_reject(const Route *route);
+
 bool link_find_default_gateway(Link *link, int family, Route **gw);
 static inline bool link_has_default_gateway(Link *link, int family) {
         return link_find_default_gateway(link, family, NULL);
index 0553a6634381f0e34b183b9d1ae3083ffc2e7925..05280a582f6ac6d57b1680e73adbb95b27b0fc4f 100644 (file)
 #include "vrf.h"
 #include "wireguard.h"
 
-int route_new(Route **ret) {
-        _cleanup_(route_freep) Route *route = NULL;
-
-        route = new(Route, 1);
-        if (!route)
-                return -ENOMEM;
-
-        *route = (Route) {
-                .family = AF_UNSPEC,
-                .scope = RT_SCOPE_UNIVERSE,
-                .protocol = RTPROT_UNSPEC,
-                .type = RTN_UNICAST,
-                .table = RT_TABLE_MAIN,
-                .lifetime_usec = USEC_INFINITY,
-                .gateway_onlink = -1,
-        };
-
-        *ret = TAKE_PTR(route);
-
-        return 0;
-}
-
-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;
-
-        assert(network);
-        assert(ret);
-        assert(filename);
-        assert(section_line > 0);
-
-        r = config_section_new(filename, section_line, &n);
-        if (r < 0)
-                return r;
-
-        route = hashmap_get(network->routes_by_section, n);
-        if (route) {
-                *ret = TAKE_PTR(route);
-                return 0;
-        }
-
-        if (hashmap_size(network->routes_by_section) >= routes_max())
-                return -E2BIG;
-
-        r = route_new(&route);
-        if (r < 0)
-                return r;
-
-        route->protocol = RTPROT_STATIC;
-        route->network = network;
-        route->section = TAKE_PTR(n);
-        route->source = NETWORK_CONFIG_SOURCE_STATIC;
-
-        r = hashmap_ensure_put(&network->routes_by_section, &config_section_hash_ops, route->section, route);
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(route);
-        return 0;
-}
-
-Route *route_free(Route *route) {
+Route* route_free(Route *route) {
         if (!route)
                 return NULL;
 
@@ -92,15 +30,17 @@ Route *route_free(Route *route) {
                 hashmap_remove(route->network->routes_by_section, route->section);
         }
 
-        config_section_free(route->section);
-
         if (route->link)
                 set_remove(route->link->routes, route);
 
         if (route->manager)
                 set_remove(route->manager->routes, route);
 
-        ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free);
+        if (route->wireguard)
+                set_remove(route->wireguard->routes, route);
+
+        config_section_free(route->section);
+        route_nexthops_done(route);
         route_metric_done(&route->metric);
         sd_event_source_disable_unref(route->expire);
 
@@ -114,31 +54,51 @@ static void route_hash_func(const Route *route, struct siphash *state) {
 
         switch (route->family) {
         case AF_INET:
-        case AF_INET6:
-                siphash24_compress_typesafe(route->dst_prefixlen, state);
+                /* First, the table, destination prefix, priority, and tos (dscp), are used to find routes.
+                 * See fib_table_insert(), fib_find_node(), and fib_find_alias() in net/ipv4/fib_trie.c of the kernel. */
+                siphash24_compress_typesafe(route->table, state);
                 in_addr_hash_func(&route->dst, route->family, state);
-
-                siphash24_compress_typesafe(route->src_prefixlen, state);
-                in_addr_hash_func(&route->src, route->family, state);
-
-                siphash24_compress_typesafe(route->gw_family, state);
-                if (IN_SET(route->gw_family, AF_INET, AF_INET6)) {
-                        in_addr_hash_func(&route->gw, route->gw_family, state);
-                        siphash24_compress_typesafe(route->gw_weight, state);
-                }
-
-                in_addr_hash_func(&route->prefsrc, route->family, state);
-
-                siphash24_compress_typesafe(route->tos, state);
+                siphash24_compress_typesafe(route->dst_prefixlen, state);
                 siphash24_compress_typesafe(route->priority, state);
-                siphash24_compress_typesafe(route->table, state);
+                siphash24_compress_typesafe(route->tos, state);
+
+                /* Then, protocol, scope, type, flags, prefsrc, metrics (RTAX_* attributes), and nexthops (gateways)
+                 * are used to find routes. See fib_find_info() in net/ipv4/fib_semantics.c of the kernel. */
                 siphash24_compress_typesafe(route->protocol, state);
                 siphash24_compress_typesafe(route->scope, state);
                 siphash24_compress_typesafe(route->type, state);
+                unsigned flags = route->flags & ~RTNH_COMPARE_MASK;
+                siphash24_compress_typesafe(flags, state);
+                in_addr_hash_func(&route->prefsrc, route->family, state);
+
+                /* nexthops (id, number of nexthops, nexthop) */
+                route_nexthops_hash_func(route, state);
+
+                /* metrics */
                 route_metric_hash_func(&route->metric, state);
-                siphash24_compress_typesafe(route->nexthop_id, state);
+                break;
 
+        case AF_INET6:
+                /* First, table and destination prefix are used for classifying routes.
+                 * See fib6_add() and fib6_add_1() in net/ipv6/ip6_fib.c of the kernel. */
+                siphash24_compress_typesafe(route->table, state);
+                in_addr_hash_func(&route->dst, route->family, state);
+                siphash24_compress_typesafe(route->dst_prefixlen, state);
+
+                /* Then, source prefix is used. See fib6_add(). */
+                in_addr_hash_func(&route->src, route->family, state);
+                siphash24_compress_typesafe(route->src_prefixlen, state);
+
+                /* See fib6_add_rt2node(). */
+                siphash24_compress_typesafe(route->priority, state);
+
+                /* See rt6_duplicate_nexthop() in include/net/ip6_route.h of the kernel.
+                 * Here, we hash nexthop in a similar way as the one for IPv4. */
+                route_nexthops_hash_func(route, state);
+
+                /* Unlike IPv4 routes, metrics are not taken into account. */
                 break;
+
         default:
                 /* treat any other address family as AF_UNSPEC */
                 break;
@@ -154,8 +114,7 @@ static int route_compare_func(const Route *a, const Route *b) {
 
         switch (a->family) {
         case AF_INET:
-        case AF_INET6:
-                r = CMP(a->dst_prefixlen, b->dst_prefixlen);
+                r = CMP(a->table, b->table);
                 if (r != 0)
                         return r;
 
@@ -163,65 +122,71 @@ static int route_compare_func(const Route *a, const Route *b) {
                 if (r != 0)
                         return r;
 
-                r = CMP(a->src_prefixlen, b->src_prefixlen);
+                r = CMP(a->dst_prefixlen, b->dst_prefixlen);
                 if (r != 0)
                         return r;
 
-                r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family));
+                r = CMP(a->priority, b->priority);
                 if (r != 0)
                         return r;
 
-                r = CMP(a->gw_family, b->gw_family);
+                r = CMP(a->tos, b->tos);
                 if (r != 0)
                         return r;
 
-                if (IN_SET(a->gw_family, AF_INET, AF_INET6)) {
-                        r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
-                        if (r != 0)
-                                return r;
+                r = CMP(a->protocol, b->protocol);
+                if (r != 0)
+                        return r;
 
-                        r = CMP(a->gw_weight, b->gw_weight);
-                        if (r != 0)
-                                return r;
-                }
+                r = CMP(a->scope, b->scope);
+                if (r != 0)
+                        return r;
 
-                r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family));
+                r = CMP(a->type, b->type);
                 if (r != 0)
                         return r;
 
-                r = CMP(a->tos, b->tos);
+                r = CMP(a->flags & ~RTNH_COMPARE_MASK, b->flags & ~RTNH_COMPARE_MASK);
                 if (r != 0)
                         return r;
 
-                r = CMP(a->priority, b->priority);
+                r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family));
                 if (r != 0)
                         return r;
 
+                r = route_nexthops_compare_func(a, b);
+                if (r != 0)
+                        return r;
+
+                return route_metric_compare_func(&a->metric, &b->metric);
+
+        case AF_INET6:
                 r = CMP(a->table, b->table);
                 if (r != 0)
                         return r;
 
-                r = CMP(a->protocol, b->protocol);
+                r = memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family));
                 if (r != 0)
                         return r;
 
-                r = CMP(a->scope, b->scope);
+                r = CMP(a->dst_prefixlen, b->dst_prefixlen);
                 if (r != 0)
                         return r;
 
-                r = CMP(a->type, b->type);
+                r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family));
                 if (r != 0)
                         return r;
 
-                r = route_metric_compare_func(&a->metric, &b->metric);
+                r = CMP(a->src_prefixlen, b->src_prefixlen);
                 if (r != 0)
                         return r;
 
-                r = CMP(a->nexthop_id, b->nexthop_id);
+                r = CMP(a->priority, b->priority);
                 if (r != 0)
                         return r;
 
-                return 0;
+                return route_nexthops_compare_func(a, b);
+
         default:
                 /* treat any other address family as AF_UNSPEC */
                 return 0;
@@ -235,286 +200,190 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
                 route_compare_func,
                 route_free);
 
-static bool route_type_is_reject(const Route *route) {
-        assert(route);
-
-        return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW);
-}
-
-static bool route_needs_convert(const Route *route) {
-        assert(route);
-
-        return route->nexthop_id > 0 || !ordered_set_isempty(route->multipath_routes);
-}
-
-static int route_add(Manager *manager, Link *link, Route *route) {
-        int r;
-
-        assert(route);
-
-        if (route_type_is_reject(route)) {
-                assert(manager);
-
-                r = set_ensure_put(&manager->routes, &route_hash_ops, route);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        return -EEXIST;
-
-                route->manager = manager;
-        } else {
-                assert(link);
-
-                r = set_ensure_put(&link->routes, &route_hash_ops, route);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        return -EEXIST;
-
-                route->link = link;
-        }
-
-        return 0;
-}
-
-int route_get(Manager *manager, Link *link, const Route *in, Route **ret) {
-        Route *route;
-
-        assert(in);
-
-        if (route_type_is_reject(in)) {
-                if (!manager)
-                        return -ENOENT;
-
-                route = set_get(manager->routes, in);
-        } else {
-                if (!link)
-                        return -ENOENT;
+int route_new(Route **ret) {
+        _cleanup_(route_freep) Route *route = NULL;
 
-                route = set_get(link->routes, in);
-        }
+        route = new(Route, 1);
         if (!route)
-                return -ENOENT;
+                return -ENOMEM;
 
-        if (ret)
-                *ret = route;
+        *route = (Route) {
+                .family = AF_UNSPEC,
+                .scope = RT_SCOPE_UNIVERSE,
+                .protocol = RTPROT_UNSPEC,
+                .type = RTN_UNICAST,
+                .table = RT_TABLE_MAIN,
+                .lifetime_usec = USEC_INFINITY,
+                .gateway_onlink = -1,
+        };
+
+        *ret = TAKE_PTR(route);
 
         return 0;
 }
 
-int route_dup(const Route *src, Route **ret) {
-        _cleanup_(route_freep) Route *dest = NULL;
+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;
 
-        /* This does not copy mulipath routes. */
-
-        assert(src);
+        assert(network);
         assert(ret);
+        assert(filename);
+        assert(section_line > 0);
 
-        dest = newdup(Route, src, 1);
-        if (!dest)
-                return -ENOMEM;
-
-        /* Unset all pointers */
-        dest->network = NULL;
-        dest->section = NULL;
-        dest->link = NULL;
-        dest->manager = NULL;
-        dest->multipath_routes = NULL;
-        dest->metric = ROUTE_METRIC_NULL;
-        dest->expire = NULL;
-
-        r = route_metric_copy(&src->metric, &dest->metric);
+        r = config_section_new(filename, section_line, &n);
         if (r < 0)
                 return r;
 
-        *ret = TAKE_PTR(dest);
-        return 0;
-}
-
-static void route_apply_nexthop(Route *route, const NextHop *nh, uint8_t nh_weight) {
-        assert(route);
-        assert(nh);
-        assert(hashmap_isempty(nh->group));
+        route = hashmap_get(network->routes_by_section, n);
+        if (route) {
+                *ret = TAKE_PTR(route);
+                return 0;
+        }
 
-        route->gw_family = nh->family;
-        route->gw = nh->gw;
+        if (hashmap_size(network->routes_by_section) >= routes_max())
+                return -E2BIG;
 
-        if (nh_weight != UINT8_MAX)
-                route->gw_weight = nh_weight;
+        r = route_new(&route);
+        if (r < 0)
+                return r;
 
-        if (nh->blackhole)
-                route->type = RTN_BLACKHOLE;
-}
+        route->protocol = RTPROT_STATIC;
+        route->network = network;
+        route->section = TAKE_PTR(n);
+        route->source = NETWORK_CONFIG_SOURCE_STATIC;
 
-static void route_apply_multipath_route(Route *route, const MultipathRoute *m) {
-        assert(route);
-        assert(m);
+        r = hashmap_ensure_put(&network->routes_by_section, &config_section_hash_ops, route->section, route);
+        if (r < 0)
+                return r;
 
-        route->gw_family = m->gateway.family;
-        route->gw = m->gateway.address;
-        route->gw_weight = m->weight;
+        *ret = TAKE_PTR(route);
+        return 0;
 }
 
-static int multipath_route_get_link(Manager *manager, const MultipathRoute *m, Link **ret) {
+static int route_add(Manager *manager, Route *route) {
         int r;
 
         assert(manager);
-        assert(m);
+        assert(route);
+        assert(!route->network);
+        assert(!route->wireguard);
 
-        if (m->ifname) {
-                r = link_get_by_name(manager, m->ifname, ret);
-                return r < 0 ? r : 1;
+        Link *link;
+        if (route_nexthop_get_link(manager, &route->nexthop, &link) >= 0) {
+                r = set_ensure_put(&link->routes, &route_hash_ops, route);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EEXIST;
 
-        } else if (m->ifindex > 0) { /* Always ignore ifindex if ifname is set. */
-                r = link_get_by_index(manager, m->ifindex, ret);
-                return r < 0 ? r : 1;
+                route->link = link;
+                return 0;
         }
 
-        if (ret)
-                *ret = NULL;
-        return 0;
-}
-
-typedef struct ConvertedRoutes {
-        size_t n;
-        Route **routes;
-        Link **links;
-} ConvertedRoutes;
-
-static ConvertedRoutes *converted_routes_free(ConvertedRoutes *c) {
-        if (!c)
-                return NULL;
-
-        for (size_t i = 0; i < c->n; i++)
-                route_free(c->routes[i]);
-
-        free(c->routes);
-        free(c->links);
+        r = set_ensure_put(&manager->routes, &route_hash_ops, route);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EEXIST;
 
-        return mfree(c);
+        route->manager = manager;
+        return 0;
 }
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(ConvertedRoutes*, converted_routes_free);
+int route_get(Manager *manager, Link *link, const Route *route, Route **ret) {
+        Route *existing;
 
-static int converted_routes_new(size_t n, ConvertedRoutes **ret) {
-        _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL;
-        _cleanup_free_ Route **routes = NULL;
-        _cleanup_free_ Link **links = NULL;
-
-        assert(n > 0);
-        assert(ret);
-
-        routes = new0(Route*, n);
-        if (!routes)
-                return -ENOMEM;
-
-        links = new0(Link*, n);
-        if (!links)
-                return -ENOMEM;
+        if (!manager)
+                manager = ASSERT_PTR(ASSERT_PTR(link)->manager);
+        assert(route);
 
-        c = new(ConvertedRoutes, 1);
-        if (!c)
-                return -ENOMEM;
+        if (route_nexthop_get_link(manager, &route->nexthop, &link) >= 0)
+                existing = set_get(link->routes, route);
+        else
+                existing = set_get(manager->routes, route);
+        if (!existing)
+                return -ENOENT;
 
-        *c = (ConvertedRoutes) {
-                .n = n,
-                .routes = TAKE_PTR(routes),
-                .links = TAKE_PTR(links),
-        };
+        if (ret)
+                *ret = existing;
 
-        *ret = TAKE_PTR(c);
         return 0;
 }
 
-static int route_convert(Manager *manager, const Route *route, ConvertedRoutes **ret) {
-        _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL;
+static int route_get_link(Manager *manager, const Route *route, Link **ret) {
         int r;
 
         assert(manager);
-        assert(route);
-        assert(ret);
-
-        if (!route_needs_convert(route)) {
-                *ret = NULL;
-                return 0;
-        }
+        assert(route);
 
-        if (route->nexthop_id > 0) {
-                struct nexthop_grp *nhg;
+        if (route->nexthop_id != 0) {
                 NextHop *nh;
 
                 r = nexthop_get_by_id(manager, route->nexthop_id, &nh);
                 if (r < 0)
                         return r;
 
-                if (hashmap_isempty(nh->group)) {
-                        r = converted_routes_new(1, &c);
-                        if (r < 0)
-                                return r;
-
-                        r = route_dup(route, &c->routes[0]);
-                        if (r < 0)
-                                return r;
-
-                        route_apply_nexthop(c->routes[0], nh, UINT8_MAX);
-                        (void) link_get_by_index(manager, nh->ifindex, c->links);
-
-                        *ret = TAKE_PTR(c);
-                        return 1;
-                }
+                return link_get_by_index(manager, nh->ifindex, ret);
+        }
 
-                r = converted_routes_new(hashmap_size(nh->group), &c);
-                if (r < 0)
-                        return r;
+        return route_nexthop_get_link(manager, &route->nexthop, ret);
+}
 
-                size_t i = 0;
-                HASHMAP_FOREACH(nhg, nh->group) {
-                        NextHop *h;
+static int route_get_request(Manager *manager, const Route *route, Request **ret) {
+        Request *req;
 
-                        r = nexthop_get_by_id(manager, nhg->id, &h);
-                        if (r < 0)
-                                return r;
+        assert(manager);
+        assert(route);
 
-                        r = route_dup(route, &c->routes[i]);
-                        if (r < 0)
-                                return r;
+        req = ordered_set_get(manager->request_queue,
+                              &(const Request) {
+                                      .type = REQUEST_TYPE_ROUTE,
+                                      .userdata = (void*) route,
+                                      .hash_func = (hash_func_t) route_hash_func,
+                                      .compare_func = (compare_func_t) route_compare_func,
+                              });
+        if (!req)
+                return -ENOENT;
 
-                        route_apply_nexthop(c->routes[i], h, nhg->weight);
-                        (void) link_get_by_index(manager, h->ifindex, c->links + i);
+        if (ret)
+                *ret = req;
+        return 0;
+}
 
-                        i++;
-                }
+int route_dup(const Route *src, const RouteNextHop *nh, Route **ret) {
+        _cleanup_(route_freep) Route *dest = NULL;
+        int r;
 
-                *ret = TAKE_PTR(c);
-                return 1;
+        assert(src);
+        assert(ret);
 
-        }
+        dest = newdup(Route, src, 1);
+        if (!dest)
+                return -ENOMEM;
 
-        assert(!ordered_set_isempty(route->multipath_routes));
+        /* Unset all pointers */
+        dest->manager = NULL;
+        dest->network = NULL;
+        dest->wireguard = NULL;
+        dest->section = NULL;
+        dest->link = NULL;
+        dest->nexthop = ROUTE_NEXTHOP_NULL;
+        dest->nexthops = NULL;
+        dest->metric = ROUTE_METRIC_NULL;
+        dest->expire = NULL;
 
-        r = converted_routes_new(ordered_set_size(route->multipath_routes), &c);
+        r = route_nexthops_copy(src, nh, dest);
         if (r < 0)
                 return r;
 
-        size_t i = 0;
-        MultipathRoute *m;
-        ORDERED_SET_FOREACH(m, route->multipath_routes) {
-                r = route_dup(route, &c->routes[i]);
-                if (r < 0)
-                        return r;
-
-                route_apply_multipath_route(c->routes[i], m);
-
-                r = multipath_route_get_link(manager, m, &c->links[i]);
-                if (r < 0)
-                        return r;
-
-                i++;
-        }
+        r = route_metric_copy(&src->metric, &dest->metric);
+        if (r < 0)
+                return r;
 
-        *ret = TAKE_PTR(c);
-        return 1;
+        *ret = TAKE_PTR(dest);
+        return 0;
 }
 
 void link_mark_routes(Link *link, NetworkConfigSource source) {
@@ -530,20 +399,21 @@ void link_mark_routes(Link *link, NetworkConfigSource source) {
         }
 }
 
-static void log_route_debug(const Route *route, const char *str, const Link *link, const Manager *manager) {
-        _cleanup_free_ char *state = NULL, *gw_alloc = NULL, *prefsrc = NULL,
+static void log_route_debug(const Route *route, const char *str, Manager *manager) {
+        _cleanup_free_ char *state = NULL, *nexthop = NULL, *prefsrc = NULL,
                 *table = NULL, *scope = NULL, *proto = NULL, *flags = NULL;
-        const char *gw = NULL, *dst, *src;
+        const char *dst, *src;
+        Link *link = NULL;
 
         assert(route);
         assert(str);
         assert(manager);
 
-        /* link may be NULL. */
-
         if (!DEBUG_LOGGING)
                 return;
 
+        (void) route_get_link(manager, route, &link);
+
         (void) network_config_state_to_string_alloc(route->state, &state);
 
         dst = in_addr_is_set(route->family, &route->dst) || route->dst_prefixlen > 0 ?
@@ -551,32 +421,8 @@ static void log_route_debug(const Route *route, const char *str, const Link *lin
         src = in_addr_is_set(route->family, &route->src) || route->src_prefixlen > 0 ?
                 IN_ADDR_PREFIX_TO_STRING(route->family, &route->src, route->src_prefixlen) : NULL;
 
-        if (in_addr_is_set(route->gw_family, &route->gw)) {
-                (void) in_addr_to_string(route->gw_family, &route->gw, &gw_alloc);
-                gw = gw_alloc;
-        } else if (route->gateway_from_dhcp_or_ra) {
-                if (route->gw_family == AF_INET)
-                        gw = "_dhcp4";
-                else if (route->gw_family == AF_INET6)
-                        gw = "_ipv6ra";
-        } else {
-                MultipathRoute *m;
-
-                ORDERED_SET_FOREACH(m, route->multipath_routes) {
-                        _cleanup_free_ char *buf = NULL;
-                        union in_addr_union a = m->gateway.address;
-
-                        (void) in_addr_to_string(m->gateway.family, &a, &buf);
-                        (void) strextend_with_separator(&gw_alloc, ",", strna(buf));
-                        if (m->ifname)
-                                (void) strextend(&gw_alloc, "@", m->ifname);
-                        else if (m->ifindex > 0)
-                                (void) strextendf(&gw_alloc, "@%i", m->ifindex);
-                        /* See comments in config_parse_multipath_route(). */
-                        (void) strextendf(&gw_alloc, ":%"PRIu32, m->weight + 1);
-                }
-                gw = gw_alloc;
-        }
+        (void) route_nexthops_to_string(route, &nexthop);
+
         if (in_addr_is_set(route->family, &route->prefsrc))
                 (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc);
         (void) route_scope_to_string_alloc(route->scope, &scope);
@@ -585,110 +431,95 @@ static void log_route_debug(const Route *route, const char *str, const Link *lin
         (void) route_flags_to_string_alloc(route->flags, &flags);
 
         log_link_debug(link,
-                       "%s %s route (%s): dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, "
-                       "proto: %s, type: %s, nexthop: %"PRIu32", priority: %"PRIu32", flags: %s",
+                       "%s %s route (%s): dst: %s, src: %s, %s, prefsrc: %s, "
+                       "table: %s, priority: %"PRIu32", "
+                       "proto: %s, scope: %s, type: %s, flags: %s",
                        str, strna(network_config_source_to_string(route->source)), strna(state),
-                       strna(dst), strna(src), strna(gw), strna(prefsrc),
-                       strna(scope), strna(table), strna(proto),
-                       strna(route_type_to_string(route->type)),
-                       route->nexthop_id, route->priority, strna(flags));
+                       strna(dst), strna(src), strna(nexthop), strna(prefsrc),
+                       strna(table), route->priority,
+                       strna(proto), strna(scope), strna(route_type_to_string(route->type)), strna(flags));
 }
 
-static int route_set_netlink_message(const Route *route, sd_netlink_message *req, Link *link) {
+static int route_set_netlink_message(const Route *route, sd_netlink_message *m) {
         int r;
 
         assert(route);
-        assert(req);
-
-        /* link may be NULL */
-
-        if (in_addr_is_set(route->gw_family, &route->gw) && route->nexthop_id == 0) {
-                if (route->gw_family == route->family) {
-                        r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->gw_family, &route->gw);
-                        if (r < 0)
-                                return r;
-                } else {
-                        RouteVia rtvia = {
-                                .family = route->gw_family,
-                                .address = route->gw,
-                        };
-
-                        r = sd_netlink_message_append_data(req, RTA_VIA, &rtvia, sizeof(rtvia));
-                        if (r < 0)
-                                return r;
-                }
-        }
+        assert(m);
 
+        /* rtmsg header (and relevant attributes) */
         if (route->dst_prefixlen > 0) {
-                r = netlink_message_append_in_addr_union(req, RTA_DST, route->family, &route->dst);
+                r = netlink_message_append_in_addr_union(m, RTA_DST, route->family, &route->dst);
                 if (r < 0)
                         return r;
 
-                r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen);
+                r = sd_rtnl_message_route_set_dst_prefixlen(m, route->dst_prefixlen);
                 if (r < 0)
                         return r;
         }
 
         if (route->src_prefixlen > 0) {
-                r = netlink_message_append_in_addr_union(req, RTA_SRC, route->family, &route->src);
+                r = netlink_message_append_in_addr_union(m, RTA_SRC, route->family, &route->src);
                 if (r < 0)
                         return r;
 
-                r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen);
+                r = sd_rtnl_message_route_set_src_prefixlen(m, route->src_prefixlen);
                 if (r < 0)
                         return r;
         }
 
-        if (in_addr_is_set(route->family, &route->prefsrc)) {
-                r = netlink_message_append_in_addr_union(req, RTA_PREFSRC, route->family, &route->prefsrc);
-                if (r < 0)
-                        return r;
-        }
+        r = sd_rtnl_message_route_set_tos(m, route->tos);
+        if (r < 0)
+                return r;
+
+        r = sd_rtnl_message_route_set_scope(m, route->scope);
+        if (r < 0)
+                return r;
+
+        r = sd_rtnl_message_route_set_type(m, route->type);
+        if (r < 0)
+                return r;
 
-        r = sd_rtnl_message_route_set_scope(req, route->scope);
+        r = sd_rtnl_message_route_set_flags(m, route->flags & ~RTNH_COMPARE_MASK);
         if (r < 0)
                 return r;
 
-        r = sd_rtnl_message_route_set_flags(req, route->flags & RTNH_F_ONLINK);
+        /* attributes */
+        r = sd_netlink_message_append_u32(m, RTA_PRIORITY, route->priority);
         if (r < 0)
                 return r;
 
+        if (in_addr_is_set(route->family, &route->prefsrc)) {
+                r = netlink_message_append_in_addr_union(m, RTA_PREFSRC, route->family, &route->prefsrc);
+                if (r < 0)
+                        return r;
+        }
+
         if (route->table < 256) {
-                r = sd_rtnl_message_route_set_table(req, route->table);
+                r = sd_rtnl_message_route_set_table(m, route->table);
                 if (r < 0)
                         return r;
         } else {
-                r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC);
+                r = sd_rtnl_message_route_set_table(m, RT_TABLE_UNSPEC);
                 if (r < 0)
                         return r;
 
                 /* Table attribute to allow more than 256. */
-                r = sd_netlink_message_append_u32(req, RTA_TABLE, route->table);
-                if (r < 0)
-                        return r;
-        }
-
-        if (!route_type_is_reject(route) &&
-            route->nexthop_id == 0 &&
-            ordered_set_isempty(route->multipath_routes)) {
-                assert(link); /* Those routes must be attached to a specific link */
-
-                r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+                r = sd_netlink_message_append_u32(m, RTA_TABLE, route->table);
                 if (r < 0)
                         return r;
         }
 
-        if (route->nexthop_id > 0) {
-                r = sd_netlink_message_append_u32(req, RTA_NH_ID, route->nexthop_id);
-                if (r < 0)
-                        return r;
-        }
+        r = sd_netlink_message_append_u8(m, RTA_PREF, route->pref);
+        if (r < 0)
+                return r;
 
-        r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref);
+        /* nexthops */
+        r = route_nexthops_set_netlink_message(route, m);
         if (r < 0)
                 return r;
 
-        r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority);
+        /* metrics */
+        r = route_metric_set_netlink_message(&route->metric, m);
         if (r < 0)
                 return r;
 
@@ -713,50 +544,31 @@ static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *l
 }
 
 int route_remove(Route *route) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
-        unsigned char type;
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
         Manager *manager;
         Link *link;
         int r;
 
         assert(route);
         assert(route->manager || (route->link && route->link->manager));
-        assert(IN_SET(route->family, AF_INET, AF_INET6));
 
         link = route->link;
         manager = route->manager ?: link->manager;
 
-        log_route_debug(route, "Removing", link, manager);
-
-        r = sd_rtnl_message_new_route(manager->rtnl, &req,
-                                      RTM_DELROUTE, route->family,
-                                      route->protocol);
-        if (r < 0)
-                return log_link_error_errno(link, r, "Could not create netlink message: %m");
-
-        if (route->family == AF_INET && route->nexthop_id > 0 && route->type == RTN_BLACKHOLE)
-                /* When IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel
-                 * sends RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type
-                 * fib_rt_info::type may not be blackhole. Thus, we cannot know the internal value.
-                 * Moreover, on route removal, the matching is done with the hidden value if we set
-                 * non-zero type in RTM_DELROUTE message. Note, sd_rtnl_message_new_route() sets
-                 * RTN_UNICAST by default. So, we need to clear the type here. */
-                type = RTN_UNSPEC;
-        else
-                type = route->type;
+        log_route_debug(route, "Removing", manager);
 
-        r = sd_rtnl_message_route_set_type(req, type);
+        r = sd_rtnl_message_new_route(manager->rtnl, &m, RTM_DELROUTE, route->family, route->protocol);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not set route type: %m");
+                return log_link_warning_errno(link, r, "Could not create netlink message: %m");
 
-        r = route_set_netlink_message(route, req, link);
+        r = route_set_netlink_message(route, m);
         if (r < 0)
-                return log_error_errno(r, "Could not fill netlink message: %m");
+                return log_link_warning_errno(link, r, "Could not fill netlink message: %m");
 
-        r = netlink_call_async(manager->rtnl, NULL, req, route_remove_handler,
+        r = netlink_call_async(manager->rtnl, NULL, m, route_remove_handler,
                                link ? link_netlink_destroy_callback : NULL, link);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not send netlink message: %m");
+                return log_link_warning_errno(link, r, "Could not send netlink message: %m");
 
         link_ref(link);
 
@@ -779,10 +591,32 @@ int route_remove_and_drop(Route *route) {
         return 0;
 }
 
+static int link_unmark_route(Link *link, const Route *route, const RouteNextHop *nh) {
+        _cleanup_(route_freep) Route *tmp = NULL;
+        Route *existing;
+        int r;
+
+        assert(link);
+        assert(route);
+
+        r = route_dup(route, nh, &tmp);
+        if (r < 0)
+                return r;
+
+        r = route_adjust_nexthops(tmp, link);
+        if (r < 0)
+                return r;
+
+        if (route_get(link->manager, link, tmp, &existing) < 0)
+                return 0;
+
+        route_unmark(existing);
+        return 1;
+}
+
 static void manager_mark_routes(Manager *manager, bool foreign, const Link *except) {
         Route *route;
         Link *link;
-        int r;
 
         assert(manager);
 
@@ -819,21 +653,14 @@ static void manager_mark_routes(Manager *manager, bool foreign, const Link *exce
                         continue;
 
                 HASHMAP_FOREACH(route, link->network->routes_by_section) {
-                        _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
-                        Route *existing;
-
-                        r = route_convert(manager, route, &converted);
-                        if (r < 0)
-                                continue;
-                        if (r == 0) {
-                                if (route_get(manager, NULL, route, &existing) >= 0)
-                                        route_unmark(existing);
-                                continue;
-                        }
+                        if (route->family == AF_INET || ordered_set_isempty(route->nexthops))
+                                (void) link_unmark_route(link, route, NULL);
 
-                        for (size_t i = 0; i < converted->n; i++)
-                                if (route_get(manager, NULL, converted->routes[i], &existing) >= 0)
-                                        route_unmark(existing);
+                        else {
+                                RouteNextHop *nh;
+                                ORDERED_SET_FOREACH(nh, route->nexthops)
+                                        (void) link_unmark_route(link, route, nh);
+                        }
                 }
         }
 }
@@ -877,12 +704,11 @@ static void link_unmark_wireguard_routes(Link *link) {
         if (!link->netdev || link->netdev->kind != NETDEV_KIND_WIREGUARD)
                 return;
 
-        Route *route, *existing;
+        Route *route;
         Wireguard *w = WIREGUARD(link->netdev);
 
         SET_FOREACH(route, w->routes)
-                if (route_get(NULL, link, route, &existing) >= 0)
-                        route_unmark(existing);
+                (void) link_unmark_route(link, route, NULL);
 }
 
 int link_drop_foreign_routes(Link *link) {
@@ -918,21 +744,14 @@ int link_drop_foreign_routes(Link *link) {
         }
 
         HASHMAP_FOREACH(route, link->network->routes_by_section) {
-                _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
-                Route *existing;
+                if (route->family == AF_INET || ordered_set_isempty(route->nexthops))
+                        (void) link_unmark_route(link, route, NULL);
 
-                r = route_convert(link->manager, route, &converted);
-                if (r < 0)
-                        continue;
-                if (r == 0) {
-                        if (route_get(NULL, link, route, &existing) >= 0)
-                                route_unmark(existing);
-                        continue;
+                else {
+                        RouteNextHop *nh;
+                        ORDERED_SET_FOREACH(nh, route->nexthops)
+                                (void) link_unmark_route(link, route, nh);
                 }
-
-                for (size_t i = 0; i < converted->n; i++)
-                        if (route_get(NULL, link, converted->routes[i], &existing) >= 0)
-                                route_unmark(existing);
         }
 
         link_unmark_wireguard_routes(link);
@@ -1014,124 +833,64 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat
 }
 
 static int route_setup_timer(Route *route, const struct rta_cacheinfo *cacheinfo) {
-        Manager *manager;
         int r;
 
         assert(route);
-        assert(route->manager || (route->link && route->link->manager));
-
-        manager = route->manager ?: route->link->manager;
-
-        if (route->lifetime_usec == USEC_INFINITY)
-                return 0;
 
         if (cacheinfo && cacheinfo->rta_expires != 0)
-                /* Assume that non-zero rta_expires means kernel will handle the route expiration. */
+                route->expiration_managed_by_kernel = true;
+
+        if (route->lifetime_usec == USEC_INFINITY || /* We do not request expiration for the route. */
+            route->expiration_managed_by_kernel) {   /* We have received nonzero expiration previously. The expiration is managed by the kernel. */
+                route->expire = sd_event_source_disable_unref(route->expire);
                 return 0;
+        }
 
+        Manager *manager = ASSERT_PTR(route->manager ?: ASSERT_PTR(route->link)->manager);
         r = event_reset_time(manager->event, &route->expire, CLOCK_BOOTTIME,
                              route->lifetime_usec, 0, route_expire_handler, route, 0, "route-expiration", true);
         if (r < 0)
-                return r;
+                return log_link_warning_errno(route->link, r, "Failed to configure expiration timer for route, ignoring: %m");
 
+        log_route_debug(route, "Configured expiration timer for", manager);
         return 1;
 }
 
-static int append_nexthop_one(const Link *link, const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) {
-        struct rtnexthop *rtnh;
-        struct rtattr *new_rta;
+int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg) {
         int r;
 
-        assert(route);
         assert(m);
-        assert(rta);
-        assert(*rta);
-
-        new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop)));
-        if (!new_rta)
-                return -ENOMEM;
-        *rta = new_rta;
-
-        rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
-        *rtnh = (struct rtnexthop) {
-                .rtnh_len = sizeof(*rtnh),
-                .rtnh_ifindex = m->ifindex > 0 ? m->ifindex : link->ifindex,
-                .rtnh_hops = m->weight,
-        };
-
-        (*rta)->rta_len += sizeof(struct rtnexthop);
-
-        if (route->family == m->gateway.family) {
-                r = rtattr_append_attribute(rta, RTA_GATEWAY, &m->gateway.address, FAMILY_ADDRESS_SIZE(m->gateway.family));
-                if (r < 0)
-                        goto clear;
-                rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
-                rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family));
-        } else {
-                r = rtattr_append_attribute(rta, RTA_VIA, &m->gateway, FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family));
-                if (r < 0)
-                        goto clear;
-                rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
-                rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family));
-        }
-
-        return 0;
-
-clear:
-        (*rta)->rta_len -= sizeof(struct rtnexthop);
-        return r;
-}
-
-static int append_nexthops(const Link *link, const Route *route, sd_netlink_message *req) {
-        _cleanup_free_ struct rtattr *rta = NULL;
-        struct rtnexthop *rtnh;
-        MultipathRoute *m;
-        size_t offset;
-        int r;
-
         assert(link);
+        assert(link->manager);
         assert(route);
-        assert(req);
-
-        if (ordered_set_isempty(route->multipath_routes))
-                return 0;
-
-        rta = new(struct rtattr, 1);
-        if (!rta)
-                return -ENOMEM;
-
-        *rta = (struct rtattr) {
-                .rta_type = RTA_MULTIPATH,
-                .rta_len = RTA_LENGTH(0),
-        };
-        offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta;
-
-        ORDERED_SET_FOREACH(m, route->multipath_routes) {
-                r = append_nexthop_one(link, route, m, &rta, offset);
-                if (r < 0)
-                        return r;
-
-                rtnh = (struct rtnexthop *)((uint8_t *) rta + offset);
-                offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta;
-        }
-
-        r = sd_netlink_message_append_data(req, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta));
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg) {
-        int r;
-
-        assert(m);
-        assert(link);
         assert(error_msg);
 
         r = sd_netlink_message_get_errno(m);
-        if (r < 0 && r != -EEXIST) {
-                log_link_message_warning_errno(link, m, r, "Could not set route");
+        if (r == -EEXIST) {
+                Route *existing;
+
+                if (route_get(link->manager, link, route, &existing) >= 0) {
+                        /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE
+                         * notification, so we need to update the timer here. */
+                        existing->lifetime_usec = route->lifetime_usec;
+                        (void) route_setup_timer(existing, NULL);
+
+                        /* This may be a bug in the kernel, but the MTU of an IPv6 route can be updated only
+                         * when the route has an expiration timer managed by the kernel (not by us).
+                         * See fib6_add_rt2node() in net/ipv6/ip6_fib.c of the kernel. */
+                        if (existing->family == AF_INET6 &&
+                            existing->expiration_managed_by_kernel) {
+                                r = route_metric_set(&existing->metric, RTAX_MTU, route_metric_get(&route->metric, RTAX_MTU));
+                                if (r < 0) {
+                                        log_oom();
+                                        link_enter_failed(link);
+                                        return 0;
+                                }
+                        }
+                }
+
+        } else if (r < 0) {
+                log_link_message_warning_errno(link, m, r, error_msg);
                 link_enter_failed(link);
                 return 0;
         }
@@ -1144,24 +903,17 @@ static int route_configure(const Route *route, uint32_t lifetime_sec, Link *link
         int r;
 
         assert(route);
-        assert(IN_SET(route->family, AF_INET, AF_INET6));
         assert(link);
         assert(link->manager);
-        assert(link->manager->rtnl);
-        assert(link->ifindex > 0);
         assert(req);
 
-        log_route_debug(route, "Configuring", link, link->manager);
+        log_route_debug(route, "Configuring", link->manager);
 
         r = sd_rtnl_message_new_route(link->manager->rtnl, &m, RTM_NEWROUTE, route->family, route->protocol);
         if (r < 0)
                 return r;
 
-        r = sd_rtnl_message_route_set_type(m, route->type);
-        if (r < 0)
-                return r;
-
-        r = route_set_netlink_message(route, m, link);
+        r = route_set_netlink_message(route, m);
         if (r < 0)
                 return r;
 
@@ -1171,21 +923,65 @@ static int route_configure(const Route *route, uint32_t lifetime_sec, Link *link
                         return r;
         }
 
-        /* metrics */
-        r = route_metric_set_netlink_message(&route->metric, m);
+        return request_call_netlink_async(link->manager->rtnl, m, req);
+}
+
+static int route_requeue_request(Request *req, Link *link, const Route *route) {
+        _unused_ _cleanup_(request_unrefp) Request *req_unref = NULL;
+        _cleanup_(route_freep) Route *tmp = NULL;
+        int r;
+
+        assert(req);
+        assert(link);
+        assert(link->manager);
+        assert(route);
+
+        /* It is not possible to adjust the Route object owned by Request, as it is used as a key to manage
+         * Request objects in the queue. Hence, we need to re-request with the updated Route object. */
+
+        if (!route_nexthops_needs_adjust(route))
+                return 0; /* The Route object does not need the adjustment. Continue with it. */
+
+        r = route_dup(route, NULL, &tmp);
         if (r < 0)
                 return r;
 
-        if (!ordered_set_isempty(route->multipath_routes)) {
-                assert(route->nexthop_id == 0);
-                assert(!in_addr_is_set(route->gw_family, &route->gw));
+        r = route_adjust_nexthops(tmp, link);
+        if (r <= 0)
+                return r;
 
-                r = append_nexthops(link, route, m);
-                if (r < 0)
-                        return r;
-        }
+        if (route_compare_func(route, tmp) == 0 && route->type == tmp->type)
+                return 0; /* No effective change?? That's OK. */
+
+        /* Avoid the request to be freed by request_detach(). */
+        req_unref = request_ref(req);
+
+        /* Detach the request from the queue, to make not the new request is deduped.
+         * Why this is necessary? IPv6 routes with different type may be handled as the same,
+         * As commented in route_adjust_nexthops(), we need to configure the adjusted type,
+         * otherwise we cannot remove the route on reconfigure or so. If we request the new Route object
+         * without detaching the current request, the new request is deduped, and the route is configured
+         * with unmodified type. */
+        request_detach(req);
+
+        /* Request the route with the adjusted Route object combined with the same other parameters. */
+        r = link_queue_request_full(link,
+                                    req->type,
+                                    tmp,
+                                    req->free_func,
+                                    req->hash_func,
+                                    req->compare_func,
+                                    req->process,
+                                    req->counter,
+                                    req->netlink_handler,
+                                    NULL);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Already queued?? That's OK. Maybe, [Route] section is effectively duplicated. */
 
-        return request_call_netlink_async(link->manager->rtnl, m, req);
+        TAKE_PTR(tmp);
+        return 1; /* New request is queued. Finish to process this request. */
 }
 
 static int route_is_ready_to_configure(const Route *route, Link *link) {
@@ -1194,61 +990,23 @@ static int route_is_ready_to_configure(const Route *route, Link *link) {
         assert(route);
         assert(link);
 
-        if (!link_is_ready_to_configure(link, false))
+        if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false))
                 return false;
 
         if (set_size(link->routes) >= routes_max())
                 return false;
 
-        if (route->nexthop_id > 0) {
-                struct nexthop_grp *nhg;
-                NextHop *nh;
-
-                r = nexthop_is_ready(link->manager, route->nexthop_id, &nh);
-                if (r <= 0)
-                        return r;
-
-                HASHMAP_FOREACH(nhg, nh->group) {
-                        r = nexthop_is_ready(link->manager, nhg->id, NULL);
-                        if (r <= 0)
-                                return r;
-                }
-        }
-
         if (in_addr_is_set(route->family, &route->prefsrc) > 0) {
                 r = manager_has_address(link->manager, route->family, &route->prefsrc);
                 if (r <= 0)
                         return r;
         }
 
-        if (!gateway_is_ready(link, FLAGS_SET(route->flags, RTNH_F_ONLINK), route->gw_family, &route->gw))
-                return false;
-
-        MultipathRoute *m;
-        ORDERED_SET_FOREACH(m, route->multipath_routes) {
-                union in_addr_union a = m->gateway.address;
-                Link *l = NULL;
-
-                r = multipath_route_get_link(link->manager, m, &l);
-                if (r < 0)
-                        return false;
-                if (r > 0) {
-                        if (!link_is_ready_to_configure(l, /* allow_unmanaged = */ true) ||
-                            !link_has_carrier(l))
-                                return false;
-
-                        m->ifindex = l->ifindex;
-                }
-
-                if (!gateway_is_ready(l ?: link, FLAGS_SET(route->flags, RTNH_F_ONLINK), m->gateway.family, &a))
-                        return false;
-        }
-
-        return true;
+        return route_nexthops_is_ready_to_configure(route, link->manager);
 }
 
 static int route_process_request(Request *req, Link *link, Route *route) {
-        _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
+        Route *existing;
         int r;
 
         assert(req);
@@ -1262,36 +1020,6 @@ static int route_process_request(Request *req, Link *link, Route *route) {
         if (r == 0)
                 return 0;
 
-        if (route_needs_convert(route)) {
-                r = route_convert(link->manager, route, &converted);
-                if (r < 0)
-                        return log_link_warning_errno(link, r, "Failed to convert route: %m");
-
-                assert(r > 0);
-                assert(converted);
-
-                for (size_t i = 0; i < converted->n; i++) {
-                        Route *existing;
-
-                        if (route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) < 0) {
-                                _cleanup_(route_freep) Route *tmp = NULL;
-
-                                r = route_dup(converted->routes[i], &tmp);
-                                if (r < 0)
-                                        return log_oom();
-
-                                r = route_add(link->manager, converted->links[i] ?: link, tmp);
-                                if (r < 0)
-                                        return log_link_warning_errno(link, r, "Failed to add route: %m");
-
-                                TAKE_PTR(tmp);
-                        } else {
-                                existing->source = converted->routes[i]->source;
-                                existing->provider = converted->routes[i]->provider;
-                        }
-                }
-        }
-
         usec_t now_usec;
         assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0);
         uint32_t sec = usec_to_sec(route->lifetime_usec, now_usec);
@@ -1299,109 +1027,98 @@ static int route_process_request(Request *req, Link *link, Route *route) {
                 log_link_debug(link, "Refuse to configure %s route with zero lifetime.",
                                network_config_source_to_string(route->source));
 
-                if (converted)
-                        for (size_t i = 0; i < converted->n; i++) {
-                                Route *existing;
+                route_cancel_requesting(route);
+                if (route_get(link->manager, link, route, &existing) >= 0)
+                        route_cancel_requesting(existing);
+                return 1;
+        }
 
-                                assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0);
-                                route_cancel_requesting(existing);
-                        }
-                else
-                        route_cancel_requesting(route);
+        r = route_requeue_request(req, link, route);
+        if (r != 0)
+                return r;
+
+        r = route_configure(route, sec, link, req);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to configure route: %m");
+
+        route_enter_configuring(route);
+        if (route_get(link->manager, link, route, &existing) >= 0)
+                route_enter_configuring(existing);
+        return 1;
+}
+
+static int link_request_route_one(
+                Link *link,
+                const Route *route,
+                const RouteNextHop *nh,
+                unsigned *message_counter,
+                route_netlink_handler_t netlink_handler) {
+
+        _cleanup_(route_freep) Route *tmp = NULL;
+        Route *existing = NULL;
+        int r;
+
+        assert(link);
+        assert(link->manager);
+        assert(route);
 
-                return 1;
-        }
+        r = route_dup(route, nh, &tmp);
+        if (r < 0)
+                return r;
 
-        r = route_configure(route, sec, link, req);
+        r = route_adjust_nexthops(tmp, link);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to configure route: %m");
+                return r;
 
-        if (converted)
-                for (size_t i = 0; i < converted->n; i++) {
-                        Route *existing;
+        if (route_get(link->manager, link, tmp, &existing) >= 0)
+                /* Copy state for logging below. */
+                tmp->state = existing->state;
 
-                        assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0);
-                        route_enter_configuring(existing);
-                }
-        else
-                route_enter_configuring(route);
+        log_route_debug(tmp, "Requesting", link->manager);
+        r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
+                                    tmp,
+                                    route_free,
+                                    route_hash_func,
+                                    route_compare_func,
+                                    route_process_request,
+                                    message_counter,
+                                    netlink_handler,
+                                    NULL);
+        if (r <= 0)
+                return r;
+
+        route_enter_requesting(tmp);
+        if (existing)
+                route_enter_requesting(existing);
 
+        TAKE_PTR(tmp);
         return 1;
 }
 
 int link_request_route(
                 Link *link,
-                Route *route,
-                bool consume_object,
+                const Route *route,
                 unsigned *message_counter,
-                route_netlink_handler_t netlink_handler,
-                Request **ret) {
+                route_netlink_handler_t netlink_handler) {
 
-        Route *existing = NULL;
         int r;
 
         assert(link);
         assert(link->manager);
         assert(route);
         assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN);
-        assert(!route_needs_convert(route));
-
-        (void) route_get(link->manager, link, route, &existing);
-
-        if (route->lifetime_usec == 0) {
-                if (consume_object)
-                        route_free(route);
 
-                /* The requested route is outdated. Let's remove it. */
-                return route_remove_and_drop(existing);
-        }
-
-        if (!existing) {
-                _cleanup_(route_freep) Route *tmp = NULL;
-
-                if (consume_object)
-                        tmp = route;
-                else {
-                        r = route_dup(route, &tmp);
-                        if (r < 0)
-                                return r;
-                }
+        if (route->family == AF_INET || route_type_is_reject(route) || ordered_set_isempty(route->nexthops))
+                return link_request_route_one(link, route, NULL, message_counter, netlink_handler);
 
-                r = route_add(link->manager, link, tmp);
+        RouteNextHop *nh;
+        ORDERED_SET_FOREACH(nh, route->nexthops) {
+                r = link_request_route_one(link, route, nh, message_counter, netlink_handler);
                 if (r < 0)
                         return r;
-
-                existing = TAKE_PTR(tmp);
-        } else {
-                existing->source = route->source;
-                existing->provider = route->provider;
-                existing->lifetime_usec = route->lifetime_usec;
-                if (consume_object)
-                        route_free(route);
-
-                if (existing->expire) {
-                        /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE
-                         * message, so we need to update the timer here. */
-                        r = route_setup_timer(existing, NULL);
-                        if (r < 0)
-                                log_link_warning_errno(link, r, "Failed to update expiration timer for route, ignoring: %m");
-                        if (r > 0)
-                                log_route_debug(existing, "Updated expiration timer for", link, link->manager);
-                }
         }
 
-        log_route_debug(existing, "Requesting", link, link->manager);
-        r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
-                                    existing, NULL,
-                                    route_hash_func,
-                                    route_compare_func,
-                                    route_process_request,
-                                    message_counter, netlink_handler, ret);
-        if (r <= 0)
-                return r;
-
-        route_enter_requesting(existing);
-        return 1;
+        return 0;
 }
 
 static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) {
@@ -1409,7 +1126,7 @@ static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request
 
         assert(link);
 
-        r = route_configure_handler_internal(rtnl, m, link, "Could not set route");
+        r = route_configure_handler_internal(rtnl, m, link, route, "Could not set route");
         if (r <= 0)
                 return r;
 
@@ -1422,22 +1139,6 @@ static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request
         return 1;
 }
 
-static int link_request_static_route(Link *link, Route *route) {
-        assert(link);
-        assert(link->manager);
-        assert(route);
-
-        if (!route_needs_convert(route))
-                return link_request_route(link, route, false, &link->static_route_messages,
-                                          static_route_handler, NULL);
-
-        log_route_debug(route, "Requesting", link, link->manager);
-        return link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
-                                       route, NULL, route_hash_func, route_compare_func,
-                                       route_process_request,
-                                       &link->static_route_messages, static_route_handler, NULL);
-}
-
 static int link_request_wireguard_routes(Link *link, bool only_ipv4) {
         NetDev *netdev;
         Route *route;
@@ -1457,7 +1158,7 @@ static int link_request_wireguard_routes(Link *link, bool only_ipv4) {
                 if (only_ipv4 && route->family != AF_INET)
                         continue;
 
-                r = link_request_static_route(link, route);
+                r = link_request_route(link, route, &link->static_route_messages, static_route_handler);
                 if (r < 0)
                         return r;
         }
@@ -1481,7 +1182,7 @@ int link_request_static_routes(Link *link, bool only_ipv4) {
                 if (only_ipv4 && route->family != AF_INET)
                         continue;
 
-                r = link_request_static_route(link, route);
+                r = link_request_route(link, route, &link->static_route_messages, static_route_handler);
                 if (r < 0)
                         return r;
         }
@@ -1502,94 +1203,95 @@ int link_request_static_routes(Link *link, bool only_ipv4) {
 }
 
 void route_cancel_request(Route *route, Link *link) {
-        Request req;
-
         assert(route);
-
-        link = route->link ?: link;
-
-        assert(link);
+        Manager *manager = ASSERT_PTR(route->manager ?:
+                                      route->link ? route->link->manager :
+                                      ASSERT_PTR(link)->manager);
 
         if (!route_is_requesting(route))
                 return;
 
-        req = (Request) {
-                .link = link,
-                .type = REQUEST_TYPE_ROUTE,
-                .userdata = route,
-                .hash_func = (hash_func_t) route_hash_func,
-                .compare_func = (compare_func_t) route_compare_func,
-        };
+        Request *req;
+        if (route_get_request(manager, route, &req) >= 0)
+                request_detach(req);
 
-        request_detach(link->manager, &req);
         route_cancel_requesting(route);
 }
 
 static int process_route_one(
                 Manager *manager,
-                Link *link,
                 uint16_t type,
                 Route *in,
                 const struct rta_cacheinfo *cacheinfo) {
 
         _cleanup_(route_freep) Route *tmp = in;
+        Request *req = NULL;
         Route *route = NULL;
-        bool update_dhcp4;
+        Link *link = NULL;
+        bool is_new = false, update_dhcp4;
         int r;
 
         assert(manager);
         assert(tmp);
         assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE));
 
-        /* link may be NULL. This consumes 'in'. */
+        (void) route_get(manager, NULL, tmp, &route);
+        (void) route_get_request(manager, tmp, &req);
+        (void) route_get_link(manager, tmp, &link);
 
         update_dhcp4 = link && tmp->family == AF_INET6 && tmp->dst_prefixlen == 0;
 
-        (void) route_get(manager, link, tmp, &route);
-
         switch (type) {
         case RTM_NEWROUTE:
-                if (route) {
-                        route->flags = tmp->flags;
-                        route_enter_configured(route);
-                        log_route_debug(route, "Received remembered", link, manager);
-
-                        r = route_setup_timer(route, cacheinfo);
-                        if (r < 0)
-                                log_link_warning_errno(link, r, "Failed to configure expiration timer for route, ignoring: %m");
-                        if (r > 0)
-                                log_route_debug(route, "Configured expiration timer for", link, manager);
-
-                } else if (!manager->manage_foreign_routes) {
-                        route_enter_configured(tmp);
-                        log_route_debug(tmp, "Ignoring received", link, manager);
-
-                } else {
-                        /* A route appeared that we did not request */
-                        route_enter_configured(tmp);
-                        log_route_debug(tmp, "Received new", link, manager);
-                        r = route_add(manager, link, tmp);
+                if (!route) {
+                        if (!manager->manage_foreign_routes && !(req && req->waiting_reply)) {
+                                route_enter_configured(tmp);
+                                log_route_debug(tmp, "Ignoring received", manager);
+                                return 0;
+                        }
+
+                        /* If we do not know the route, then save it. */
+                        r = route_add(manager, tmp);
                         if (r < 0) {
                                 log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m");
                                 return 0;
                         }
-                        TAKE_PTR(tmp);
+
+                        route = TAKE_PTR(tmp);
+                        is_new = true;
+
+                } else
+                        /* Update remembered route with the received notification. */
+                        route->nexthop.weight = tmp->nexthop.weight;
+
+                /* Also update information that cannot be obtained through netlink notification. */
+                if (req && req->waiting_reply) {
+                        Route *rt = ASSERT_PTR(req->userdata);
+
+                        route->source = rt->source;
+                        route->provider = rt->provider;
+                        route->lifetime_usec = rt->lifetime_usec;
                 }
 
+                route_enter_configured(route);
+                log_route_debug(route, is_new ? "Received new" : "Received remembered", manager);
+
+                (void) route_setup_timer(route, cacheinfo);
+
                 break;
 
         case RTM_DELROUTE:
                 if (route) {
                         route_enter_removed(route);
-                        if (route->state == 0) {
-                                log_route_debug(route, "Forgetting", link, manager);
-                                route_free(route);
-                        } else
-                                log_route_debug(route, "Removed", link, manager);
+                        log_route_debug(route, "Forgetting removed", manager);
+                        route_free(route);
                 } else
                         log_route_debug(tmp,
                                         manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received",
-                                        link, manager);
+                                        manager);
+
+                if (req)
+                        route_enter_removed(req->userdata);
 
                 break;
 
@@ -1609,15 +1311,7 @@ static int process_route_one(
 }
 
 int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
-        _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
         _cleanup_(route_freep) Route *tmp = NULL;
-        _cleanup_free_ void *rta_multipath = NULL;
-        struct rta_cacheinfo cacheinfo;
-        bool has_cacheinfo;
-        Link *link = NULL;
-        uint32_t ifindex;
-        uint16_t type;
-        size_t rta_len;
         int r;
 
         assert(rtnl);
@@ -1632,6 +1326,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma
                 return 0;
         }
 
+        uint16_t type;
         r = sd_netlink_message_get_type(message, &type);
         if (r < 0) {
                 log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
@@ -1641,115 +1336,84 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma
                 return 0;
         }
 
-        r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex);
-        if (r < 0 && r != -ENODATA) {
-                log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m");
-                return 0;
-        } else if (r >= 0) {
-                if (ifindex <= 0) {
-                        log_warning("rtnl: received route message with invalid ifindex %u, ignoring.", ifindex);
-                        return 0;
-                }
-
-                r = link_get_by_index(m, ifindex, &link);
-                if (r < 0) {
-                        /* when enumerating we might be out of sync, but we will
-                         * get the route again, so just ignore it */
-                        if (!m->enumerating)
-                                log_warning("rtnl: received route message for link (%u) we do not know about, ignoring", ifindex);
-                        return 0;
-                }
-        }
-
         r = route_new(&tmp);
         if (r < 0)
                 return log_oom();
 
+        /* rtmsg header */
         r = sd_rtnl_message_route_get_family(message, &tmp->family);
         if (r < 0) {
-                log_link_warning(link, "rtnl: received route message without family, ignoring");
+                log_warning_errno(r, "rtnl: received route message without family, ignoring: %m");
                 return 0;
         } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) {
-                log_link_debug(link, "rtnl: received route message with invalid family '%i', ignoring", tmp->family);
+                log_debug("rtnl: received route message with invalid family '%i', ignoring.", tmp->family);
                 return 0;
         }
 
-        r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol);
+        r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen);
         if (r < 0) {
-                log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m");
+                log_warning_errno(r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m");
                 return 0;
         }
 
-        r = sd_rtnl_message_route_get_flags(message, &tmp->flags);
+        r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen);
         if (r < 0) {
-                log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m");
+                log_warning_errno(r, "rtnl: received route message with invalid source prefixlen, ignoring: %m");
                 return 0;
         }
 
-        r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst);
-        if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: received route message without valid destination, ignoring: %m");
+        r = sd_rtnl_message_route_get_tos(message, &tmp->tos);
+        if (r < 0) {
+                log_warning_errno(r, "rtnl: received route message with invalid tos, ignoring: %m");
                 return 0;
         }
 
-        r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, tmp->family, &tmp->gw);
-        if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m");
+        r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol);
+        if (r < 0) {
+                log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m");
                 return 0;
-        } else if (r >= 0)
-                tmp->gw_family = tmp->family;
-        else if (tmp->family == AF_INET) {
-                RouteVia via;
-
-                r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via);
-                if (r < 0 && r != -ENODATA) {
-                        log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m");
-                        return 0;
-                } else if (r >= 0) {
-                        tmp->gw_family = via.family;
-                        tmp->gw = via.address;
-                }
         }
 
-        r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src);
-        if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: received route message without valid source, ignoring: %m");
+        r = sd_rtnl_message_route_get_scope(message, &tmp->scope);
+        if (r < 0) {
+                log_warning_errno(r, "rtnl: received route message with invalid scope, ignoring: %m");
                 return 0;
         }
 
-        r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc);
-        if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: received route message without valid preferred source, ignoring: %m");
+        r = sd_rtnl_message_route_get_type(message, &tmp->type);
+        if (r < 0) {
+                log_warning_errno(r, "rtnl: received route message with invalid type, ignoring: %m");
                 return 0;
         }
 
-        r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen);
+        r = sd_rtnl_message_route_get_flags(message, &tmp->flags);
         if (r < 0) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m");
+                log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m");
                 return 0;
         }
 
-        r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid source prefixlen, ignoring: %m");
+        /* attributes */
+        r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst);
+        if (r < 0 && r != -ENODATA) {
+                log_warning_errno(r, "rtnl: received route message without valid destination, ignoring: %m");
                 return 0;
         }
 
-        r = sd_rtnl_message_route_get_scope(message, &tmp->scope);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid scope, ignoring: %m");
+        r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src);
+        if (r < 0 && r != -ENODATA) {
+                log_warning_errno(r, "rtnl: received route message without valid source, ignoring: %m");
                 return 0;
         }
 
-        r = sd_rtnl_message_route_get_tos(message, &tmp->tos);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid tos, ignoring: %m");
+        r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority);
+        if (r < 0 && r != -ENODATA) {
+                log_warning_errno(r, "rtnl: received route message with invalid priority, ignoring: %m");
                 return 0;
         }
 
-        r = sd_rtnl_message_route_get_type(message, &tmp->type);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid type, ignoring: %m");
+        r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc);
+        if (r < 0 && r != -ENODATA) {
+                log_warning_errno(r, "rtnl: received route message without valid preferred source, ignoring: %m");
                 return 0;
         }
 
@@ -1762,69 +1426,48 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma
                         tmp->table = table;
         }
         if (r < 0) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid table, ignoring: %m");
+                log_warning_errno(r, "rtnl: received route message with invalid table, ignoring: %m");
                 return 0;
         }
 
-        r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority);
+        r = sd_netlink_message_read_u8(message, RTA_PREF, &tmp->pref);
         if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid priority, ignoring: %m");
+                log_warning_errno(r, "rtnl: received route message with invalid preference, ignoring: %m");
                 return 0;
         }
 
-        r = sd_netlink_message_read_u32(message, RTA_NH_ID, &tmp->nexthop_id);
-        if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: received route message with invalid nexthop id, ignoring: %m");
+        /* nexthops */
+        if (route_nexthops_read_netlink_message(tmp, message) < 0)
                 return 0;
-        }
 
         /* metrics */
         if (route_metric_read_netlink_message(&tmp->metric, message) < 0)
                 return 0;
 
-        r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta_multipath);
-        if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m");
-                return 0;
-        } else if (r >= 0) {
-                r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &tmp->multipath_routes);
-                if (r < 0) {
-                        log_link_warning_errno(link, r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m");
-                        return 0;
-                }
-        }
-
+        bool has_cacheinfo;
+        struct rta_cacheinfo cacheinfo;
         r = sd_netlink_message_read(message, RTA_CACHEINFO, sizeof(cacheinfo), &cacheinfo);
         if (r < 0 && r != -ENODATA) {
-                log_link_warning_errno(link, r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m");
+                log_warning_errno(r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m");
                 return 0;
         }
         has_cacheinfo = r >= 0;
 
-        /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's
-         * fib6_nh_init() in net/ipv6/route.c. However, we'd like to manage them by Manager. Hence, set
-         * link to NULL here. */
-        if (route_type_is_reject(tmp))
-                link = NULL;
+        if (tmp->family == AF_INET || ordered_set_isempty(tmp->nexthops))
+                return process_route_one(m, type, TAKE_PTR(tmp), has_cacheinfo ? &cacheinfo : NULL);
 
-        if (!route_needs_convert(tmp))
-                return process_route_one(m, link, type, TAKE_PTR(tmp), has_cacheinfo ? &cacheinfo : NULL);
-
-        r = route_convert(m, tmp, &converted);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "rtnl: failed to convert received route, ignoring: %m");
-                return 0;
-        }
+        RouteNextHop *nh;
+        ORDERED_SET_FOREACH(nh, tmp->nexthops) {
+                _cleanup_(route_freep) Route *dup = NULL;
 
-        assert(r > 0);
-        assert(converted);
+                r = route_dup(tmp, nh, &dup);
+                if (r < 0)
+                        return log_oom();
 
-        for (size_t i = 0; i < converted->n; i++)
-                (void) process_route_one(m,
-                                         converted->links[i] ?: link,
-                                         type,
-                                         TAKE_PTR(converted->routes[i]),
-                                         has_cacheinfo ? &cacheinfo : NULL);
+                r = process_route_one(m, type, TAKE_PTR(dup), has_cacheinfo ? &cacheinfo : NULL);
+                if (r < 0)
+                        return r;
+        }
 
         return 1;
 }
@@ -1892,89 +1535,6 @@ int network_add_default_route_on_device(Network *network) {
         return 0;
 }
 
-int config_parse_gateway(
-                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;
-        int r;
-
-        assert(filename);
-        assert(section);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (streq(section, "Network")) {
-                /* we are not in an Route section, so use line number instead */
-                r = route_new_static(network, filename, 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;
-                }
-        } else {
-                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->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")) {
-                        route->gateway_from_dhcp_or_ra = true;
-                        TAKE_PTR(route);
-                        return 0;
-                }
-
-                if (streq(rvalue, "_dhcp4")) {
-                        route->gw_family = AF_INET;
-                        route->gateway_from_dhcp_or_ra = true;
-                        TAKE_PTR(route);
-                        return 0;
-                }
-
-                if (streq(rvalue, "_ipv6ra")) {
-                        route->gw_family = AF_INET6;
-                        route->gateway_from_dhcp_or_ra = true;
-                        TAKE_PTR(route);
-                        return 0;
-                }
-        }
-
-        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;
-        }
-
-        route->gateway_from_dhcp_or_ra = false;
-        TAKE_PTR(route);
-        return 0;
-}
-
 int config_parse_preferred_src(
                 const char *unit,
                 const char *filename,
@@ -2207,50 +1767,6 @@ int config_parse_route_table(
         return 0;
 }
 
-int config_parse_route_gateway_onlink(
-                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;
-        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;
-        }
-
-        r = parse_boolean(rvalue);
-        if (r < 0) {
-                log_syntax(unit, LOG_WARNING, filename, line, r,
-                           "Could not parse %s=\"%s\", ignoring assignment: %m", lvalue, rvalue);
-                return 0;
-        }
-
-        route->gateway_onlink = r;
-
-        TAKE_PTR(route);
-        return 0;
-}
-
 int config_parse_ipv6_route_preference(
                 const char *unit,
                 const char *filename,
@@ -2368,140 +1884,56 @@ int config_parse_route_type(
         return 0;
 }
 
-static int route_section_verify(Route *route, Network *network) {
+int route_section_verify(Route *route) {
+        int r;
+
+        assert(route);
+        assert(route->section);
+
         if (section_is_invalid(route->section))
                 return -EINVAL;
 
         /* Currently, we do not support static route with finite lifetime. */
         assert(route->lifetime_usec == USEC_INFINITY);
 
-        if (route->gateway_from_dhcp_or_ra) {
-                if (route->gw_family == AF_UNSPEC) {
-                        /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */
-                        switch (route->family) {
-                        case AF_UNSPEC:
-                                log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
-                                            "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".",
-                                            route->section->filename, route->section->line);
-                                route->family = AF_INET;
-                                break;
-                        case AF_INET:
-                        case AF_INET6:
-                                log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
-                                            "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.",
-                                            route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra");
-                                break;
-                        default:
-                                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                         "%s: Invalid route family. Ignoring [Route] section from line %u.",
-                                                         route->section->filename, route->section->line);
-                        }
-                        route->gw_family = route->family;
-                }
-
-                if (route->gw_family == AF_INET && !FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4))
-                        return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                 "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. "
-                                                 "Ignoring [Route] section from line %u.",
-                                                 route->section->filename, route->section->line);
-
-                if (route->gw_family == AF_INET6 && !network->ipv6_accept_ra)
-                        return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                 "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. "
-                                                 "Ignoring [Route] section from line %u.",
-                                                 route->section->filename, route->section->line);
-        }
-
-        /* When only Gateway= is specified, assume the route family based on the Gateway address. */
-        if (route->family == AF_UNSPEC)
-                route->family = route->gw_family;
-
-        if (route->family == AF_UNSPEC) {
-                assert(route->section);
-
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: Route section without Gateway=, Destination=, Source=, "
-                                         "or PreferredSource= field configured. "
-                                         "Ignoring [Route] section from line %u.",
-                                         route->section->filename, route->section->line);
-        }
-
-        if (route->family == AF_INET6 && route->gw_family == AF_INET)
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: IPv4 gateway is configured for IPv6 route. "
-                                         "Ignoring [Route] section from line %u.",
-                                         route->section->filename, route->section->line);
+        r = route_section_verify_nexthops(route);
+        if (r < 0)
+                return r;
 
-        if (!route->table_set && network->vrf) {
-                route->table = VRF(network->vrf)->table;
+        /* table */
+        if (!route->table_set && route->network && route->network->vrf) {
+                route->table = VRF(route->network->vrf)->table;
                 route->table_set = true;
         }
 
         if (!route->table_set && IN_SET(route->type, RTN_LOCAL, RTN_BROADCAST, RTN_ANYCAST, RTN_NAT))
                 route->table = RT_TABLE_LOCAL;
 
-        if (!route->scope_set && route->family != AF_INET6) {
+        /* scope */
+        if (!route->scope_set && route->family == AF_INET) {
                 if (IN_SET(route->type, RTN_LOCAL, RTN_NAT))
                         route->scope = RT_SCOPE_HOST;
                 else if (IN_SET(route->type, RTN_BROADCAST, RTN_ANYCAST, RTN_MULTICAST))
                         route->scope = RT_SCOPE_LINK;
                 else if (IN_SET(route->type, RTN_UNICAST, RTN_UNSPEC) &&
                          !route->gateway_from_dhcp_or_ra &&
-                         !in_addr_is_set(route->gw_family, &route->gw) &&
-                         ordered_set_isempty(route->multipath_routes) &&
+                         !in_addr_is_set(route->nexthop.family, &route->nexthop.gw) &&
+                         ordered_set_isempty(route->nexthops) &&
                          route->nexthop_id == 0)
                         route->scope = RT_SCOPE_LINK;
         }
 
-        if (route->scope != RT_SCOPE_UNIVERSE && route->family == AF_INET6) {
-                log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename);
-                route->scope = RT_SCOPE_UNIVERSE;
-        }
-
-        if (route->family == AF_INET6 && route->priority == 0)
-                route->priority = IP6_RT_PRIO_USER;
+        /* IPv6 route */
+        if (route->family == AF_INET6) {
+                if (route->scope != RT_SCOPE_UNIVERSE) {
+                        log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename);
+                        route->scope = RT_SCOPE_UNIVERSE;
+                }
 
-        if (route->gateway_onlink < 0 && in_addr_is_set(route->gw_family, &route->gw) &&
-            ordered_hashmap_isempty(network->addresses_by_section)) {
-                /* If no address is configured, in most cases the gateway cannot be reachable.
-                 * TODO: we may need to improve the condition above. */
-                log_warning("%s: Gateway= without static address configured. "
-                            "Enabling GatewayOnLink= option.",
-                            network->filename);
-                route->gateway_onlink = true;
+                if (route->priority == 0)
+                        route->priority = IP6_RT_PRIO_USER;
         }
 
-        if (route->gateway_onlink >= 0)
-                SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink);
-
-        if (route->family == AF_INET6) {
-                MultipathRoute *m;
-
-                ORDERED_SET_FOREACH(m, route->multipath_routes)
-                        if (m->gateway.family == AF_INET)
-                                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                         "%s: IPv4 multipath route is specified for IPv6 route. "
-                                                         "Ignoring [Route] section from line %u.",
-                                                         route->section->filename, route->section->line);
-        }
-
-        if ((route->gateway_from_dhcp_or_ra ||
-             in_addr_is_set(route->gw_family, &route->gw)) &&
-            !ordered_set_isempty(route->multipath_routes))
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: Gateway= cannot be specified with MultiPathRoute=. "
-                                         "Ignoring [Route] section from line %u.",
-                                         route->section->filename, route->section->line);
-
-        if (route->nexthop_id > 0 &&
-            (route->gateway_from_dhcp_or_ra ||
-             in_addr_is_set(route->gw_family, &route->gw) ||
-             !ordered_set_isempty(route->multipath_routes)))
-                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
-                                         "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. "
-                                         "Ignoring [Route] section from line %u.",
-                                         route->section->filename, route->section->line);
-
         return 0;
 }
 
@@ -2511,6 +1943,6 @@ void network_drop_invalid_routes(Network *network) {
         assert(network);
 
         HASHMAP_FOREACH(route, network->routes_by_section)
-                if (route_section_verify(route, network) < 0)
+                if (route_section_verify(route) < 0)
                         route_free(route);
 }
index 33d1e643cc49e92ae6cf36045d8bbedf028a44d7..e86693e3152d437d8d64835fa7c3faf594b79c53 100644 (file)
@@ -17,6 +17,8 @@ typedef struct Manager Manager;
 typedef struct Network Network;
 typedef struct Request Request;
 typedef struct Route Route;
+typedef struct Wireguard Wireguard;
+
 typedef int (*route_netlink_handler_t)(
                 sd_netlink *rtnl,
                 sd_netlink_message *m,
@@ -28,60 +30,65 @@ struct Route {
         Link *link;
         Manager *manager;
         Network *network;
+        Wireguard *wireguard;
         ConfigSection *section;
         NetworkConfigSource source;
         NetworkConfigState state;
         union in_addr_union provider; /* DHCP server or router address */
 
+        /* rtmsg header */
         int family;
-        int gw_family;
-        uint32_t gw_weight;
-
         unsigned char dst_prefixlen;
-        unsigned char src_prefixlen;
-        unsigned char scope;
+        unsigned char src_prefixlen; /* IPv6 only */
+        unsigned char tos; /* IPv4 only */
         unsigned char protocol;  /* RTPROT_* */
-        unsigned char type; /* RTN_* */
-        unsigned char tos;
-        uint32_t priority; /* note that ip(8) calls this 'metric' */
-        uint32_t table;
-        unsigned char pref;
-        unsigned flags;
-        int gateway_onlink; /* Only used in conf parser and route_section_verify(). */
-        uint32_t nexthop_id;
+        unsigned char scope; /* IPv4 only */
+        unsigned char type; /* RTN_*, e.g. RTN_LOCAL, RTN_UNREACHABLE */
+        unsigned flags; /* e.g. RTNH_F_ONLINK */
+
+        /* attributes */
+        union in_addr_union dst; /* RTA_DST */
+        union in_addr_union src; /* RTA_SRC (IPv6 only) */
+        uint32_t priority; /* RTA_PRIORITY, note that ip(8) calls this 'metric' */
+        union in_addr_union prefsrc; /* RTA_PREFSRC */
+        uint32_t table; /* RTA_TABLE, also used in rtmsg header */
+        uint8_t pref; /* RTA_PREF (IPv6 only) */
+
+        /* nexthops */
+        RouteNextHop nexthop; /* RTA_OIF, and RTA_GATEWAY or RTA_VIA (IPv4 only) */
+        OrderedSet *nexthops; /* RTA_MULTIPATH */
+        uint32_t nexthop_id; /* RTA_NH_ID */
 
         /* metrics (RTA_METRICS) */
         RouteMetric metric;
 
+        /* This is an absolute point in time, and NOT a timespan/duration.
+         * Must be specified with clock_boottime_or_monotonic(). */
+        usec_t lifetime_usec; /* RTA_EXPIRES (IPv6 only) */
+        /* Used when kernel does not support RTA_EXPIRES attribute. */
+        sd_event_source *expire;
+        bool expiration_managed_by_kernel:1; /* RTA_CACHEINFO has nonzero rta_expires */
+
+        /* Only used by conf persers and route_section_verify(). */
         bool scope_set:1;
         bool table_set:1;
         bool priority_set:1;
         bool protocol_set:1;
         bool pref_set:1;
         bool gateway_from_dhcp_or_ra:1;
-
-        union in_addr_union gw;
-        union in_addr_union dst;
-        union in_addr_union src;
-        union in_addr_union prefsrc;
-        OrderedSet *multipath_routes;
-
-        /* This is an absolute point in time, and NOT a timespan/duration.
-         * Must be specified with clock_boottime_or_monotonic(). */
-        usec_t lifetime_usec;
-        /* Used when kernel does not support RTA_EXPIRES attribute. */
-        sd_event_source *expire;
+        int gateway_onlink;
 };
 
 extern const struct hash_ops route_hash_ops;
 
+Route* route_free(Route *route);
+DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_free);
+
 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);
+int route_dup(const Route *src, const RouteNextHop *nh, Route **ret);
 
-int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg);
+int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg);
 int route_remove(Route *route);
 int route_remove_and_drop(Route *route);
 
@@ -94,11 +101,9 @@ void link_foreignize_routes(Link *link);
 void route_cancel_request(Route *route, Link *link);
 int link_request_route(
                 Link *link,
-                Route *route,
-                bool consume_object,
+                const Route *route,
                 unsigned *message_counter,
-                route_netlink_handler_t netlink_handler,
-                Request **ret);
+                route_netlink_handler_t netlink_handler);
 int link_request_static_routes(Link *link, bool only_ipv4);
 
 int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m);
@@ -106,17 +111,16 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma
 int network_add_ipv4ll_route(Network *network);
 int network_add_default_route_on_device(Network *network);
 void network_drop_invalid_routes(Network *network);
+int route_section_verify(Route *route);
 
 DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(Route, route);
 void link_mark_routes(Link *link, NetworkConfigSource source);
 
-CONFIG_PARSER_PROTOTYPE(config_parse_gateway);
 CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src);
 CONFIG_PARSER_PROTOTYPE(config_parse_destination);
 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_gateway_onlink);
 CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_route_preference);
 CONFIG_PARSER_PROTOTYPE(config_parse_route_protocol);
 CONFIG_PARSER_PROTOTYPE(config_parse_route_type);
index 914e288aeccca924ffac3fc9a2dc957b94cb0d89..e8e154dd60671f42ad88062a82e82679fe30400a 100644 (file)
@@ -164,6 +164,8 @@ static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct
                 in_addr_hash_func(&rule->from, rule->family, state);
                 siphash24_compress_typesafe(rule->from_prefixlen, state);
 
+                siphash24_compress_boolean(rule->l3mdev, state);
+
                 in_addr_hash_func(&rule->to, rule->family, state);
                 siphash24_compress_typesafe(rule->to_prefixlen, state);
 
@@ -212,6 +214,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro
                 if (r != 0)
                         return r;
 
+                r = CMP(a->l3mdev, b->l3mdev);
+                if (r != 0)
+                        return r;
+
                 r = CMP(a->to_prefixlen, b->to_prefixlen);
                 if (r != 0)
                         return r;
@@ -476,19 +482,19 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule
                         return r;
         }
 
-        if (rule->table < 256) {
+        if (rule->l3mdev)
+                r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC);
+        else if (rule->table < 256)
                 r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table);
-                if (r < 0)
-                        return r;
-        } else {
+        else {
                 r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC);
                 if (r < 0)
                         return r;
 
                 r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table);
-                if (r < 0)
-                        return r;
         }
+        if (r < 0)
+                return r;
 
         if (rule->fwmark > 0) {
                 r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark);
@@ -544,6 +550,12 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule
                         return r;
         }
 
+        if (rule->l3mdev) {
+                r = sd_netlink_message_append_u8(m, FRA_L3MDEV, 1);
+                if (r < 0)
+                        return r;
+        }
+
         if (rule->suppress_prefixlen >= 0) {
                 r = sd_netlink_message_append_u32(m, FRA_SUPPRESS_PREFIXLEN, (uint32_t) rule->suppress_prefixlen);
                 if (r < 0)
@@ -853,20 +865,17 @@ int link_request_static_routing_policy_rules(Link *link) {
 
 static const RoutingPolicyRule kernel_rules[] = {
         { .family = AF_INET,  .priority_set = true, .priority = 0,     .table = RT_TABLE_LOCAL,   .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
+        { .family = AF_INET,  .priority_set = true, .priority = 1000,  .table = RT_TABLE_UNSPEC,  .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true },
         { .family = AF_INET,  .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN,    .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
         { .family = AF_INET,  .priority_set = true, .priority = 32767, .table = RT_TABLE_DEFAULT, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
         { .family = AF_INET6, .priority_set = true, .priority = 0,     .table = RT_TABLE_LOCAL,   .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
+        { .family = AF_INET6, .priority_set = true, .priority = 1000,  .table = RT_TABLE_UNSPEC,  .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true },
         { .family = AF_INET6, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN,    .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
 };
 
 static bool routing_policy_rule_is_created_by_kernel(const RoutingPolicyRule *rule) {
         assert(rule);
 
-        if (rule->l3mdev > 0)
-                /* Currently, [RoutingPolicyRule] does not explicitly set FRA_L3MDEV. So, if the flag
-                 * is set, it is safe to treat the rule as created by kernel. */
-                return true;
-
         for (size_t i = 0; i < ELEMENTSOF(kernel_rules); i++)
                 if (routing_policy_rule_equal(rule, &kernel_rules[i]))
                         return true;
@@ -1016,11 +1025,13 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Man
                 return 0;
         }
 
-        r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &tmp->l3mdev);
+        uint8_t l3mdev = 0;
+        r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &l3mdev);
         if (r < 0 && r != -ENODATA) {
                 log_warning_errno(r, "rtnl: could not get FRA_L3MDEV attribute, ignoring: %m");
                 return 0;
         }
+        tmp->l3mdev = l3mdev != 0;
 
         r = sd_netlink_message_read(message, FRA_SPORT_RANGE, sizeof(tmp->sport), &tmp->sport);
         if (r < 0 && r != -ENODATA) {
@@ -1502,6 +1513,44 @@ int config_parse_routing_policy_rule_invert(
         return 0;
 }
 
+int config_parse_routing_policy_rule_l3mdev(
+                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_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL;
+        Network *network = userdata;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = routing_policy_rule_new_static(network, filename, section_line, &n);
+        if (r < 0)
+                return log_oom();
+
+        r = parse_boolean(rvalue);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule l3mdev, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        n->l3mdev = r;
+
+        TAKE_PTR(n);
+        return 0;
+}
+
 int config_parse_routing_policy_rule_family(
                 const char *unit,
                 const char *filename,
@@ -1734,12 +1783,6 @@ static int routing_policy_rule_section_verify(RoutingPolicyRule *rule) {
                 /* rule->family can be AF_UNSPEC only when Family=both. */
         }
 
-        /* Currently, [RoutingPolicyRule] does not have a setting to set FRA_L3MDEV flag. Please also
-         * update routing_policy_rule_is_created_by_kernel() when a new setting which sets the flag is
-         * added in the future. */
-        if (rule->l3mdev > 0)
-                assert_not_reached();
-
         return 0;
 }
 
index b6ce2fa19c42ecffce97de0f74cf1ff51a873d9b..85016bbc8a3cc8e70a8f03f079e40050e7aa2408 100644 (file)
@@ -22,6 +22,7 @@ typedef struct RoutingPolicyRule {
 
         bool invert_rule;
         bool priority_set;
+        bool l3mdev; /* FRA_L3MDEV */
 
         uint8_t tos;
         uint8_t type;
@@ -29,7 +30,6 @@ typedef struct RoutingPolicyRule {
         uint8_t protocol; /* FRA_PROTOCOL */
         uint8_t to_prefixlen;
         uint8_t from_prefixlen;
-        uint8_t l3mdev; /* FRA_L3MDEV */
 
         uint32_t table;
         uint32_t fwmark;
@@ -80,6 +80,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_fwmark_mask);
 CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_prefix);
 CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_priority);
 CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_device);
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_l3mdev);
 CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range);
 CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol);
 CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert);
index 9f0e365c22b6ce1d3dbfa7e52f386b0ce73042e6..b79b898832b3b9dc1edc77163104edb54a4317a2 100644 (file)
@@ -630,11 +630,11 @@ static int link_save(Link *link) {
                 fprintf(f, "REQUIRED_FOR_ONLINE=%s\n",
                         yes_no(link->network->required_for_online));
 
-                LinkOperationalStateRange st = link->network->required_operstate_for_online;
-                fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n",
-                        strempty(link_operstate_to_string(st.min)),
-                        st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "",
-                        st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : "");
+                LinkOperationalStateRange st;
+                link_required_operstate_for_online(link, &st);
+
+                fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s:%s\n",
+                        link_operstate_to_string(st.min), link_operstate_to_string(st.max));
 
                 fprintf(f, "REQUIRED_FAMILY_FOR_ONLINE=%s\n",
                         link_required_address_family_to_string(link->network->required_family_for_online));
index 9d188c022e0bde093542580b6dddcdd50479e5d3..8fa0ede5c2aee0057f4ce06dc905ae1006bf4770 100644 (file)
@@ -179,6 +179,24 @@ static int link_set_ipv6_hop_limit(Link *link) {
         return sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit);
 }
 
+static int link_set_ipv6_retransmission_time(Link *link) {
+        usec_t retrans_time_ms;
+
+        assert(link);
+
+        if (!link_is_configured_for_family(link, AF_INET6))
+                return 0;
+
+        if (!timestamp_is_set(link->network->ipv6_retransmission_time))
+                return 0;
+
+        retrans_time_ms = DIV_ROUND_UP(link->network->ipv6_retransmission_time, USEC_PER_MSEC);
+         if (retrans_time_ms <= 0 || retrans_time_ms > UINT32_MAX)
+                return 0;
+
+        return sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", retrans_time_ms);
+}
+
 static int link_set_ipv6_proxy_ndp(Link *link) {
         bool v;
 
@@ -297,6 +315,10 @@ int link_set_sysctl(Link *link) {
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface, ignoring: %m");
 
+        r = link_set_ipv6_retransmission_time(link);
+        if (r < 0)
+                log_link_warning_errno(link, r, "Cannot set IPv6 retransmission time for interface, ignoring: %m");
+
         r = link_set_ipv6_proxy_ndp(link);
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m");
index 9213795f54a5a7d58b3d29e6b808c0cb2e2f7904..da5eed5d73b9e2299559771b4be1c24e1abdef24 100644 (file)
@@ -52,7 +52,7 @@ static bool manager_ignore_link(Manager *m, Link *link) {
         return false;
 }
 
-static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange s) {
+static int manager_link_is_online(Manager *m, Link *l, const LinkOperationalStateRange *state_range) {
         AddressFamily required_family;
         bool needs_ipv4;
         bool needs_ipv6;
@@ -91,25 +91,23 @@ static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange
                                             "link is being processed by networkd: setup state is %s.",
                                             l->state);
 
-        if (s.min < 0)
-                s.min = m->required_operstate.min >= 0 ? m->required_operstate.min
-                                                       : l->required_operstate.min;
+        const LinkOperationalStateRange *range;
+        FOREACH_POINTER(range, state_range, &m->required_operstate, &l->required_operstate)
+                if (operational_state_range_is_valid(range))
+                        break;
+        assert(range != POINTER_MAX);
 
-        if (s.max < 0)
-                s.max = m->required_operstate.max >= 0 ? m->required_operstate.max
-                                                       : l->required_operstate.max;
-
-        if (l->operational_state < s.min || l->operational_state > s.max)
+        if (!operational_state_is_in_range(l->operational_state, range))
                 return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
                                             "Operational state '%s' is not in range ['%s':'%s']",
                                             link_operstate_to_string(l->operational_state),
-                                            link_operstate_to_string(s.min), link_operstate_to_string(s.max));
+                                            link_operstate_to_string(range->min), link_operstate_to_string(range->max));
 
         required_family = m->required_family > 0 ? m->required_family : l->required_family;
         needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4;
         needs_ipv6 = required_family & ADDRESS_FAMILY_IPV6;
 
-        if (s.min < LINK_OPERSTATE_ROUTABLE) {
+        if (range->min < LINK_OPERSTATE_ROUTABLE) {
                 if (needs_ipv4 && l->ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED)
                         return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
                                                     "No routable or link-local IPv4 address is configured.");
@@ -155,7 +153,7 @@ bool manager_configured(Manager *m) {
                                 continue;
                         }
 
-                        r = manager_link_is_online(m, l, *range);
+                        r = manager_link_is_online(m, l, range);
                         if (r <= 0 && !m->any)
                                 return false;
                         if (r > 0 && m->any)
@@ -175,9 +173,7 @@ bool manager_configured(Manager *m) {
                         continue;
                 }
 
-                r = manager_link_is_online(m, l,
-                                           (LinkOperationalStateRange) { _LINK_OPERSTATE_INVALID,
-                                                                         _LINK_OPERSTATE_INVALID });
+                r = manager_link_is_online(m, l, /* state_range = */ NULL);
                 if (r < 0 && !m->any) /* Unlike the above loop, unmanaged interfaces are ignored here. */
                         return false;
                 if (r > 0) {
index 5328bba2d8c0a53f18354153454d53cb33a1bab7..544c360eddcf980355c45c3e0d392cf21a08bb4d 100644 (file)
@@ -19,7 +19,7 @@ static bool arg_quiet = false;
 static usec_t arg_timeout = 120 * USEC_PER_SEC;
 static Hashmap *arg_interfaces = NULL;
 static char **arg_ignore = NULL;
-static LinkOperationalStateRange arg_required_operstate = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID };
+static LinkOperationalStateRange arg_required_operstate = LINK_OPERSTATE_RANGE_INVALID;
 static AddressFamily arg_required_family = ADDRESS_FAMILY_NO;
 static bool arg_any = false;
 
@@ -71,12 +71,11 @@ static int parse_interface_with_operstate_range(const char *str) {
         if (p) {
                 r = parse_operational_state_range(p + 1, range);
                 if (r < 0)
-                         log_error_errno(r, "Invalid operational state range '%s'", p + 1);
+                        return log_error_errno(r, "Invalid operational state range: %s", p + 1);
 
                 ifname = strndup(optarg, p - optarg);
         } else {
-                range->min = _LINK_OPERSTATE_INVALID;
-                range->max = _LINK_OPERSTATE_INVALID;
+                *range = LINK_OPERSTATE_RANGE_INVALID;
                 ifname = strdup(str);
         }
         if (!ifname)
@@ -84,18 +83,19 @@ static int parse_interface_with_operstate_range(const char *str) {
 
         if (!ifname_valid(ifname))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Invalid interface name '%s'", ifname);
+                                       "Invalid interface name: %s", ifname);
 
-        r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, TAKE_PTR(range));
+        r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, range);
         if (r == -ENOMEM)
                 return log_oom();
         if (r < 0)
                 return log_error_errno(r, "Failed to store interface name: %m");
         if (r == 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Interface name %s is already specified", ifname);
+                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+                                       "Interface name %s is already specified.", ifname);
 
         TAKE_PTR(ifname);
+        TAKE_PTR(range);
         return 0;
 }
 
@@ -154,17 +154,11 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
-                case 'o': {
-                        LinkOperationalStateRange range;
-
-                        r = parse_operational_state_range(optarg, &range);
+                case 'o':
+                        r = parse_operational_state_range(optarg, &arg_required_operstate);
                         if (r < 0)
                                 return log_error_errno(r, "Invalid operational state range '%s'", optarg);
-
-                        arg_required_operstate = range;
-
                         break;
-                }
 
                 case '4':
                         arg_required_family |= ADDRESS_FAMILY_IPV4;
index c7e1a9253c557755e007bda9d060f566271af789..018e7a37e23cc37b89369f68ffdab61759f853ac 100644 (file)
@@ -286,7 +286,7 @@ int bind_user_prepare(
                 if (!sd)
                         return log_oom();
 
-                cm = reallocarray(*custom_mounts, sizeof(CustomMount), *n_custom_mounts + 1);
+                cm = reallocarray(*custom_mounts, *n_custom_mounts + 1, sizeof(CustomMount));
                 if (!cm)
                         return log_oom();
 
index 470f477f22c1d038742ca8390bfbc8f60ed5f1b9..24771076b44108d99c2e397500b4021c4f9b9757 100644 (file)
@@ -1388,17 +1388,30 @@ int wipe_fully_visible_fs(int mntns_fd) {
         _cleanup_close_ int orig_mntns_fd = -EBADF;
         int r, rr;
 
-        r = namespace_open(0, NULL, &orig_mntns_fd, NULL, NULL, NULL);
+        r = namespace_open(0,
+                           /* ret_pidns_fd = */ NULL,
+                           &orig_mntns_fd,
+                           /* ret_netns_fd = */ NULL,
+                           /* ret_userns_fd = */ NULL,
+                           /* ret_root_fd = */ NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to pin originating mount namespace: %m");
 
-        r = namespace_enter(-EBADF, mntns_fd, -EBADF, -EBADF, -EBADF);
+        r = namespace_enter(/* pidns_fd = */ -EBADF,
+                            mntns_fd,
+                            /* netns_fd = */ -EBADF,
+                            /* userns_fd = */ -EBADF,
+                            /* root_fd = */ -EBADF);
         if (r < 0)
                 return log_error_errno(r, "Failed to enter mount namespace: %m");
 
         rr = do_wipe_fully_visible_fs();
 
-        r = namespace_enter(-EBADF, orig_mntns_fd, -EBADF, -EBADF, -EBADF);
+        r = namespace_enter(/* pidns_fd = */ -EBADF,
+                            orig_mntns_fd,
+                            /* netns_fd = */ -EBADF,
+                            /* userns_fd = */ -EBADF,
+                            /* root_fd = */ -EBADF);
         if (r < 0)
                 return log_error_errno(r, "Failed to enter original mount namespace: %m");
 
index 369d8742b60f0be1da0a9041d2ea29b4171ed0ea..151bb1d69a0e433177a21c1cd905cbaa507d7fbf 100644 (file)
@@ -2,22 +2,31 @@
 
 #include <net/if.h>
 #include <linux/if.h>
+#include <linux/nl80211.h>
 #include <linux/veth.h>
 #include <sys/file.h>
+#include <sys/mount.h>
 
 #include "sd-device.h"
 #include "sd-id128.h"
 #include "sd-netlink.h"
 
 #include "alloc-util.h"
+#include "device-private.h"
+#include "device-util.h"
 #include "ether-addr-util.h"
+#include "fd-util.h"
 #include "hexdecoct.h"
 #include "lock-util.h"
 #include "missing_network.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "namespace-util.h"
 #include "netif-naming-scheme.h"
 #include "netlink-util.h"
 #include "nspawn-network.h"
 #include "parse-util.h"
+#include "process-util.h"
 #include "siphash24.h"
 #include "socket-netlink.h"
 #include "socket-util.h"
@@ -503,42 +512,306 @@ int test_network_interfaces_initialized(char **iface_pairs) {
         return 0;
 }
 
-int move_network_interfaces(int netns_fd, char **iface_pairs) {
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+static int netns_child_begin(int netns_fd, int *ret_original_netns_fd) {
+        _cleanup_close_ int original_netns_fd = -EBADF;
         int r;
 
-        if (strv_isempty(iface_pairs))
+        assert(netns_fd >= 0);
+
+        if (ret_original_netns_fd) {
+                r = namespace_open(0,
+                                   /* ret_pidns_fd = */ NULL,
+                                   /* ret_mntns_fd = */ NULL,
+                                   &original_netns_fd,
+                                   /* ret_userns_fd = */ NULL,
+                                   /* ret_root_fd = */ NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to open original network namespace: %m");
+        }
+
+        r = namespace_enter(/* pidns_fd = */ -EBADF,
+                            /* mntns_fd = */ -EBADF,
+                            netns_fd,
+                            /* userns_fd = */ -EBADF,
+                            /* root_fd = */ -EBADF);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enter child network namespace: %m");
+
+        r = umount_recursive("/sys/", /* flags = */ 0);
+        if (r < 0)
+                log_debug_errno(r, "Failed to unmount directories below /sys/, ignoring: %m");
+
+        (void) mkdir_p("/sys/", 0755);
+
+        /* Populate new sysfs instance associated with the client netns, to make sd_device usable. */
+        r = mount_nofollow_verbose(LOG_ERR, "sysfs", "/sys/", "sysfs",
+                                   MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, /* opts = */ NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to mount sysfs on /sys/: %m");
+
+        /* udev_avaliable() might be called previously and the result may be cached.
+         * Now, we (re-)mount sysfs. Hence, we need to reset the cache. */
+        reset_cached_udev_availability();
+
+        if (ret_original_netns_fd)
+                *ret_original_netns_fd = TAKE_FD(original_netns_fd);
+
+        return 0;
+}
+
+static int netns_fork_and_wait(int netns_fd, int *ret_original_netns_fd) {
+        int r;
+
+        assert(netns_fd >= 0);
+
+        r = safe_fork("(sd-netns)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to fork process (sd-netns): %m");
+        if (r == 0) {
+                if (netns_child_begin(netns_fd, ret_original_netns_fd) < 0)
+                        _exit(EXIT_FAILURE);
+
                 return 0;
+        }
 
-        r = sd_netlink_open(&rtnl);
+        if (ret_original_netns_fd)
+                *ret_original_netns_fd = -EBADF;
+
+        return 1;
+}
+
+static int needs_rename(sd_netlink **rtnl, sd_device *dev, const char *name) {
+        int r;
+
+        assert(rtnl);
+        assert(dev);
+        assert(name);
+
+        const char *ifname;
+        r = sd_device_get_sysname(dev, &ifname);
         if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
+                return r;
 
-        STRV_FOREACH_PAIR(i, b, iface_pairs) {
-                _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-                int ifi;
+        if (streq(name, ifname))
+                return false;
 
-                ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
-                if (ifi < 0)
-                        return ifi;
+        int ifindex;
+        r = sd_device_get_ifindex(dev, &ifindex);
+        if (r < 0)
+                return r;
+
+        _cleanup_strv_free_ char **altnames = NULL;
+        r = rtnl_get_link_alternative_names(rtnl, ifindex, &altnames);
+        if (r == -EOPNOTSUPP)
+                return true; /* alternative interface name is not supported, hence the name is not
+                              * assigned to the interface. */
+        if (r < 0)
+                return r;
 
-                r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi);
+        return !strv_contains(altnames, name);
+}
+
+static int move_wlan_interface_impl(sd_netlink **genl, int netns_fd, sd_device *dev) {
+        _cleanup_(sd_netlink_unrefp) sd_netlink *our_genl = NULL;
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+        int r;
+
+        assert(netns_fd >= 0);
+        assert(dev);
+
+        if (!genl)
+                genl = &our_genl;
+        if (!*genl) {
+                r = sd_genl_socket_open(genl);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to allocate netlink message: %m");
+                        return log_error_errno(r, "Failed to connect to generic netlink: %m");
+        }
+
+        r = sd_genl_message_new(*genl, NL80211_GENL_NAME, NL80211_CMD_SET_WIPHY_NETNS, &m);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to allocate netlink message: %m");
+
+        uint32_t phy_index;
+        r = device_get_sysattr_u32(dev, "phy80211/index", &phy_index);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to get phy index: %m");
+
+        r = sd_netlink_message_append_u32(m, NL80211_ATTR_WIPHY, phy_index);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to append phy index to netlink message: %m");
+
+        r = sd_netlink_message_append_u32(m, NL80211_ATTR_NETNS_FD, netns_fd);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to append namespace fd to netlink message: %m");
+
+        r = sd_netlink_call(*genl, m, 0, NULL);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to move interface to namespace: %m");
+
+        return 0;
+}
+
+static int move_wlan_interface_one(
+                        sd_netlink **rtnl,
+                        sd_netlink **genl,
+                        int *temp_netns_fd,
+                        int netns_fd,
+                        sd_device *dev,
+                        const char *name) {
+
+        int r;
 
-                r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd);
+        assert(rtnl);
+        assert(genl);
+        assert(temp_netns_fd);
+        assert(netns_fd >= 0);
+        assert(dev);
+        assert(name);
+
+        r = needs_rename(rtnl, dev, name);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to determine if the interface should be renamed to '%s': %m", name);
+        if (r == 0)
+                return move_wlan_interface_impl(genl, netns_fd, dev);
+
+        /* The command NL80211_CMD_SET_WIPHY_NETNS takes phy instead of network interface, and does not take
+         * an interface name in the passed network namespace. Hence, we need to move the phy and interface to
+         * a temporary network namespace, rename the interface in it, and move them to the requested netns. */
+
+        if (*temp_netns_fd < 0) {
+                r = netns_acquire();
                 if (r < 0)
-                        return log_error_errno(r, "Failed to append namespace fd to netlink message: %m");
+                        return log_error_errno(r, "Failed to acquire new network namespace: %m");
+                *temp_netns_fd = r;
+        }
+
+        r = move_wlan_interface_impl(genl, *temp_netns_fd, dev);
+        if (r < 0)
+                return r;
+
+        const char *sysname;
+        r = sd_device_get_sysname(dev, &sysname);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to get interface name: %m");
 
-                if (!streq(*b, *i)) {
-                        r = sd_netlink_message_append_string(m, IFLA_IFNAME, *b);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to add netlink interface name: %m");
+        r = netns_fork_and_wait(*temp_netns_fd, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to fork process (nspawn-rename-wlan): %m");
+        if (r == 0) {
+                _cleanup_(sd_device_unrefp) sd_device *temp_dev = NULL;
+
+                r = rtnl_rename_link(NULL, sysname, name);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to rename network interface '%s' to '%s': %m", sysname, name);
+                        goto finalize;
                 }
 
-                r = sd_netlink_call(rtnl, m, 0, NULL);
+                r = sd_device_new_from_ifname(&temp_dev, name);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to acquire device '%s': %m", name);
+                        goto finalize;
+                }
+
+                r = move_wlan_interface_impl(NULL, netns_fd, temp_dev);
+
+        finalize:
+                _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+        }
+
+        return 0;
+}
+
+static int move_network_interface_one(sd_netlink **rtnl, int netns_fd, sd_device *dev, const char *name) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+        int r;
+
+        assert(rtnl);
+        assert(netns_fd >= 0);
+        assert(dev);
+        assert(name);
+
+        if (!*rtnl) {
+                r = sd_netlink_open(rtnl);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to connect to rtnetlink: %m");
+        }
+
+        int ifindex;
+        r = sd_device_get_ifindex(dev, &ifindex);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to get ifindex: %m");
+
+        r = sd_rtnl_message_new_link(*rtnl, &m, RTM_SETLINK, ifindex);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to allocate netlink message: %m");
+
+        r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to append namespace fd to netlink message: %m");
+
+        r = needs_rename(rtnl, dev, name);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to determine if the interface should be renamed to '%s': %m", name);
+        if (r > 0) {
+                r = sd_netlink_message_append_string(m, IFLA_IFNAME, name);
+                if (r < 0)
+                        return log_device_error_errno(dev, r, "Failed to add netlink interface name: %m");
+        }
+
+        r = sd_netlink_call(*rtnl, m, 0, NULL);
+        if (r < 0)
+                return log_device_error_errno(dev, r, "Failed to move interface to namespace: %m");
+
+        return 0;
+}
+
+int move_network_interfaces(int netns_fd, char **iface_pairs) {
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL, *genl = NULL;
+        _cleanup_close_ int temp_netns_fd = -EBADF;
+        int r;
+
+        assert(netns_fd >= 0);
+
+        if (strv_isempty(iface_pairs))
+                return 0;
+
+        STRV_FOREACH_PAIR(from, to, iface_pairs) {
+                _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+
+                r = sd_device_new_from_ifname(&dev, *from);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i);
+                        return log_error_errno(r, "Unknown interface name %s: %m", *from);
+
+                if (device_is_devtype(dev, "wlan"))
+                        r = move_wlan_interface_one(&rtnl, &genl, &temp_netns_fd, netns_fd, dev, *to);
+                else
+                        r = move_network_interface_one(&rtnl, netns_fd, dev, *to);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int move_back_network_interfaces(int child_netns_fd, char **interface_pairs) {
+        _cleanup_close_ int parent_netns_fd = -EBADF;
+        int r;
+
+        assert(child_netns_fd >= 0);
+
+        if (strv_isempty(interface_pairs))
+                return 0;
+
+        r = netns_fork_and_wait(child_netns_fd, &parent_netns_fd);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* Reverse network interfaces pair list so that interfaces get their initial name back.
+                 * This is about ensuring interfaces get their old name back when being moved back. */
+                interface_pairs = strv_reverse(interface_pairs);
+
+                r = move_network_interfaces(parent_netns_fd, interface_pairs);
+                _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
         }
 
         return 0;
index a785f8ea42ffc9f81c9c88f626481eef4fe685bb..9729b6e37e799fd8a75954c451233ce8e60139bd 100644 (file)
@@ -19,6 +19,7 @@ int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs);
 int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs);
 
 int move_network_interfaces(int netns_fd, char **iface_pairs);
+int move_back_network_interfaces(int child_netns_fd, char **interface_pairs);
 
 int veth_extra_parse(char ***l, const char *p);
 
index 62cf331a4a9c1e521cb0705b7540e63c1ec56152..8926e275a38be6300d9c83052ea05918a23d8708 100644 (file)
@@ -1588,7 +1588,7 @@ static int oci_sysctl(const char *name, JsonVariant *v, JsonDispatchFlags flags,
                         return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
                                         "sysctl key invalid, refusing: %s", k);
 
-                r = strv_extend_strv(&s->sysctl, STRV_MAKE(k, m), false);
+                r = strv_extend_many(&s->sysctl, k, m);
                 if (r < 0)
                         return log_oom();
         }
index 7ec9889870cefeafcd63352d1f856c005e44567d..89ef0e4daaa040385e45104918108de735a78a4c 100644 (file)
@@ -3781,7 +3781,12 @@ static int outer_child(
                 return r;
 
         if (arg_userns_mode != USER_NAMESPACE_NO) {
-                r = namespace_open(0, NULL, &mntns_fd, NULL, NULL, NULL);
+                r = namespace_open(0,
+                                   /* ret_pidns_fd = */ NULL,
+                                   &mntns_fd,
+                                   /* ret_netns_fd = */ NULL,
+                                   /* ret_userns_fd = */ NULL,
+                                   /* ret_root_fd = */ NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to pin outer mount namespace: %m");
 
@@ -4130,7 +4135,11 @@ static int outer_child(
                  * user if user namespaces are turned on. */
 
                 if (arg_network_namespace_path) {
-                        r = namespace_enter(-1, -1, netns_fd, -1, -1);
+                        r = namespace_enter(/* pidns_fd = */ -EBADF,
+                                            /* mntns_fd = */ -EBADF,
+                                            netns_fd,
+                                            /* userns_fd = */ -EBADF,
+                                            /* root_fd = */ -EBADF);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to join network namespace: %m");
                 }
@@ -4202,13 +4211,13 @@ static int uid_shift_pick(uid_t *shift, LockFile *ret_lock_file) {
                         return r;
 
                 /* Make some superficial checks whether the range is currently known in the user database */
-                if (getpwuid(candidate))
+                if (getpwuid_malloc(candidate, /* ret= */ NULL) >= 0)
                         goto next;
-                if (getpwuid(candidate + UINT32_C(0xFFFE)))
+                if (getpwuid_malloc(candidate + UINT32_C(0xFFFE), /* ret= */ NULL) >= 0)
                         goto next;
-                if (getgrgid(candidate))
+                if (getgrgid_malloc(candidate, /* ret= */ NULL) >= 0)
                         goto next;
-                if (getgrgid(candidate + UINT32_C(0xFFFE)))
+                if (getgrgid_malloc(candidate + UINT32_C(0xFFFE), /* ret= */ NULL) >= 0)
                         goto next;
 
                 *ret_lock_file = lf;
@@ -5078,7 +5087,12 @@ static int run_container(
                 if (child_netns_fd < 0) {
                         /* Make sure we have an open file descriptor to the child's network
                          * namespace so it stays alive even if the child exits. */
-                        r = namespace_open(*pid, NULL, NULL, &child_netns_fd, NULL, NULL);
+                        r = namespace_open(*pid,
+                                           /* ret_pidns_fd = */ NULL,
+                                           /* ret_mntns_fd = */ NULL,
+                                           &child_netns_fd,
+                                           /* ret_userns_fd = */ NULL,
+                                           /* ret_root_fd = */ NULL);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to open child network namespace: %m");
                 }
@@ -5363,37 +5377,9 @@ static int run_container(
         fd_kmsg_fifo = safe_close(fd_kmsg_fifo);
 
         if (arg_private_network) {
-                /* Move network interfaces back to the parent network namespace. We use `safe_fork`
-                 * to avoid having to move the parent to the child network namespace. */
-                r = safe_fork(NULL, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL);
+                r = move_back_network_interfaces(child_netns_fd, arg_network_interfaces);
                 if (r < 0)
                         return r;
-
-                if (r == 0) {
-                        _cleanup_close_ int parent_netns_fd = -EBADF;
-
-                        r = namespace_open(getpid_cached(), NULL, NULL, &parent_netns_fd, NULL, NULL);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to open parent network namespace: %m");
-                                _exit(EXIT_FAILURE);
-                        }
-
-                        r = namespace_enter(-1, -1, child_netns_fd, -1, -1);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to enter child network namespace: %m");
-                                _exit(EXIT_FAILURE);
-                        }
-
-                        /* Reverse network interfaces pair list so that interfaces get their initial name back.
-                         * This is about ensuring interfaces get their old name back when being moved back. */
-                        arg_network_interfaces = strv_reverse(arg_network_interfaces);
-
-                        r = move_network_interfaces(parent_netns_fd, arg_network_interfaces);
-                        if (r < 0)
-                                log_error_errno(r, "Failed to move network interfaces back to parent network namespace: %m");
-
-                        _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
-                }
         }
 
         r = wait_for_container(TAKE_PID(*pid), &container_status);
@@ -5957,9 +5943,16 @@ static int run(int argc, char *argv[]) {
         if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */
                 arg_quiet = true;
 
-        if (!arg_quiet)
-                log_info("Spawning container %s on %s.\nPress Ctrl-] three times within 1s to kill container.",
-                         arg_machine, arg_image ?: arg_directory);
+        if (!arg_quiet) {
+                const char *t = arg_image ?: arg_directory;
+                _cleanup_free_ char *u = NULL;
+                (void) terminal_urlify_path(t, t, &u);
+
+                log_info("%s %sSpawning container %s on %s.%s\n"
+                         "%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
+                         special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal(),
+                         special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+        }
 
         assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0);
 
index 822ad4f6221eae37674cf0825abc8acdfacd3ee0..ce8c0640043eab653b68e833b3735cbd2ed17004 100644 (file)
@@ -261,7 +261,7 @@ enum nss_status _nss_resolve_gethostbyname4_r(
          * configuration can distinguish such executed but negative replies from complete failure to
          * talk to resolved). */
         const char *error_id;
-        r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id, NULL);
+        r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id);
         if (r < 0)
                 goto fail;
         if (!isempty(error_id)) {
@@ -423,7 +423,7 @@ enum nss_status _nss_resolve_gethostbyname3_r(
                 goto fail;
 
         const char *error_id;
-        r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id, NULL);
+        r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id);
         if (r < 0)
                 goto fail;
         if (!isempty(error_id)) {
@@ -641,7 +641,7 @@ enum nss_status _nss_resolve_gethostbyaddr2_r(
                 goto fail;
 
         const char* error_id;
-        r = varlink_call(link, "io.systemd.Resolve.ResolveAddress", cparams, &rparams, &error_id, NULL);
+        r = varlink_call(link, "io.systemd.Resolve.ResolveAddress", cparams, &rparams, &error_id);
         if (r < 0)
                 goto fail;
         if (!isempty(error_id)) {
index 9eeef91a4cee422c9f8f2667630c7ecbbdc21f36..e8c902c927ce45e251824f6cc5c0f81a9b3faef9 100644 (file)
@@ -2768,8 +2768,8 @@ static int verb_lock_secureboot_policy(int argc, char *argv[], void *userdata) {
                 int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */
         } variables[] = {
                 { EFI_VENDOR_GLOBAL,   "SecureBoot", 0 },
-                { EFI_VENDOR_GLOBAL,   "PK",         0 },
-                { EFI_VENDOR_GLOBAL,   "KEK",        0 },
+                { EFI_VENDOR_GLOBAL,   "PK",         1 },
+                { EFI_VENDOR_GLOBAL,   "KEK",        1 },
                 { EFI_VENDOR_DATABASE, "db",         1 },
                 { EFI_VENDOR_DATABASE, "dbx",        1 },
                 { EFI_VENDOR_DATABASE, "dbt",       -1 },
index d4b448a6274ab2f892716e24da75a1f10eb435fd..6054f0f17f8de20f2e8c09795309166169e1c0a7 100644 (file)
@@ -2,6 +2,8 @@
 
 #include <linux/loop.h>
 
+#include "sd-messages.h"
+
 #include "bus-common-errors.h"
 #include "bus-error.h"
 #include "bus-locator.h"
@@ -1430,6 +1432,78 @@ static bool prefix_matches_compatible(char **matches, char **valid_prefixes) {
         return true;
 }
 
+static void log_portable_verb(
+                const char *verb,
+                const char *message_id,
+                const char *image_path,
+                OrderedHashmap *extension_images,
+                char **extension_image_paths,
+                PortableFlags flags) {
+
+        _cleanup_free_ char *root_base_name = NULL, *extensions_joined = NULL;
+        _cleanup_strv_free_ char **extension_base_names = NULL;
+        Image *ext;
+        int r;
+
+        assert(verb);
+        assert(message_id);
+        assert(image_path);
+        assert(!extension_images || !extension_image_paths);
+
+        /* Use the same structured metadata as it is attached to units via LogExtraFields=. The main image
+         * is logged as PORTABLE_ROOT= and extensions, if any, as individual PORTABLE_EXTENSION= fields. */
+
+        r = path_extract_filename(image_path, &root_base_name);
+        if (r < 0)
+                log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", image_path);
+
+        ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+                _cleanup_free_ char *extension_base_name = NULL;
+
+                r = path_extract_filename(ext->path, &extension_base_name);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", ext->path);
+                        continue;
+                }
+
+                r = strv_extendf(&extension_base_names, "PORTABLE_EXTENSION=%s", extension_base_name);
+                if (r < 0)
+                        log_oom_debug();
+
+                if (!strextend_with_separator(&extensions_joined, ", ", ext->path))
+                        log_oom_debug();
+        }
+
+        STRV_FOREACH(e, extension_image_paths) {
+                _cleanup_free_ char *extension_base_name = NULL;
+
+                r = path_extract_filename(*e, &extension_base_name);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", *e);
+                        continue;
+                }
+
+                r = strv_extendf(&extension_base_names, "PORTABLE_EXTENSION=%s", extension_base_name);
+                if (r < 0)
+                        log_oom_debug();
+
+                if (!strextend_with_separator(&extensions_joined, ", ", *e))
+                        log_oom_debug();
+        }
+
+        LOG_CONTEXT_PUSH_STRV(extension_base_names);
+
+        log_struct(LOG_INFO,
+                   LOG_MESSAGE("Successfully %s%s '%s%s%s'",
+                               verb,
+                               FLAGS_SET(flags, PORTABLE_RUNTIME) ? " ephemeral" : "",
+                               image_path,
+                               isempty(extensions_joined) ? "" : "' and its extension(s) '",
+                               strempty(extensions_joined)),
+                   message_id,
+                   "PORTABLE_ROOT=%s", strna(root_base_name));
+}
+
 int portable_attach(
                 sd_bus *bus,
                 const char *name_or_path,
@@ -1538,6 +1612,14 @@ int portable_attach(
          * operation otherwise. */
         (void) install_image_and_extensions_symlinks(image, extension_images, flags, changes, n_changes);
 
+        log_portable_verb(
+                        "attached",
+                        "MESSAGE_ID=" SD_MESSAGE_PORTABLE_ATTACHED_STR,
+                        image->path,
+                        extension_images,
+                        /* extension_image_paths= */ NULL,
+                        flags);
+
         return 0;
 }
 
@@ -1861,6 +1943,14 @@ int portable_detach(
         if (rmdir(where) >= 0)
                 portable_changes_add(changes, n_changes, PORTABLE_UNLINK, where, NULL);
 
+        log_portable_verb(
+                        "detached",
+                        "MESSAGE_ID=" SD_MESSAGE_PORTABLE_DETACHED_STR,
+                        name_or_path,
+                        /* extension_images= */ NULL,
+                        extension_image_paths,
+                        flags);
+
         return ret;
 
 not_found:
index b086a67010f3667a3eeb4cb4666d95e5e5692bec..fcc8eb7bdc973d73a29afead55e51f9ac44a9779 100644 (file)
@@ -1080,9 +1080,9 @@ static int show_statistics(int argc, char **argv, void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
 
-        r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpStatistics", NULL, &reply, NULL, 0);
+        r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpStatistics", /* parameters= */ NULL, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to issue DumpStatistics() varlink call: %m");
+                return r;
 
         if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
                 return json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
@@ -1238,9 +1238,9 @@ static int reset_statistics(int argc, char **argv, void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
 
-        r = varlink_call(vl, "io.systemd.Resolve.Monitor.ResetStatistics", NULL, &reply, NULL, 0);
+        r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.ResetStatistics", /* parameters= */ NULL, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to issue ResetStatistics() varlink call: %m");
+                return r;
 
         if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
                 return json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
@@ -2972,9 +2972,9 @@ static int verb_show_cache(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
 
-        r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpCache", NULL, &reply, NULL, 0);
+        r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpCache", /* parameters= */ NULL, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to issue DumpCache() varlink call: %m");
+                return r;
 
         d = json_variant_by_key(reply, "dump");
         if (!d)
@@ -3146,9 +3146,9 @@ static int verb_show_server_state(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
 
-        r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpServerState", NULL, &reply, NULL, 0);
+        r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpServerState", /* parameters= */ NULL, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to issue DumpServerState() varlink call: %m");
+                return r;
 
         d = json_variant_by_key(reply, "dump");
         if (!d)
index 44e1e4faabf3390bf7f0cbe4c7a84a673421c162..a7d04449b2f78bb33bd4bb36d8268fdbac79a4b9 100644 (file)
@@ -1183,6 +1183,31 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns
                 r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL);
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                r = dns_packet_append_uint16(p, rr->svcb.priority, NULL);
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_name(p, rr->svcb.target_name, false, false, NULL);
+                if (r < 0)
+                        goto fail;
+
+                LIST_FOREACH(params, i, rr->svcb.params) {
+                        r = dns_packet_append_uint16(p, i->key, NULL);
+                        if (r < 0)
+                                goto fail;
+
+                        r = dns_packet_append_uint16(p, i->length, NULL);
+                        if (r < 0)
+                                goto fail;
+
+                        r = dns_packet_append_blob(p, i->value, i->length, NULL);
+                        if (r < 0)
+                                goto fail;
+                }
+                break;
+
         case DNS_TYPE_CAA:
                 r = dns_packet_append_uint8(p, rr->caa.flags, NULL);
                 if (r < 0)
@@ -1689,6 +1714,41 @@ static bool loc_size_ok(uint8_t size) {
         return m <= 9 && e <= 9 && (m > 0 || e == 0);
 }
 
+static bool dns_svc_param_is_valid(DnsSvcParam *i) {
+        if (!i)
+                return false;
+
+        switch (i->key) {
+        /* RFC 9460, section 7.1.1: alpn-ids must exactly fill SvcParamValue */
+        case DNS_SVC_PARAM_KEY_ALPN: {
+                size_t sz = 0;
+                if (i->length <= 0)
+                        return false;
+                while (sz < i->length)
+                        sz += 1 + i->value[sz]; /* N.B. will not overflow */
+                return sz == i->length;
+        }
+
+        /* RFC 9460, section 7.1.1: value must be empty */
+        case DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN:
+                return i->length == 0;
+
+        /* RFC 9460, section 7.2 */
+        case DNS_SVC_PARAM_KEY_PORT:
+                return i->length == 2;
+
+        /* RFC 9460, section 7.3: addrs must exactly fill SvcParamValue */
+        case DNS_SVC_PARAM_KEY_IPV4HINT:
+                return i->length % (sizeof (struct in_addr)) == 0;
+        case DNS_SVC_PARAM_KEY_IPV6HINT:
+                return i->length % (sizeof (struct in6_addr)) == 0;
+
+        /* Otherwise, permit any value */
+        default:
+                return true;
+        }
+}
+
 int dns_packet_read_rr(
                 DnsPacket *p,
                 DnsResourceRecord **ret,
@@ -2123,6 +2183,52 @@ int dns_packet_read_rr(
 
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                r = dns_packet_read_uint16(p, &rr->svcb.priority, NULL);
+                if (r < 0)
+                        return r;
+
+                r = dns_packet_read_name(p, &rr->svcb.target_name, false /* uncompressed */, NULL);
+                if (r < 0)
+                        return r;
+
+                DnsSvcParam *last = NULL;
+                while (p->rindex - offset < rdlength) {
+                        _cleanup_free_ DnsSvcParam *i = NULL;
+                        uint16_t svc_param_key;
+                        uint16_t sz;
+
+                        r = dns_packet_read_uint16(p, &svc_param_key, NULL);
+                        if (r < 0)
+                                return r;
+                        /* RFC 9460, section 2.2 says we must consider an RR malformed if SvcParamKeys are
+                         * not in strictly increasing order */
+                        if (last && last->key >= svc_param_key)
+                                return -EBADMSG;
+
+                        r = dns_packet_read_uint16(p, &sz, NULL);
+                        if (r < 0)
+                                return r;
+
+                        i = malloc0(offsetof(DnsSvcParam, value) + sz);
+                        if (!i)
+                                return -ENOMEM;
+
+                        i->key = svc_param_key;
+                        i->length = sz;
+                        r = dns_packet_read_blob(p, &i->value, sz, NULL);
+                        if (r < 0)
+                                return r;
+                        if (!dns_svc_param_is_valid(i))
+                                return -EBADMSG;
+
+                        LIST_INSERT_AFTER(params, rr->svcb.params, last, i);
+                        last = TAKE_PTR(i);
+                }
+
+                break;
+
         case DNS_TYPE_CAA:
                 r = dns_packet_read_uint8(p, &rr->caa.flags, NULL);
                 if (r < 0)
@@ -2801,6 +2907,27 @@ const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) {
         return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i);
 }
 
+static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
+        [DNS_SVC_PARAM_KEY_MANDATORY]       = "mandatory",
+        [DNS_SVC_PARAM_KEY_ALPN]            = "alpn",
+        [DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
+        [DNS_SVC_PARAM_KEY_PORT]            = "port",
+        [DNS_SVC_PARAM_KEY_IPV4HINT]        = "ipv4hint",
+        [DNS_SVC_PARAM_KEY_ECH]             = "ech",
+        [DNS_SVC_PARAM_KEY_IPV6HINT]        = "ipv6hint",
+        [DNS_SVC_PARAM_KEY_DOHPATH]         = "dohpath",
+        [DNS_SVC_PARAM_KEY_OHTTP]           = "ohttp",
+};
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
+
+const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
+        const char *p = dns_svc_param_key_to_string(i);
+        if (p)
+                return p;
+
+        return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
+}
+
 static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
         [DNS_PROTOCOL_DNS]   = "dns",
         [DNS_PROTOCOL_MDNS]  = "mdns",
index 705fc511d6bd9bc9b3ccfef95de77db27d0bb0af..393b7b2364a5b5bcedf2929b974f61823e47f9f0 100644 (file)
@@ -361,6 +361,25 @@ const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
 const char* dns_protocol_to_string(DnsProtocol p) _const_;
 DnsProtocol dns_protocol_from_string(const char *s) _pure_;
 
+/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
+enum {
+        DNS_SVC_PARAM_KEY_MANDATORY        = 0,     /* RFC 9460 section 8 */
+        DNS_SVC_PARAM_KEY_ALPN             = 1,     /* RFC 9460 section 7.1 */
+        DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN  = 2,     /* RFC 9460 Section 7.1 */
+        DNS_SVC_PARAM_KEY_PORT             = 3,     /* RFC 9460 section 7.2 */
+        DNS_SVC_PARAM_KEY_IPV4HINT         = 4,     /* RFC 9460 section 7.3 */
+        DNS_SVC_PARAM_KEY_ECH              = 5,     /* RFC 9460 */
+        DNS_SVC_PARAM_KEY_IPV6HINT         = 6,     /* RFC 9460 section 7.3 */
+        DNS_SVC_PARAM_KEY_DOHPATH          = 7,     /* RFC 9461 */
+        DNS_SVC_PARAM_KEY_OHTTP            = 8,
+        _DNS_SVC_PARAM_KEY_MAX_DEFINED,
+        DNS_SVC_PARAM_KEY_INVALID          = 65535  /* RFC 9460 */
+};
+
+const char* dns_svc_param_key_to_string(int i) _const_;
+const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
+#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
+
 #define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
 #define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
 
index 2bdcc7c1dcd5097966ed25700ede899c6b0862de..7d824ee806526f6338609fdfe7b69b795d60922f 100644 (file)
@@ -15,6 +15,7 @@
 #include "string-util.h"
 #include "strv.h"
 #include "terminal-util.h"
+#include "unaligned.h"
 
 DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) {
         DnsResourceKey *k;
@@ -469,6 +470,12 @@ static DnsResourceRecord* dns_resource_record_free(DnsResourceRecord *rr) {
                         free(rr->tlsa.data);
                         break;
 
+                case DNS_TYPE_SVCB:
+                case DNS_TYPE_HTTPS:
+                        free(rr->svcb.target_name);
+                        dns_svc_param_free_all(rr->svcb.params);
+                        break;
+
                 case DNS_TYPE_CAA:
                         free(rr->caa.tag);
                         free(rr->caa.value);
@@ -676,6 +683,12 @@ int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResou
                        a->tlsa.matching_type == b->tlsa.matching_type &&
                        FIELD_EQUAL(a->tlsa, b->tlsa, data);
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                return a->svcb.priority == b->svcb.priority &&
+                       dns_name_equal(a->svcb.target_name, b->svcb.target_name) &&
+                       dns_svc_params_equal(a->svcb.params, b->svcb.params);
+
         case DNS_TYPE_CAA:
                 return a->caa.flags == b->caa.flags &&
                        streq(a->caa.tag, b->caa.tag) &&
@@ -814,6 +827,107 @@ static char *format_txt(DnsTxtItem *first) {
         return s;
 }
 
+static char *format_svc_param_value(DnsSvcParam *i) {
+        _cleanup_free_ char *value = NULL;
+
+        assert(i);
+
+        switch (i->key) {
+        case DNS_SVC_PARAM_KEY_ALPN: {
+                size_t offset = 0;
+                _cleanup_strv_free_ char **values_strv = NULL;
+                while (offset < i->length) {
+                        size_t sz = (uint8_t) i->value[offset++];
+
+                        char *alpn = cescape_length((char *)&i->value[offset], sz);
+                        if (!alpn)
+                                return NULL;
+
+                        if (strv_push(&values_strv, alpn) < 0)
+                                return NULL;
+
+                        offset += sz;
+                }
+                value = strv_join(values_strv, ",");
+                if (!value)
+                        return NULL;
+                break;
+
+        }
+        case DNS_SVC_PARAM_KEY_PORT: {
+                uint16_t port = unaligned_read_be16(i->value);
+                if (asprintf(&value, "%" PRIu16, port) < 0)
+                        return NULL;
+                return TAKE_PTR(value);
+        }
+        case DNS_SVC_PARAM_KEY_IPV4HINT: {
+                const struct in_addr *addrs = i->value_in_addr;
+                _cleanup_strv_free_ char **values_strv = NULL;
+                for (size_t n = 0; n < i->length / sizeof (struct in_addr); n++) {
+                        char *addr;
+                        if (in_addr_to_string(AF_INET, (const union in_addr_union*) &addrs[n], &addr) < 0)
+                                return NULL;
+                        if (strv_push(&values_strv, addr) < 0)
+                                return NULL;
+                }
+                return strv_join(values_strv, ",");
+        }
+        case DNS_SVC_PARAM_KEY_IPV6HINT: {
+                const struct in6_addr *addrs = i->value_in6_addr;
+                _cleanup_strv_free_ char **values_strv = NULL;
+                for (size_t n = 0; n < i->length / sizeof (struct in6_addr); n++) {
+                        char *addr;
+                        if (in_addr_to_string(AF_INET6, (const union in_addr_union*) &addrs[n], &addr) < 0)
+                                return NULL;
+                        if (strv_push(&values_strv, addr) < 0)
+                                return NULL;
+                }
+                return strv_join(values_strv, ",");
+        }
+        default: {
+                value = decescape((char *)&i->value, " ,", i->length);
+                if (!value)
+                        return NULL;
+                break;
+        }
+        }
+
+        char *qvalue;
+        if (asprintf(&qvalue, "\"%s\"", value) < 0)
+                return NULL;
+        return qvalue;
+}
+
+static char *format_svc_param(DnsSvcParam *i) {
+        const char *key = FORMAT_DNS_SVC_PARAM_KEY(i->key);
+        _cleanup_free_ char *value = NULL;
+
+        assert(i);
+
+        if (i->length == 0)
+                return strdup(key);
+
+        value = format_svc_param_value(i);
+        if (!value)
+                return NULL;
+
+        return strjoin(key, "=", value);
+}
+
+static char *format_svc_params(DnsSvcParam *first) {
+        _cleanup_strv_free_ char **params = NULL;
+
+        LIST_FOREACH(params, i, first) {
+                char *param = format_svc_param(i);
+                if (!param)
+                        return NULL;
+                if (strv_push(&params, param) < 0)
+                        return NULL;
+        }
+
+        return strv_join(params, " ");
+}
+
 const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
         _cleanup_free_ char *s = NULL, *t = NULL;
         char k[DNS_RESOURCE_KEY_STRING_MAX];
@@ -1124,6 +1238,19 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
 
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                t = format_svc_params(rr->svcb.params);
+                if (!t)
+                        return NULL;
+                r = asprintf(&s, "%s %d %s %s", k, rr->svcb.priority,
+                             isempty(rr->svcb.target_name) ? "." : rr->svcb.target_name,
+                             t);
+                if (r < 0)
+                        return NULL;
+
+                break;
+
         case DNS_TYPE_OPENPGPKEY:
                 r = asprintf(&s, "%s", k);
                 if (r < 0)
@@ -1445,6 +1572,16 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash *
                 siphash24_compress_safe(rr->tlsa.data, rr->tlsa.data_size, state);
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                dns_name_hash_func(rr->svcb.target_name, state);
+                siphash24_compress_typesafe(rr->svcb.priority, state);
+                LIST_FOREACH(params, j, rr->svcb.params) {
+                        siphash24_compress_typesafe(j->key, state);
+                        siphash24_compress_safe(j->value, j->length, state);
+                }
+                break;
+
         case DNS_TYPE_CAA:
                 siphash24_compress_typesafe(rr->caa.flags, state);
                 string_hash_func(rr->caa.tag, state);
@@ -1658,6 +1795,17 @@ DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) {
                 copy->caa.value_size = rr->caa.value_size;
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                copy->svcb.priority = rr->svcb.priority;
+                copy->svcb.target_name = strdup(rr->svcb.target_name);
+                if (!copy->svcb.target_name)
+                        return NULL;
+                copy->svcb.params = dns_svc_params_copy(rr->svcb.params);
+                if (rr->svcb.params && !copy->svcb.params)
+                        return NULL;
+                break;
+
         case DNS_TYPE_OPT:
         default:
                 copy->generic.data = memdup(rr->generic.data, rr->generic.data_size);
@@ -1772,6 +1920,13 @@ DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *first) {
         return NULL;
 }
 
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *first) {
+        LIST_FOREACH(params, i, first)
+                free(i);
+
+        return NULL;
+}
+
 bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
         DnsTxtItem *bb = b;
 
@@ -1808,6 +1963,45 @@ DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) {
         return copy;
 }
 
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b) {
+        DnsSvcParam *bb = b;
+
+        if (a == b)
+                return true;
+
+        LIST_FOREACH(params, aa, a) {
+                if (!bb)
+                        return false;
+
+                if (aa->key != bb->key)
+                        return false;
+
+                if (memcmp_nn(aa->value, aa->length, bb->value, bb->length) != 0)
+                        return false;
+
+                bb = bb->params_next;
+        }
+
+        return !bb;
+}
+
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first) {
+        DnsSvcParam *copy = NULL, *end = NULL;
+
+        LIST_FOREACH(params, i, first) {
+                DnsSvcParam *j;
+
+                j = memdup(i, offsetof(DnsSvcParam, value) + i->length);
+                if (!j)
+                        return dns_svc_param_free_all(copy);
+
+                LIST_INSERT_AFTER(params, copy, end, j);
+                end = j;
+        }
+
+        return copy;
+}
+
 int dns_txt_item_new_empty(DnsTxtItem **ret) {
         DnsTxtItem *i;
 
@@ -1930,10 +2124,33 @@ static int txt_to_json(DnsTxtItem *items, JsonVariant **ret) {
         r = json_variant_new_array(ret, elements, n);
 
 finalize:
-        for (size_t i = 0; i < n; i++)
-                json_variant_unref(elements[i]);
+        json_variant_unref_many(elements, n);
+        return r;
+}
+
+static int svc_params_to_json(DnsSvcParam *params, JsonVariant **ret) {
+        JsonVariant **elements = NULL;
+        size_t n = 0;
+        int r;
+
+        assert(ret);
+
+        LIST_FOREACH(params, i, params) {
+                if (!GREEDY_REALLOC(elements, n + 1)) {
+                        r = -ENOMEM;
+                        goto finalize;
+                }
+
+                r = json_variant_new_base64(elements + n, i->value, i->length);
+                if (r < 0)
+                        goto finalize;
+
+                n++;
+        }
 
-        free(elements);
+        r = json_variant_new_array(ret, elements, n);
+finalize:
+        json_variant_unref_many(elements, n);
         return r;
 }
 
@@ -2112,6 +2329,21 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) {
                                                   JSON_BUILD_PAIR("matchingType", JSON_BUILD_UNSIGNED(rr->tlsa.matching_type)),
                                                   JSON_BUILD_PAIR("data", JSON_BUILD_HEX(rr->tlsa.data, rr->tlsa.data_size))));
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS: {
+                _cleanup_(json_variant_unrefp) JsonVariant *p = NULL;
+                r = svc_params_to_json(rr->svcb.params, &p);
+                if (r < 0)
+                        return r;
+
+                return json_build(ret,
+                                  JSON_BUILD_OBJECT(
+                                                  JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)),
+                                                  JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->svcb.priority)),
+                                                  JSON_BUILD_PAIR("target", JSON_BUILD_STRING(rr->svcb.target_name)),
+                                                  JSON_BUILD_PAIR("params", JSON_BUILD_VARIANT(p))));
+        }
+
         case DNS_TYPE_CAA:
                 return json_build(ret,
                                   JSON_BUILD_OBJECT(
index fd15cc343d859ef3b632c4d2bb362532746e59b3..961d3c785265df1897e50a850789665b211ac482 100644 (file)
@@ -16,6 +16,7 @@
 typedef struct DnsResourceKey DnsResourceKey;
 typedef struct DnsResourceRecord DnsResourceRecord;
 typedef struct DnsTxtItem DnsTxtItem;
+typedef struct DnsSvcParam DnsSvcParam;
 
 /* DNSKEY RR flags */
 #define DNSKEY_FLAG_SEP            (UINT16_C(1) << 0)
@@ -90,6 +91,17 @@ struct DnsTxtItem {
         uint8_t data[];
 };
 
+struct DnsSvcParam {
+        uint16_t key;
+        size_t length;
+        LIST_FIELDS(DnsSvcParam, params);
+        union {
+                DECLARE_FLEX_ARRAY(uint8_t, value);
+                DECLARE_FLEX_ARRAY(struct in_addr, value_in_addr);
+                DECLARE_FLEX_ARRAY(struct in6_addr, value_in6_addr);
+        };
+};
+
 struct DnsResourceRecord {
         unsigned n_ref;
         uint32_t ttl;
@@ -243,6 +255,13 @@ struct DnsResourceRecord {
                         uint8_t matching_type;
                 } tlsa;
 
+                /* https://tools.ietf.org/html/rfc9460 */
+                struct {
+                        uint16_t priority;
+                        char *target_name;
+                        DnsSvcParam *params;
+                } svcb, https;
+
                 /* https://tools.ietf.org/html/rfc6844 */
                 struct {
                         char *tag;
@@ -368,6 +387,10 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
 DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i);
 int dns_txt_item_new_empty(DnsTxtItem **ret);
 
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *i);
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b);
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first);
+
 int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size);
 
 int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret);
index 307630f3c7a1618a5ee99852f7c645cf497b0025..a99f8e8a587f09d8eeacfa3553c7ac233729d849 100644 (file)
@@ -638,9 +638,20 @@ static int on_stream_complete(DnsStream *s, int error) {
                 }
         }
 
-        if (error != 0)
-                LIST_FOREACH(transactions_by_stream, t, s->transactions)
+        if (error != 0) {
+                /* First, detach the stream from the server. Otherwise, transactions attached to this stream
+                 * may be restarted by on_transaction_stream_error() below with this stream. */
+                dns_stream_detach(s);
+
+                /* Do not use LIST_FOREACH() here, as
+                 *     on_transaction_stream_error()
+                 *         -> dns_transaction_complete_errno()
+                 *             -> dns_transaction_free()
+                 * may free multiple transactions in the list. */
+                DnsTransaction *t;
+                while ((t = s->transactions))
                         on_transaction_stream_error(t, error);
+        }
 
         return 0;
 }
index c78d3c6a1df7f921779c8a00fd4f3f6bee23a6d9..c3313c6c529fae92641a7f62abe51895c573b46f 100644 (file)
@@ -297,7 +297,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "same-dir",           no_argument,       NULL, 'd'                    },
                 { "shell",              no_argument,       NULL, 'S'                    },
                 { "ignore-failure",     no_argument,       NULL, ARG_IGNORE_FAILURE     },
-                { "background",         no_argument,       NULL, ARG_BACKGROUND         },
+                { "background",         required_argument, NULL, ARG_BACKGROUND         },
                 {},
         };
 
@@ -1822,23 +1822,36 @@ static int start_transient_service(sd_bus *bus) {
                                 log_info("CPU time consumed: %s",
                                          FORMAT_TIMESPAN(DIV_ROUND_UP(c.cpu_usage_nsec, NSEC_PER_USEC), USEC_PER_MSEC));
 
-                        if (c.memory_peak != UINT64_MAX)
-                                log_info("Memory peak: %s", FORMAT_BYTES(c.memory_peak));
+                        if (c.memory_peak != UINT64_MAX) {
+                                const char *swap;
+
+                                if (c.memory_swap_peak != UINT64_MAX)
+                                        swap = strjoina(" (swap: ", FORMAT_BYTES(c.memory_swap_peak), ")");
+                                else
+                                        swap = "";
+
+                                log_info("Memory peak: %s%s", FORMAT_BYTES(c.memory_peak), swap);
+                        }
+
+                        const char *ip_ingress = NULL, *ip_egress = NULL;
 
-                        if (c.memory_swap_peak != UINT64_MAX)
-                                log_info("Memory swap peak: %s", FORMAT_BYTES(c.memory_swap_peak));
+                        if (!IN_SET(c.ip_ingress_bytes, 0, UINT64_MAX))
+                                ip_ingress = strjoina(" received: ", FORMAT_BYTES(c.ip_ingress_bytes));
+                        if (!IN_SET(c.ip_egress_bytes, 0, UINT64_MAX))
+                                ip_egress = strjoina(" sent: ", FORMAT_BYTES(c.ip_egress_bytes));
 
-                        if (c.ip_ingress_bytes != UINT64_MAX)
-                                log_info("IP traffic received: %s", FORMAT_BYTES(c.ip_ingress_bytes));
+                        if (ip_ingress || ip_egress)
+                                log_info("IP traffic%s%s", strempty(ip_ingress), strempty(ip_egress));
 
-                        if (c.ip_egress_bytes != UINT64_MAX)
-                                log_info("IP traffic sent: %s", FORMAT_BYTES(c.ip_egress_bytes));
+                        const char *io_read = NULL, *io_write = NULL;
 
-                        if (c.io_read_bytes != UINT64_MAX)
-                                log_info("IO bytes read: %s", FORMAT_BYTES(c.io_read_bytes));
+                        if (!IN_SET(c.io_read_bytes, 0, UINT64_MAX))
+                                io_read = strjoina(" read: ", FORMAT_BYTES(c.io_read_bytes));
+                        if (!IN_SET(c.io_write_bytes, 0, UINT64_MAX))
+                                io_write = strjoina(" written: ", FORMAT_BYTES(c.io_write_bytes));
 
-                        if (c.io_write_bytes != UINT64_MAX)
-                                log_info("IO bytes written: %s", FORMAT_BYTES(c.io_write_bytes));
+                        if (io_read || io_write)
+                                log_info("IO bytes%s%s", strempty(io_read), strempty(io_write));
                 }
 
                 /* Try to propagate the service's return value. But if the service defines
index 85cb067c8414f29f44612945389fc56113ebb858..37b3f6a6ea35767fa92c6920ddca4d593ba6b928 100644 (file)
@@ -241,13 +241,13 @@ int battery_is_discharging_and_low(void) {
 
         r = on_ac_power();
         if (r < 0)
-                log_debug_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m");
+                log_warning_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m");
         if (r > 0)
                 return false;
 
         r = battery_enumerator_new(&e);
         if (r < 0)
-                return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
+                return log_error_errno(r, "Failed to initialize battery enumerator: %m");
 
         FOREACH_DEVICE(e, dev) {
                 int level;
index e726073b646a51f74f0a8c833c49c619f26066a3..72d3cbea987d5b08a10afff12c5c9c7c4a36e47f 100644 (file)
@@ -15,20 +15,18 @@ bool boot_entry_token_valid(const char *p) {
         return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p);
 }
 
-static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType *type, char **token) {
+static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *type, char **token) {
         _cleanup_free_ char *buf = NULL, *p = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
         assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(dir);
         assert(type);
         assert(*type == BOOT_ENTRY_TOKEN_AUTO);
         assert(token);
 
-        if (!etc_kernel)
-                return 0;
-
-        p = path_join(etc_kernel, "entry-token");
+        p = path_join(dir, "entry-token");
         if (!p)
                 return log_oom();
 
@@ -55,6 +53,26 @@ static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType
         return 1;
 }
 
+static int entry_token_load(int rfd, const char *conf_root, BootEntryTokenType *type, char **token) {
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(type);
+        assert(*type == BOOT_ENTRY_TOKEN_AUTO);
+        assert(token);
+
+        if (conf_root)
+                return entry_token_load_one(rfd, conf_root, type, token);
+
+        FOREACH_STRING(path, "/etc/kernel", "/usr/lib/kernel") {
+                r = entry_token_load_one(rfd, path, type, token);
+                if (r != 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 static int entry_token_from_machine_id(sd_id128_t machine_id, BootEntryTokenType *type, char **token) {
         char *p;
 
@@ -123,7 +141,7 @@ static int entry_token_from_os_release(int rfd, BootEntryTokenType *type, char *
 
 int boot_entry_token_ensure_at(
                 int rfd,
-                const char *etc_kernel,
+                const char *conf_root,
                 sd_id128_t machine_id,
                 bool machine_id_is_random,
                 BootEntryTokenType *type,
@@ -141,7 +159,7 @@ int boot_entry_token_ensure_at(
         switch (*type) {
 
         case BOOT_ENTRY_TOKEN_AUTO:
-                r = entry_token_load(rfd, etc_kernel, type, token);
+                r = entry_token_load(rfd, conf_root, type, token);
                 if (r != 0)
                         return r;
 
@@ -198,7 +216,7 @@ int boot_entry_token_ensure_at(
 
 int boot_entry_token_ensure(
                 const char *root,
-                const char *etc_kernel,
+                const char *conf_root,
                 sd_id128_t machine_id,
                 bool machine_id_is_random,
                 BootEntryTokenType *type,
@@ -215,7 +233,7 @@ int boot_entry_token_ensure(
         if (rfd < 0)
                 return -errno;
 
-        return boot_entry_token_ensure_at(rfd, etc_kernel, machine_id, machine_id_is_random, type, token);
+        return boot_entry_token_ensure_at(rfd, conf_root, machine_id, machine_id_is_random, type, token);
 }
 
 int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token) {
index f3a6f28417668520fa01094c83571bd448db9b09..836b63733a9b5494440e0f4873467b4b4f46eb37 100644 (file)
@@ -17,14 +17,14 @@ bool boot_entry_token_valid(const char *p);
 
 int boot_entry_token_ensure(
                 const char *root,
-                const char *etc_kernel,   /* will be prefixed with root, typically /etc/kernel. */
+                const char *conf_root,   /* will be prefixed with root, typically /etc/kernel. */
                 sd_id128_t machine_id,
                 bool machine_id_is_random,
                 BootEntryTokenType *type, /* input and output */
                 char **token);            /* output, but do not pass uninitialized value. */
 int boot_entry_token_ensure_at(
                 int rfd,
-                const char *etc_kernel,
+                const char *conf_root,
                 sd_id128_t machine_id,
                 bool machine_id_is_random,
                 BootEntryTokenType *type,
index bbdd4f64ac827dd66856c74e3cde5bff26812208..ac92ec8241099a1a642491ea6685538a0375ff92 100644 (file)
@@ -321,7 +321,7 @@ int bpf_map_new(
         /* The map name is primarily informational for debugging purposes, and typically too short
          * to carry the full unit name, hence we employ a trivial lossy escaping to make it fit
          * (truncation + only alphanumerical, "." and "_" are allowed as per
-         * https://www.kernel.org/doc/html/next/bpf/maps.html#usage-notes) */
+         * https://docs.kernel.org/bpf/maps.html#usage-notes) */
         for (size_t i = 0; i < sizeof(attr.map_name) - 1 && *n; i++, n++)
                 attr.map_name[i] = strchr(ALPHANUMERICAL ".", *n) ? *n : '_';
 
index ff905d147f82c6f4a859bba01d5407fe963dd07b..d05c474e7f06bf7a061c719390a0d5806a057174 100644 (file)
@@ -280,8 +280,13 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) {
 
                 e = sd_bus_message_get_error(reply);
 
-                if (bus_error_is_unknown_service(e))
-                        /* Treat no PK available as access denied */
+                if (bus_error_is_unknown_service(e) ||
+                    sd_bus_error_has_names(
+                                    e,
+                                    "org.freedesktop.PolicyKit1.Error.Failed",
+                                    "org.freedesktop.PolicyKit1.Error.Cancelled",
+                                    "org.freedesktop.PolicyKit1.Error.NotAuthorized"))
+                        /* Treat no PK available as access denied. Also treat some of the well-known PK errors as such. */
                         q->denied_action = TAKE_PTR(a);
                 else {
                         /* Save error from polkit reply, so it can be returned when the same authorization
@@ -381,7 +386,7 @@ static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_e
                 if (q->request)
                         (void) sd_bus_reply_method_errno(q->request, r, NULL);
                 if (q->link)
-                        varlink_error_errno(q->link, r);
+                        (void) varlink_error_errno(q->link, r);
                 async_polkit_query_unref(q);
         }
         return r;
@@ -742,10 +747,9 @@ int varlink_verify_polkit_async(
                 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);
+                                (void) varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL);
+                        else if (ERRNO_IS_NEG_PRIVILEGE(r))
+                                (void) varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL);
 
                         return r;
                 }
index e34ebcca6eacbebefe35e779f91427bb3d71affa..f5efc7618a819b98b96bd05df1acadfdeb4f53f7 100644 (file)
@@ -1064,7 +1064,7 @@ int config_parse_tristate(
 
         if (isempty(rvalue)) {
                 *t = -1;
-                return 0;
+                return 1;
         }
 
         r = parse_tristate(rvalue, t);
@@ -1074,7 +1074,7 @@ int config_parse_tristate(
                 return 0;
         }
 
-        return 0;
+        return 1;
 }
 
 int config_parse_string(
index 434907c9986b9eb18ec8a48d966a73644f808e5b..0325f6e129387c9b59c52f71efdf74d051b2d471 100644 (file)
@@ -28,7 +28,6 @@
 #include "sparse-endian.h"
 #include "stat-util.h"
 #include "tpm2-util.h"
-#include "virt.h"
 
 #define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024))
 
@@ -1170,7 +1169,7 @@ int decrypt_credential_and_warn(
                 /* 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->iov_len <
-                    ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
+                    p +
                     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) +
                     ALIGN8(offsetof(struct metadata_credential_header, name)) +
@@ -1190,8 +1189,7 @@ int decrypt_credential_and_warn(
                                 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key 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)) +
+                            p +
                             ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
                             ALIGN8(offsetof(struct metadata_credential_header, name)) +
                             le32toh(h->tag_size))
index e5e47e4ac6d7480e74f27327eaf7753e580d077a..5b664d2333ef72d74150d572795bbfcd8a568678 100644 (file)
@@ -3382,11 +3382,10 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
         };
 
         _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **initrd_release = NULL, **sysext_release = NULL, **confext_release = NULL;
+        _cleanup_free_ char *hostname = NULL, *t = NULL;
         _cleanup_close_pair_ int error_pipe[2] = EBADF_PAIR;
-        _cleanup_(rmdir_and_freep) char *t = NULL;
         _cleanup_(sigkill_waitp) pid_t child = 0;
         sd_id128_t machine_id = SD_ID128_NULL;
-        _cleanup_free_ char *hostname = NULL;
         unsigned n_meta_initialized = 0;
         int fds[2 * _META_MAX], r, v;
         int has_init_system = -1;
@@ -3405,7 +3404,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
                 }
         }
 
-        r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t);
+        r = get_common_dissect_directory(&t);
         if (r < 0)
                 goto finish;
 
@@ -4070,3 +4069,29 @@ int verity_dissect_and_mount(
 
         return 0;
 }
+
+int get_common_dissect_directory(char **ret) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        /* A common location we mount dissected images to. The assumption is that everyone who uses this
+         * function runs in their own private mount namespace (with mount propagation off on /run/systemd/,
+         * and thus can mount something here without affecting anyone else). */
+
+        t = strdup("/run/systemd/dissect-root");
+        if (!t)
+                return log_oom_debug();
+
+        r = mkdir_parents(t, 0755);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to create parent dirs of mount point '%s': %m", t);
+
+        r = RET_NERRNO(mkdir(t, 0000)); /* It's supposed to be overmounted, hence let's make this inaccessible */
+        if (r < 0 && r != -EEXIST)
+                return log_debug_errno(r, "Failed to create mount point '%s': %m", t);
+
+        if (ret)
+                *ret = TAKE_PTR(t);
+
+        return 0;
+}
index ed02049ed0c7190768a22c4e1a2cc3340f880658..2366a383979499df720d4de496ea9f2219f90a7d 100644 (file)
@@ -229,3 +229,5 @@ static inline const char *dissected_partition_fstype(const DissectedPartition *m
 
         return m->decrypted_node ? m->decrypted_fstype : m->fstype;
 }
+
+int get_common_dissect_directory(char **ret);
index 7eebd2300e4f5c0e0e84a54e00bbf02fee6763f9..6c1e41a1fe4a3f6c60b12d02067252b3e29cffa7 100644 (file)
@@ -102,33 +102,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
                 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
 
         JSON_VARIANT_ARRAY_FOREACH(e, variant) {
-                bool matching = false;
-                JsonVariant *m;
-
                 if (!json_variant_is_object(e))
                         return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
 
-                m = json_variant_by_key(e, "matchMachineId");
-                if (m) {
-                        r = per_machine_id_match(m, flags);
-                        if (r < 0)
-                                return r;
-
-                        matching = r > 0;
-                }
-
-                if (!matching) {
-                        m = json_variant_by_key(e, "matchHostname");
-                        if (m) {
-                                r = per_machine_hostname_match(m, flags);
-                                if (r < 0)
-                                        return r;
-
-                                matching = r > 0;
-                        }
-                }
-
-                if (!matching)
+                r = per_machine_match(e, flags);
+                if (r < 0)
+                        return r;
+                if (r == 0)
                         continue;
 
                 r = json_dispatch(e, per_machine_dispatch_table, flags, userdata);
index 795b3a2d56f915b4bdadb61d9ed320dca5b3ae07..3547476c85141522c0fc130d0ed55f7306caa0dc 100644 (file)
@@ -152,7 +152,8 @@ static int read_resume_config(dev_t *ret_devno, uint64_t *ret_offset) {
 
         if (devno == 0 && offset > 0 && offset != UINT64_MAX)
                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.", offset);
+                                       "Found populated /sys/power/resume_offset (%" PRIu64 ") but /sys/power/resume is not set, refusing.",
+                                       offset);
 
         *ret_devno = devno;
         *ret_offset = offset;
index 0259196e4e2b9b34ccb89c70968829b891c50dfe..7ec6368e1eccbc1e36f1cea920514b490aafc24b 100644 (file)
@@ -89,6 +89,9 @@ struct JsonVariant {
         /* Erase from memory when freeing */
         bool sensitive:1;
 
+        /* True if we know that any referenced json object is marked sensitive */
+        bool recursive_sensitive:1;
+
         /* If this is an object the fields are strictly ordered by name */
         bool sorted:1;
 
@@ -1453,6 +1456,33 @@ bool json_variant_is_sensitive(JsonVariant *v) {
         return v->sensitive;
 }
 
+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 (v->recursive_sensitive) /* Already checked this before */
+                return true;
+        if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+                return false;
+        if (v->is_reference) {
+                if (!json_variant_is_sensitive_recursive(v->reference))
+                        return false;
+
+                return (v->recursive_sensitive = true);
+        }
+
+        for (size_t i = 0; i < json_variant_elements(v); i++)
+                if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
+                        return (v->recursive_sensitive = true);
+
+        /* Note: we only cache the result here in case true, since we allow all elements down the tree to
+         * have their sensitive flag toggled later on (but never off) */
+        return false;
+}
+
 static void json_variant_propagate_sensitive(JsonVariant *from, JsonVariant *to) {
         if (json_variant_is_sensitive(from))
                 json_variant_sensitive(to);
@@ -1582,6 +1612,15 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha
         assert(f);
         assert(v);
 
+        if (FLAGS_SET(flags, JSON_FORMAT_CENSOR_SENSITIVE) && json_variant_is_sensitive(v)) {
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ansi_red(), f);
+                fputs("\"<sensitive data>\"", f);
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+                return 0;
+        }
+
         switch (json_variant_type(v)) {
 
         case JSON_VARIANT_REAL: {
@@ -1773,25 +1812,6 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha
         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;
@@ -1807,10 +1827,6 @@ int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
         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;
index 3c20f94b5874fc33584c950eb5e41986807660db..9c8448f728b82e450de8034c1c2c7d5e18cf832c 100644 (file)
@@ -155,6 +155,7 @@ bool json_variant_equal(JsonVariant *a, JsonVariant *b);
 
 void json_variant_sensitive(JsonVariant *v);
 bool json_variant_is_sensitive(JsonVariant *v);
+bool json_variant_is_sensitive_recursive(JsonVariant *v);
 
 struct json_variant_foreach_state {
         JsonVariant *variant;
@@ -196,7 +197,7 @@ typedef enum JsonFormatFlags {
         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 */
+        JSON_FORMAT_CENSOR_SENSITIVE = 1 << 11, /* Replace all sensitive elements with the string "<sensitive data>" */
 } JsonFormatFlags;
 
 int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
index 858b707f6c8c99da83c90624fb4be0ff1179a345..1f0279cde378abdf30f062580ecdb7b0f5561543 100644 (file)
@@ -1688,7 +1688,7 @@ static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) {
         if (r < 0)
                 return r;
 
-        r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd);
+        r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &rootfd);
         if (r < 0)
                 return r;
 
index d6aa667adacaf4e41a5ea8f292dd081344d57ba7..833c98b88ef1b979b03eaeb22b20a620c92ea870 100644 (file)
@@ -265,7 +265,12 @@ int machine_id_commit(const char *root) {
         fd = safe_close(fd);
 
         /* Store current mount namespace */
-        r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL);
+        r = namespace_open(0,
+                           /* ret_pidns_fd = */ NULL,
+                           &initial_mntns_fd,
+                           /* ret_netns_fd = */ NULL,
+                           /* ret_userns_fd = */ NULL,
+                           /* ret_root_fd = */ NULL);
         if (r < 0)
                 return log_error_errno(r, "Can't fetch current mount namespace: %m");
 
@@ -284,7 +289,11 @@ int machine_id_commit(const char *root) {
                 return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id);
 
         /* Return to initial namespace and proceed a lazy tmpfs unmount */
-        r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1);
+        r = namespace_enter(/* pidns_fd = */ -EBADF,
+                            initial_mntns_fd,
+                            /* netns_fd = */ -EBADF,
+                            /* userns_fd = */ -EBADF,
+                            /* root_fd = */ -EBADF);
         if (r < 0)
                 return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id);
 
index 19b119b4afaa18e7ebcd9c75e8ebe6dc33c8f8eb..4d440127e57ff942a51d9a0b7cbcc6c9a47d0675 100644 (file)
@@ -425,7 +425,7 @@ int make_filesystem(
                                 "-T", "default",
                                 node);
 
-                if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
+                if (root && strv_extend_many(&argv, "-d", root) < 0)
                         return log_oom();
 
                 if (quiet && strv_extend(&argv, "-q") < 0)
@@ -450,7 +450,7 @@ int make_filesystem(
                 if (!discard && strv_extend(&argv, "--nodiscard") < 0)
                         return log_oom();
 
-                if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0)
+                if (root && strv_extend_many(&argv, "-r", root) < 0)
                         return log_oom();
 
                 if (quiet && strv_extend(&argv, "-q") < 0)
@@ -514,7 +514,7 @@ int make_filesystem(
                         if (!protofile_with_opt)
                                 return -ENOMEM;
 
-                        if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile_with_opt), false) < 0)
+                        if (strv_extend_many(&argv, "-p", protofile_with_opt) < 0)
                                 return log_oom();
                 }
 
index 951701d497ef40320d634c238873bdc22f328563..907990d96e4d0a573f8b340ede06fbc40cd1b8f6 100644 (file)
@@ -8,6 +8,7 @@
 
 static int denylist_modules(const char *p, char ***denylist) {
         _cleanup_strv_free_ char **k = NULL;
+        int r;
 
         assert(p);
         assert(denylist);
@@ -16,8 +17,9 @@ static int denylist_modules(const char *p, char ***denylist) {
         if (!k)
                 return -ENOMEM;
 
-        if (strv_extend_strv(denylist, k, true) < 0)
-                return -ENOMEM;
+        r = strv_extend_strv(denylist, k, /* filter_duplicates= */ true);
+        if (r < 0)
+                return r;
 
         return 0;
 }
index 3305b6360e7a427c7468fbb846113172fda4741c..bf351820a74afc8b25e20de8cb2fc4791d51e9a6 100644 (file)
@@ -1100,7 +1100,7 @@ static int mount_in_namespace(
         if (!pidref_is_set(target))
                 return -ESRCH;
 
-        r = namespace_open(target->pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
+        r = namespace_open(target->pid, &pidns_fd, &mntns_fd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &root_fd);
         if (r < 0)
                 return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m");
 
index 03660ad4178fc0d3bc5aa4ed0cca43d5397c6bb8..c05ce7948926e9748bdcebfb3e60911587db1faa 100644 (file)
@@ -115,7 +115,7 @@ static void pty_forward_disconnect(PTYForward *f) {
                 (void) fd_nonblock(f->output_fd, false);
 
                 if (colors_enabled())
-                        (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_LINE, SIZE_MAX);
+                        (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN, SIZE_MAX);
 
                 if (f->close_output_fd)
                         f->output_fd = safe_close(f->output_fd);
index 0ba57627615d89b773d51e613d4e6348bd232170..6865b045b5e9172837d207bea420f8f6668f7d1f 100644 (file)
@@ -2,14 +2,17 @@
 
 #include <arpa/inet.h>
 #include <errno.h>
+#include <linux/net_namespace.h>
 #include <net/if.h>
 #include <string.h>
 
 #include "alloc-util.h"
 #include "errno-util.h"
 #include "extract-word.h"
+#include "fd-util.h"
 #include "log.h"
 #include "memory-util.h"
+#include "namespace-util.h"
 #include "netlink-util.h"
 #include "parse-util.h"
 #include "socket-netlink.h"
@@ -407,3 +410,69 @@ const char *in_addr_full_to_string(struct in_addr_full *a) {
 
         return a->cached_server_string;
 }
+
+int netns_get_nsid(int netnsfd, uint32_t *ret) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        _cleanup_close_ int _netns_fd = -EBADF;
+        int r;
+
+        if (netnsfd < 0) {
+                r = namespace_open(
+                                0,
+                                /* ret_pidns_fd = */ NULL,
+                                /* ret_mntns_fd = */ NULL,
+                                &_netns_fd,
+                                /* ret_userns_fd = */ NULL,
+                                /* ret_root_fd = */ NULL);
+                if (r < 0)
+                        return r;
+
+                netnsfd = _netns_fd;
+        }
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return r;
+
+        r = sd_rtnl_message_new_nsid(rtnl, &req, RTM_GETNSID);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_append_s32(req, NETNSA_FD, netnsfd);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_call(rtnl, req, 0, &reply);
+        if (r < 0)
+                return r;
+
+        for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
+                uint16_t type;
+
+                r = sd_netlink_message_get_errno(m);
+                if (r < 0)
+                        return r;
+
+                r = sd_netlink_message_get_type(m, &type);
+                if (r < 0)
+                        return r;
+                if (type != RTM_NEWNSID)
+                        continue;
+
+                uint32_t u;
+                r = sd_netlink_message_read_u32(m, NETNSA_NSID, &u);
+                if (r < 0)
+                        return r;
+
+                if (u == UINT32_MAX) /* no NSID assigned yet */
+                        return -ENODATA;
+
+                if (ret)
+                        *ret = u;
+
+                return 0;
+        }
+
+        return -ENXIO;
+}
index 6256a831bac3fefb09cd00e440ca16776b971549..2c06fbe3a7d56fa3dc78a3b90a83a2a7c5ab2330 100644 (file)
@@ -42,3 +42,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free);
 int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret);
 int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret);
 const char *in_addr_full_to_string(struct in_addr_full *a);
+
+int netns_get_nsid(int netnsfd, uint32_t *ret);
index 22b8a872a4811387f8025b5ce6cfe00fc964dc99..770a1e2b1429322566620a22c40e8d4ccc0dc838 100644 (file)
@@ -4196,6 +4196,11 @@ int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret
         }
 }
 
+/* Be careful before changing anything in this function, as the TPM key "name" is calculated using the entire
+ * TPMT_PUBLIC (after marshalling), and that "name" is used (for example) to calculate the policy hash for
+ * the Authorize policy. So we must ensure this conversion of a PEM to TPM2B_PUBLIC does not change the
+ * "name", because it would break unsealing of previously-sealed objects that used (for example)
+ * tpm2_calculate_policy_authorize(). See bug #30546. */
 int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) {
         int key_id, r;
 
@@ -4274,8 +4279,11 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret)
                 uint32_t exponent = 0;
                 memcpy(&exponent, e, e_size);
                 exponent = be32toh(exponent) >> (32 - e_size * 8);
-                if (exponent == TPM2_RSA_DEFAULT_EXPONENT)
-                        exponent = 0;
+
+                /* TPM specification Part 2 ("Structures") section for TPMS_RSA_PARAMS states "An exponent of
+                 * zero indicates that the exponent is the default of 2^16 + 1". However, we have no reason
+                 * to special case it in our PEM->TPM2B_PUBLIC conversion, and doing so could break backwards
+                 * compatibility, so even if it is the "default" value of 0x10001, we do not set it to 0. */
                 public.parameters.rsaDetail.exponent = exponent;
 
                 break;
@@ -5549,11 +5557,25 @@ int tpm2_unseal(Tpm2Context *c,
 
                 /* 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 (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)
+                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) {
+#if HAVE_OPENSSL
+                        if (iovec_is_set(pubkey) &&
+                            pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
+                            pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
+                                /* Due to bug #30546, if using RSA pubkey with the default exponent, we may
+                                 * need to set the exponent to the TPM special-case value of 0 and retry. */
+                                log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0.");
+                                pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0;
+                                continue;
+                        } else
+#endif
                                 return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
                                                        "Current policy digest does not match stored policy digest, cancelling "
                                                        "TPM2 authentication attempt.");
+                }
 
                 log_debug("Unsealing HMAC key.");
 
index 7a86289d166df9ca0a33b2d24eb33e4d6564b42b..4f4b675a37b9fa3a41f818aa81d2256fc6f79fe2 100644 (file)
@@ -14,6 +14,7 @@
 #include "id128-util.h"
 #include "log.h"
 #include "macro.h"
+#include "missing_threads.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "signal-util.h"
@@ -363,18 +364,22 @@ int udev_queue_is_empty(void) {
                 (errno == ENOENT ? true : -errno) : false;
 }
 
-bool udev_available(void) {
-        static int cache = -1;
+static int cached_udev_availability = -1;
+
+void reset_cached_udev_availability(void) {
+        cached_udev_availability = -1;
+}
 
+bool udev_available(void) {
         /* The service systemd-udevd is started only when /sys is read write.
          * See systemd-udevd.service: ConditionPathIsReadWrite=/sys
          * Also, our container interface (http://systemd.io/CONTAINER_INTERFACE/) states that /sys must
          * be mounted in read-only mode in containers. */
 
-        if (cache >= 0)
-                return cache;
+        if (cached_udev_availability >= 0)
+                return cached_udev_availability;
 
-        return (cache = (path_is_read_only_fs("/sys/") <= 0));
+        return (cached_udev_availability = (path_is_read_only_fs("/sys/") <= 0));
 }
 
 int device_get_vendor_string(sd_device *device, const char **ret) {
index 5f49e87116179db7989374a1fec74491de4adc3f..4d27bed1d948f26ca0de104a6a2bb7546b6f027c 100644 (file)
@@ -23,6 +23,7 @@ size_t udev_replace_chars(char *str, const char *allow);
 
 int udev_queue_is_empty(void);
 
+void reset_cached_udev_availability(void);
 bool udev_available(void);
 
 int device_get_vendor_string(sd_device *device, const char **ret);
index 414a49331b106a6033d0235f61ffeea126343c75..ffb572146628d1dd2fa13102751a9f5c5c1bee64 100644 (file)
@@ -208,39 +208,17 @@ int nss_user_record_by_name(
                 bool with_shadow,
                 UserRecord **ret) {
 
-        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
-        struct passwd pwd, *result;
+        _cleanup_free_ char *sbuf = NULL;
+        _cleanup_free_ struct passwd *result = NULL;
         bool incomplete = false;
-        size_t buflen = 4096;
         struct spwd spwd, *sresult = NULL;
         int r;
 
         assert(name);
 
-        for (;;) {
-                buf = malloc(buflen);
-                if (!buf)
-                        return -ENOMEM;
-
-                r = getpwnam_r(name, &pwd, buf, buflen, &result);
-                if (r == 0)  {
-                        if (!result)
-                                return -ESRCH;
-
-                        break;
-                }
-
-                if (r < 0)
-                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value");
-                if (r != ERANGE)
-                        return -r;
-
-                if (buflen > SIZE_MAX / 2)
-                        return -ERANGE;
-
-                buflen *= 2;
-                buf = mfree(buf);
-        }
+        r = getpwnam_malloc(name, &result);
+        if (r < 0)
+                return r;
 
         if (with_shadow) {
                 r = nss_spwd_for_passwd(result, &spwd, &sbuf);
@@ -266,36 +244,15 @@ int nss_user_record_by_uid(
                 bool with_shadow,
                 UserRecord **ret) {
 
-        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
-        struct passwd pwd, *result;
+        _cleanup_free_ char *sbuf = NULL;
+        _cleanup_free_ struct passwd *result = NULL;
         bool incomplete = false;
-        size_t buflen = 4096;
         struct spwd spwd, *sresult = NULL;
         int r;
 
-        for (;;) {
-                buf = malloc(buflen);
-                if (!buf)
-                        return -ENOMEM;
-
-                r = getpwuid_r(uid, &pwd, buf, buflen, &result);
-                if (r == 0)  {
-                        if (!result)
-                                return -ESRCH;
-
-                        break;
-                }
-                if (r < 0)
-                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value");
-                if (r != ERANGE)
-                        return -r;
-
-                if (buflen > SIZE_MAX / 2)
-                        return -ERANGE;
-
-                buflen *= 2;
-                buf = mfree(buf);
-        }
+        r = getpwuid_malloc(uid, &result);
+        if (r < 0)
+                return r;
 
         if (with_shadow)  {
                 r = nss_spwd_for_passwd(result, &spwd, &sbuf);
@@ -422,38 +379,17 @@ int nss_group_record_by_name(
                 bool with_shadow,
                 GroupRecord **ret) {
 
-        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
-        struct group grp, *result;
+        _cleanup_free_ char *sbuf = NULL;
+        _cleanup_free_ struct group *result = NULL;
         bool incomplete = false;
-        size_t buflen = 4096;
         struct sgrp sgrp, *sresult = NULL;
         int r;
 
         assert(name);
 
-        for (;;) {
-                buf = malloc(buflen);
-                if (!buf)
-                        return -ENOMEM;
-
-                r = getgrnam_r(name, &grp, buf, buflen, &result);
-                if (r == 0)  {
-                        if (!result)
-                                return -ESRCH;
-
-                        break;
-                }
-
-                if (r < 0)
-                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value");
-                if (r != ERANGE)
-                        return -r;
-                if (buflen > SIZE_MAX / 2)
-                        return -ERANGE;
-
-                buflen *= 2;
-                buf = mfree(buf);
-        }
+        r = getgrnam_malloc(name, &result);
+        if (r < 0)
+                return r;
 
         if (with_shadow) {
                 r = nss_sgrp_for_group(result, &sgrp, &sbuf);
@@ -479,35 +415,15 @@ int nss_group_record_by_gid(
                 bool with_shadow,
                 GroupRecord **ret) {
 
-        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
-        struct group grp, *result;
+        _cleanup_free_ char *sbuf = NULL;
+        _cleanup_free_ struct group *result = NULL;
         bool incomplete = false;
-        size_t buflen = 4096;
         struct sgrp sgrp, *sresult = NULL;
         int r;
 
-        for (;;) {
-                buf = malloc(buflen);
-                if (!buf)
-                        return -ENOMEM;
-
-                r = getgrgid_r(gid, &grp, buf, buflen, &result);
-                if (r == 0)  {
-                        if (!result)
-                                return -ESRCH;
-                        break;
-                }
-
-                if (r < 0)
-                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value");
-                if (r != ERANGE)
-                        return -r;
-                if (buflen > SIZE_MAX / 2)
-                        return -ERANGE;
-
-                buflen *= 2;
-                buf = mfree(buf);
-        }
+        r = getgrgid_malloc(gid, &result);
+        if (r < 0)
+                return r;
 
         if (with_shadow) {
                 r = nss_sgrp_for_group(result, &sgrp, &sbuf);
index 654c4d3588169d9f04a4a1b0f02ce4a1e59978e3..47ef30418ebcfe43aafe3f6a0a5f8c2eb4599434 100644 (file)
@@ -1133,6 +1133,33 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) {
         return false;
 }
 
+int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags) {
+        JsonVariant *m;
+        int r;
+
+        assert(json_variant_is_object(entry));
+
+        m = json_variant_by_key(entry, "matchMachineId");
+        if (m) {
+                r = per_machine_id_match(m, flags);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return true;
+        }
+
+        m = json_variant_by_key(entry, "matchHostname");
+        if (m) {
+                r = per_machine_hostname_match(m, flags);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return true;
+        }
+
+        return false;
+}
+
 static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
 
         static const JsonDispatch per_machine_dispatch_table[] = {
@@ -1219,33 +1246,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
                 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
 
         JSON_VARIANT_ARRAY_FOREACH(e, variant) {
-                bool matching = false;
-                JsonVariant *m;
-
                 if (!json_variant_is_object(e))
                         return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
 
-                m = json_variant_by_key(e, "matchMachineId");
-                if (m) {
-                        r = per_machine_id_match(m, flags);
-                        if (r < 0)
-                                return r;
-
-                        matching = r > 0;
-                }
-
-                if (!matching) {
-                        m = json_variant_by_key(e, "matchHostname");
-                        if (m) {
-                                r = per_machine_hostname_match(m, flags);
-                                if (r < 0)
-                                        return r;
-
-                                matching = r > 0;
-                        }
-                }
-
-                if (!matching)
+                r = per_machine_match(e, flags);
+                if (r < 0)
+                        return r;
+                if (r == 0)
                         continue;
 
                 r = json_dispatch(e, per_machine_dispatch_table, flags, userdata);
index 298dc244fe449fbfcba231127fcf4c02133c8a97..c8e402fd0717a7b81c7cb50330ccd70ddf083961 100644 (file)
@@ -438,6 +438,7 @@ int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonD
 
 int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags);
 int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags);
+int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags);
 int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask);
 
 const char* user_storage_to_string(UserStorage t) _const_;
index 858b493d2f60c0dbb580a06a9ee02bb8b0064ab2..ad0c66d9695ab9bec08c14cfefa3661528c19f90 100644 (file)
@@ -17,6 +17,9 @@ enum {
         _COLOR_MAX,
 };
 
+#define varlink_idl_log(error, format, ...) log_debug_errno(error, "Varlink-IDL: " format, ##__VA_ARGS__)
+#define varlink_idl_log_full(level, error, format, ...) log_full_errno(level, error, "Varlink-IDL: " format, ##__VA_ARGS__)
+
 static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX]);
 
 static int varlink_idl_format_enum_values(
@@ -512,7 +515,7 @@ static int varlink_idl_subparse_token(
 
                 l = token_match(*p, allowed_delimiters, allowed_chars);
                 if (l == 0)
-                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters));
         }
 
         t = strndup(*p, l);
@@ -662,7 +665,7 @@ static int varlink_idl_subparse_field_type(
                 if (r < 0)
                         return r;
                 if (!token)
-                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
 
                 field->named_type = TAKE_PTR(token);
                 field->field_type = VARLINK_NAMED_TYPE;
@@ -704,7 +707,7 @@ static int varlink_idl_subparse_struct_or_enum(
         assert(n_fields);
 
         if (depth > DEPTH_MAX)
-                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX);
+                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX);
 
         while (state != STATE_DONE) {
                 _cleanup_free_ char *token = NULL;
@@ -723,9 +726,9 @@ static int varlink_idl_subparse_struct_or_enum(
 
                 case STATE_OPEN:
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
                         if (!streq(token, "("))
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
 
                         state = STATE_NAME;
                         allowed_delimiters = ")";
@@ -736,7 +739,7 @@ static int varlink_idl_subparse_struct_or_enum(
                         assert(!field_name);
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
                         if (streq(token, ")"))
                                 state = STATE_DONE;
                         else {
@@ -752,7 +755,7 @@ static int varlink_idl_subparse_struct_or_enum(
                         assert(field_name);
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
 
                         if (streq(token, ":")) {
                                 VarlinkField *field;
@@ -760,7 +763,7 @@ static int varlink_idl_subparse_struct_or_enum(
                                 if ((*symbol)->symbol_type < 0)
                                         (*symbol)->symbol_type = VARLINK_STRUCT_TYPE;
                                 if ((*symbol)->symbol_type == VARLINK_ENUM_TYPE)
-                                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column);
+                                        return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column);
 
                                 r = varlink_symbol_realloc(symbol, *n_fields + 1);
                                 if (r < 0)
@@ -787,7 +790,7 @@ static int varlink_idl_subparse_struct_or_enum(
                                 if ((*symbol)->symbol_type < 0)
                                         (*symbol)->symbol_type = VARLINK_ENUM_TYPE;
                                 if ((*symbol)->symbol_type != VARLINK_ENUM_TYPE)
-                                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column);
+                                        return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column);
 
                                 r = varlink_symbol_realloc(symbol, *n_fields + 1);
                                 if (r < 0)
@@ -808,7 +811,7 @@ static int varlink_idl_subparse_struct_or_enum(
                                         state = STATE_DONE;
                                 }
                         } else
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
 
                         break;
 
@@ -816,7 +819,7 @@ static int varlink_idl_subparse_struct_or_enum(
                         assert(!field_name);
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
                         if (streq(token, ",")) {
                                 state = STATE_NAME;
                                 allowed_delimiters = NULL;
@@ -824,7 +827,7 @@ static int varlink_idl_subparse_struct_or_enum(
                         } else if (streq(token, ")"))
                                 state = STATE_DONE;
                         else
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
                         break;
 
                 default:
@@ -835,7 +838,7 @@ static int varlink_idl_subparse_struct_or_enum(
         /* If we don't know the type of the symbol by now it was an empty () which doesn't allow us to
          * determine if we look at an enum or a struct */
         if ((*symbol)->symbol_type < 0)
-                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column);
+                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column);
 
         return 0;
 }
@@ -854,14 +857,14 @@ static int varlink_idl_resolve_symbol_types(VarlinkInterface *interface, Varlink
                         continue;
 
                 if (!field->named_type)
-                        return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name.");
+                        return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name.");
 
                 found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->named_type);
                 if (!found)
-                        return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type);
+                        return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type);
 
                 if (!IN_SET(found->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE))
-                        return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type);
+                        return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type);
 
                 field->symbol = found;
         }
@@ -932,7 +935,7 @@ int varlink_idl_parse(
 
                 case STATE_PRE_INTERFACE:
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
                         if (streq(token, "#")) {
                                 r = varlink_idl_subparse_comment(&text, line, column);
                                 if (r < 0)
@@ -942,7 +945,7 @@ int varlink_idl_parse(
                                 allowed_delimiters = NULL;
                                 allowed_chars = VALID_CHARS_INTERFACE_NAME;
                         } else
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
                         break;
 
                 case STATE_INTERFACE:
@@ -950,7 +953,7 @@ int varlink_idl_parse(
                         assert(n_symbols == 0);
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
 
                         r = varlink_interface_realloc(&interface, n_symbols);
                         if (r < 0)
@@ -982,7 +985,7 @@ int varlink_idl_parse(
                                 state = STATE_ERROR;
                                 allowed_chars = VALID_CHARS_IDENTIFIER;
                         } else
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
 
                         break;
 
@@ -991,7 +994,7 @@ int varlink_idl_parse(
                         n_fields = 0;
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
 
                         r = varlink_symbol_realloc(&symbol, n_fields);
                         if (r < 0)
@@ -1012,10 +1015,10 @@ int varlink_idl_parse(
                         assert(symbol);
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
 
                         if (!streq(token, "->"))
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
 
                         r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_OUTPUT, 0);
                         if (r < 0)
@@ -1036,7 +1039,7 @@ int varlink_idl_parse(
                         n_fields = 0;
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
 
                         r = varlink_symbol_realloc(&symbol, n_fields);
                         if (r < 0)
@@ -1064,7 +1067,7 @@ int varlink_idl_parse(
                         n_fields = 0;
 
                         if (!token)
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
 
                         r = varlink_symbol_realloc(&symbol, n_fields);
                         if (r < 0)
@@ -1213,48 +1216,48 @@ static int varlink_idl_field_consistent(
         symbol_name = symbol->name ?: "<anonymous>";
 
         if (field->field_type <= 0 || field->field_type >= _VARLINK_FIELD_TYPE_MAX)
-                return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name);
+                return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name);
 
         if (field->field_type == VARLINK_ENUM_VALUE) {
 
                 if (symbol->symbol_type != VARLINK_ENUM_TYPE)
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name);
 
                 if (field->field_flags != 0)
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name);
         } else {
                 if (symbol->symbol_type == VARLINK_ENUM_TYPE)
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name);
 
                 if (!IN_SET(field->field_flags & ~VARLINK_NULLABLE, 0, VARLINK_ARRAY, VARLINK_MAP))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name);
         }
 
         if (symbol->symbol_type != VARLINK_METHOD) {
                 if (field->field_direction != VARLINK_REGULAR)
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name);
         } else {
                 if (!IN_SET(field->field_direction, VARLINK_INPUT, VARLINK_OUTPUT))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name);
         }
 
         if (field->symbol) {
                 if (!IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
 
                 if (field->field_type == VARLINK_NAMED_TYPE) {
                         const VarlinkSymbol *found;
 
                         if (!field->symbol->name || !field->named_type || !streq(field->symbol->name, field->named_type))
-                                return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name);
+                                return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name);
 
                         /* If this is a named type, then check if it's properly part of the interface */
                         found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->symbol->name);
                         if (!found)
-                                return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name);
+                                return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name);
 
                         if (!IN_SET(found->symbol_type, VARLINK_ENUM_TYPE, VARLINK_STRUCT_TYPE))
-                                return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name);
+                                return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name);
                 } else {
                         /* If this is an anonymous type, then we recursively check if it's consistent, since
                          * it's not part of the interface, and hence we won't validate it from there. */
@@ -1266,18 +1269,18 @@ static int varlink_idl_field_consistent(
 
         } else {
                 if (IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
 
                 if (field->named_type)
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name);
         }
 
         if (field->named_type) {
                 if (field->field_type != VARLINK_NAMED_TYPE)
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name);
         } else {
                 if (field->field_type == VARLINK_NAMED_TYPE)
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name);
         }
 
         return 0;
@@ -1304,19 +1307,19 @@ static int varlink_idl_symbol_consistent(
         symbol_name = symbol->name ?: "<anonymous>";
 
         if (symbol->symbol_type < 0 || symbol->symbol_type >= _VARLINK_SYMBOL_TYPE_MAX)
-                return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name);
+                return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name);
 
         if (IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE) && varlink_symbol_is_empty(symbol))
-                return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
+                return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
 
         for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
                 Set **name_set = field->field_direction == VARLINK_OUTPUT ? &output_set : &input_set; /* for the method case we need two separate sets, otherwise we use the same */
 
                 if (!varlink_idl_field_name_is_valid(field->name))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name);
 
                 if (set_contains(*name_set, field->name))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name);
 
                 if (set_ensure_put(name_set, &string_hash_ops, field->name) < 0)
                         return log_oom();
@@ -1336,15 +1339,15 @@ int varlink_idl_consistent(const VarlinkInterface *interface, int level) {
         assert(interface);
 
         if (!varlink_idl_interface_name_is_valid(interface->name))
-                return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name);
+                return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name);
 
         for (const VarlinkSymbol *const *symbol = interface->symbols; *symbol; symbol++) {
 
                 if (!varlink_idl_symbol_name_is_valid((*symbol)->name))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
 
                 if (set_contains(name_set, (*symbol)->name))
-                        return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name);
+                        return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name);
 
                 if (set_ensure_put(&name_set, &string_hash_ops, (*symbol)->name) < 0)
                         return log_oom();
@@ -1371,31 +1374,31 @@ static int varlink_idl_validate_field_element_type(const VarlinkField *field, Js
 
         case VARLINK_BOOL:
                 if (!json_variant_is_boolean(v))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name));
 
                 break;
 
         case VARLINK_INT:
                 if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name));
 
                 break;
 
         case VARLINK_FLOAT:
                 if (!json_variant_is_number(v))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name));
 
                 break;
 
         case VARLINK_STRING:
                 if (!json_variant_is_string(v))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name));
 
                 break;
 
         case VARLINK_OBJECT:
                 if (!json_variant_is_object(v))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
 
                 break;
 
@@ -1414,13 +1417,13 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v)
         if (!v || json_variant_is_null(v)) {
 
                 if (!FLAGS_SET(field->field_flags, VARLINK_NULLABLE))
-                        return log_debug_errno(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name));
 
         } else if (FLAGS_SET(field->field_flags, VARLINK_ARRAY)) {
                 JsonVariant *i;
 
                 if (!json_variant_is_array(v))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name));
 
                 JSON_VARIANT_ARRAY_FOREACH(i, v) {
                         r = varlink_idl_validate_field_element_type(field, i);
@@ -1433,7 +1436,7 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v)
                 JsonVariant *e;
 
                 if (!json_variant_is_object(v))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
 
                 JSON_VARIANT_OBJECT_FOREACH(k, e, v) {
                         r = varlink_idl_validate_field_element_type(field, e);
@@ -1458,7 +1461,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
         if (!v) {
                 if (bad_field)
                         *bad_field = NULL;
-                return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
+                return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
         }
 
         switch (symbol->symbol_type) {
@@ -1470,7 +1473,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
                 if (!json_variant_is_string(v)) {
                         if (bad_field)
                                 *bad_field = symbol->name;
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name));
                 }
 
                 assert_se(s = json_variant_string(v));
@@ -1488,7 +1491,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
                 if (!found) {
                         if (bad_field)
                                 *bad_field = s;
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name));
                 }
 
                 break;
@@ -1500,7 +1503,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
                 if (!json_variant_is_object(v)) {
                         if (bad_field)
                                 *bad_field = symbol->name;
-                        return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name));
+                        return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name));
                 }
 
                 for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
@@ -1522,7 +1525,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
                         if (!varlink_idl_find_field(symbol, name)) {
                                 if (bad_field)
                                         *bad_field = name;
-                                return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name);
+                                return varlink_idl_log(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name);
                         }
                 }
 
index ec25e26e29591ce8b4994c2bbf5094dc82f3cb71..7477d42de863a54c097e0abe94acd6974920acc9 100644 (file)
@@ -7,11 +7,12 @@ static VARLINK_DEFINE_METHOD(GetStates,
                              VARLINK_DEFINE_OUTPUT(IPv4AddressState, VARLINK_STRING, 0),
                              VARLINK_DEFINE_OUTPUT(IPv6AddressState, VARLINK_STRING, 0),
                              VARLINK_DEFINE_OUTPUT(CarrierState, VARLINK_STRING, 0),
-                             VARLINK_DEFINE_OUTPUT(OnlineState, VARLINK_STRING, 0),
+                             VARLINK_DEFINE_OUTPUT(OnlineState, VARLINK_STRING, VARLINK_NULLABLE),
                              VARLINK_DEFINE_OUTPUT(OperationalState, VARLINK_STRING, 0));
 
 static VARLINK_DEFINE_METHOD(GetNamespaceId,
-                             VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0));
+                             VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0),
+                             VARLINK_DEFINE_OUTPUT(NamespaceNSID, VARLINK_INT, VARLINK_NULLABLE));
 
 VARLINK_DEFINE_INTERFACE(
                 io_systemd_Network,
index 96a58ca768d1b25c686cb6d0206390aa0b1793db..e46d1975d7bf0997b6386a76ac804a859633e560 100644 (file)
@@ -59,7 +59,9 @@ VARLINK_DEFINE_STRUCT_TYPE(
                 VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE),
                 VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE),
                 VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE),
-                VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE));
+                VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE),
+                VARLINK_DEFINE_FIELD(target, VARLINK_STRING, VARLINK_NULLABLE),
+                VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY));
 
 VARLINK_DEFINE_STRUCT_TYPE(
                 ResourceRecordArray,
index 67ed7652336e5d344b80faee04977b963e895c30..3bce80e202e60b687b2ca8f4042d25535d7117f7 100644 (file)
@@ -184,6 +184,7 @@ struct Varlink {
         bool allow_fd_passing_output:1;
 
         bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */
+        bool input_sensitive:1; /* Whether incoming messages might be sensitive */
 
         int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */
 
@@ -703,7 +704,7 @@ static void varlink_clear(Varlink *v) {
 
         varlink_clear_current(v);
 
-        v->input_buffer = mfree(v->input_buffer);
+        v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer);
         v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer);
 
         v->input_control_buffer = mfree(v->input_control_buffer);
@@ -1022,7 +1023,8 @@ static int varlink_read(Varlink *v) {
 }
 
 static int varlink_parse_message(Varlink *v) {
-        const char *e, *begin;
+        const char *e;
+        char *begin;
         size_t sz;
         int r;
 
@@ -1047,6 +1049,8 @@ static int varlink_parse_message(Varlink *v) {
         sz = e - begin + 1;
 
         r = json_parse(begin, 0, &v->current, NULL, NULL);
+        if (v->input_sensitive)
+                explicit_bzero_safe(begin, sz);
         if (r < 0) {
                 /* If we encounter a parse failure flush all data. We cannot possibly recover from this,
                  * hence drop all buffered data now. */
@@ -1054,6 +1058,24 @@ static int varlink_parse_message(Varlink *v) {
                 return varlink_log_errno(v, r, "Failed to parse JSON: %m");
         }
 
+        if (v->input_sensitive) {
+                /* Mark the parameters subfield as sensitive right-away, if that's requested */
+                JsonVariant *parameters = json_variant_by_key(v->current, "parameters");
+                if (parameters)
+                        json_variant_sensitive(parameters);
+        }
+
+        if (DEBUG_LOGGING) {
+                _cleanup_(erase_and_freep) char *censored_text = NULL;
+
+                /* Suppress sensitive fields in the debug output */
+                r = json_variant_format(v->current, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
+                if (r < 0)
+                        return r;
+
+                varlink_log(v, "Received message: %s", censored_text);
+        }
+
         v->input_buffer_size -= sz;
 
         if (v->input_buffer_size == 0)
@@ -1849,56 +1871,60 @@ Varlink* varlink_flush_close_unref(Varlink *v) {
 
 static int varlink_format_json(Varlink *v, JsonVariant *m) {
         _cleanup_(erase_and_freep) char *text = NULL;
-        bool sensitive = false;
-        int r;
+        int sz, r;
 
         assert(v);
         assert(m);
 
-        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');
+        sz = json_variant_format(m, /* flags= */ 0, &text);
+        if (sz < 0)
+                return sz;
+        assert(text[sz] == '\0');
 
-        if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX)
+        if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX)
                 return -ENOBUFS;
 
-        varlink_log(v, "Sending message: %s", sensitive ? "<sensitive data>" : text);
+        if (DEBUG_LOGGING) {
+                _cleanup_(erase_and_freep) char *censored_text = NULL;
+
+                /* Suppress sensitive fields in the debug output */
+                r = json_variant_format(m, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
+                if (r < 0)
+                        return r;
+
+                varlink_log(v, "Sending message: %s", censored_text);
+        }
 
         if (v->output_buffer_size == 0) {
 
                 free_and_replace(v->output_buffer, text);
 
-                v->output_buffer_size = r + 1;
+                v->output_buffer_size = sz + 1;
                 v->output_buffer_index = 0;
 
         } else if (v->output_buffer_index == 0) {
 
-                if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + r + 1))
+                if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1))
                         return -ENOMEM;
 
-                memcpy(v->output_buffer + v->output_buffer_size, text, r + 1);
-                v->output_buffer_size += r + 1;
+                memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1);
+                v->output_buffer_size += sz + 1;
         } else {
                 char *n;
-                const size_t new_size = v->output_buffer_size + r + 1;
+                const size_t new_size = v->output_buffer_size + sz + 1;
 
                 n = new(char, new_size);
                 if (!n)
                         return -ENOMEM;
 
-                memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, r + 1);
+                memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1);
 
                 free_and_replace(v->output_buffer, n);
                 v->output_buffer_size = new_size;
                 v->output_buffer_index = 0;
         }
 
-        if (sensitive)
+        if (json_variant_is_sensitive_recursive(m))
                 v->output_buffer_sensitive = true; /* Propagate sensitive flag */
         else
                 text = mfree(text); /* No point in the erase_and_free() destructor declared above */
@@ -2127,7 +2153,7 @@ int varlink_observeb(Varlink *v, const char *method, ...) {
         return varlink_observe(v, method, parameters);
 }
 
-int varlink_call(
+int varlink_call_full(
                 Varlink *v,
                 const char *method,
                 JsonVariant *parameters,
@@ -2185,21 +2211,29 @@ int varlink_call(
 
         switch (v->state) {
 
-        case VARLINK_CALLED:
+        case VARLINK_CALLED: {
                 assert(v->current);
 
                 varlink_set_state(v, VARLINK_IDLE_CLIENT);
                 assert(v->n_pending == 1);
                 v->n_pending--;
 
+                JsonVariant *e = json_variant_by_key(v->current, "error"),
+                        *p = json_variant_by_key(v->current, "parameters");
+
+                /* If caller doesn't ask for the error string, then let's return an error code in case of failure */
+                if (!ret_error_id && e)
+                        return varlink_error_to_errno(json_variant_string(e), p);
+
                 if (ret_parameters)
-                        *ret_parameters = json_variant_by_key(v->current, "parameters");
+                        *ret_parameters = p;
                 if (ret_error_id)
-                        *ret_error_id = json_variant_string(json_variant_by_key(v->current, "error"));
+                        *ret_error_id = e ? json_variant_string(e) : NULL;
                 if (ret_flags)
                         *ret_flags = 0;
 
                 return 1;
+        }
 
         case VARLINK_PENDING_DISCONNECT:
         case VARLINK_DISCONNECTED:
@@ -2213,27 +2247,73 @@ int varlink_call(
         }
 }
 
-int varlink_callb(
+int varlink_callb_ap(
                 Varlink *v,
                 const char *method,
                 JsonVariant **ret_parameters,
                 const char **ret_error_id,
-                VarlinkReplyFlags *ret_flags, ...) {
+                VarlinkReplyFlags *ret_flags,
+                va_list ap) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(method, -EINVAL);
+
+        r = json_buildv(&parameters, ap);
+        if (r < 0)
+                return varlink_log_errno(v, r, "Failed to build json message: %m");
+
+        return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+}
+
+int varlink_call_and_log(
+                Varlink *v,
+                const char *method,
+                JsonVariant *parameters,
+                JsonVariant **ret_parameters) {
+
+        JsonVariant *reply = NULL;
+        const char *error_id = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(method, -EINVAL);
+
+        r = varlink_call(v, method, parameters, &reply, &error_id);
+        if (r < 0)
+                return log_error_errno(r, "Failed to issue %s() varlink call: %m", method);
+        if (error_id)
+                return log_error_errno(varlink_error_to_errno(error_id, reply),
+                                         "Failed to issue %s() varlink call: %s", method, error_id);
+
+        if (ret_parameters)
+                *ret_parameters = TAKE_PTR(reply);
+
+        return 0;
+}
+
+int varlink_callb_and_log(
+                Varlink *v,
+                const char *method,
+                JsonVariant **ret_parameters,
+                ...) {
 
         _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
         va_list ap;
         int r;
 
         assert_return(v, -EINVAL);
+        assert_return(method, -EINVAL);
 
-        va_start(ap, ret_flags);
+        va_start(ap, ret_parameters);
         r = json_buildv(&parameters, ap);
         va_end(ap);
-
         if (r < 0)
-                return varlink_log_errno(v, r, "Failed to build json message: %m");
+                return log_error_errno(r, "Failed to build JSON message: %m");
 
-        return varlink_call(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+        return varlink_call_and_log(v, method, parameters, ret_parameters);
 }
 
 static void varlink_collect_context_free(VarlinkCollectContext *cc) {
@@ -2259,6 +2339,10 @@ static int collect_callback(
         /* If we hit an error, we will drop all collected replies and just return the error_id and flags in varlink_collect() */
         if (error_id) {
                 context->error_id = error_id;
+
+                json_variant_unref(context->parameters);
+                context->parameters = json_variant_ref(parameters);
+
                 return 0;
         }
 
@@ -2311,6 +2395,13 @@ int varlink_collect(
 
                 /* If we get an error from any of the replies, return immediately with just the error_id and flags*/
                 if (context.error_id) {
+
+                        /* If caller doesn't ask for the error string, then let's return an error code in case of failure */
+                        if (!ret_error_id)
+                                return varlink_error_to_errno(context.error_id, context.parameters);
+
+                        if (ret_parameters)
+                                *ret_parameters = TAKE_PTR(context.parameters);
                         if (ret_error_id)
                                 *ret_error_id = TAKE_PTR(context.error_id);
                         if (ret_flags)
@@ -2342,6 +2433,9 @@ int varlink_collect(
                 assert_not_reached();
         }
 
+        if (!ret_error_id && context.error_id)
+                return varlink_error_to_errno(context.error_id, context.parameters);
+
         if (ret_parameters)
                 *ret_parameters = TAKE_PTR(context.parameters);
         if (ret_error_id)
@@ -3097,6 +3191,13 @@ int varlink_set_allow_fd_passing_output(Varlink *v, bool b) {
         return 0;
 }
 
+int varlink_set_input_sensitive(Varlink *v) {
+        assert_return(v, -EINVAL);
+
+        v->input_sensitive = true;
+        return 0;
+}
+
 int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
         _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
         int r;
@@ -3325,6 +3426,9 @@ static int connect_callback(sd_event_source *source, int fd, uint32_t revents, v
 
         TAKE_FD(cfd);
 
+        if (FLAGS_SET(ss->server->flags, VARLINK_SERVER_INPUT_SENSITIVE))
+                varlink_set_input_sensitive(v);
+
         if (ss->server->connect_callback) {
                 r = ss->server->connect_callback(ss->server, v, ss->server->userdata);
                 if (r < 0) {
@@ -3477,6 +3581,14 @@ int varlink_server_listen_auto(VarlinkServer *s) {
                 n++;
         }
 
+        /* For debug purposes let's listen on an explicitly specified address */
+        const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN");
+        if (e) {
+                r = varlink_server_listen_address(s, e, FLAGS_SET(s->flags, VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666);
+                if (r < 0)
+                        return r;
+        }
+
         return n;
 }
 
@@ -3922,6 +4034,10 @@ int varlink_invocation(VarlinkInvocationFlags flags) {
 
         /* Returns true if this is a "pure" varlink server invocation, i.e. with one fd passed. */
 
+        const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); /* Permit a manual override for testing purposes */
+        if (e)
+                return true;
+
         r = sd_listen_fds_with_names(/* unset_environment= */ false, &names);
         if (r < 0)
                 return r;
@@ -3949,3 +4065,42 @@ int varlink_invocation(VarlinkInvocationFlags flags) {
 
         return true;
 }
+
+int varlink_error_to_errno(const char *error, JsonVariant *parameters) {
+        static const struct {
+                const char *error;
+                int value;
+        } table[] = {
+                { VARLINK_ERROR_DISCONNECTED,           -ECONNRESET    },
+                { VARLINK_ERROR_TIMEOUT,                -ETIMEDOUT     },
+                { VARLINK_ERROR_PROTOCOL,               -EPROTO        },
+                { VARLINK_ERROR_INTERFACE_NOT_FOUND,    -EADDRNOTAVAIL },
+                { VARLINK_ERROR_METHOD_NOT_FOUND,       -ENXIO         },
+                { VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, -ENOTTY        },
+                { VARLINK_ERROR_INVALID_PARAMETER,      -EINVAL        },
+                { VARLINK_ERROR_PERMISSION_DENIED,      -EACCES        },
+                { VARLINK_ERROR_EXPECTED_MORE,          -EBADE         },
+        };
+
+        if (!error)
+                return 0;
+
+        FOREACH_ARRAY(t, table, ELEMENTSOF(table))
+                if (streq(error, t->error))
+                        return t->value;
+
+        if (streq(error, VARLINK_ERROR_SYSTEM) && parameters) {
+                JsonVariant *e;
+
+                e = json_variant_by_key(parameters, "errno");
+                if (json_variant_is_integer(e)) {
+                        int64_t i;
+
+                        i = json_variant_integer(e);
+                        if (i > 0 && i < ERRNO_MAX)
+                                return -i;
+                }
+        }
+
+        return -EBADR; /* Catch-all */
+}
index a971762a511c65ed9b8af5c4f80e4e3804886180..622ab797c5a07b62492941a64ec31925f40405c8 100644 (file)
@@ -47,7 +47,8 @@ typedef enum VarlinkServerFlags {
         VARLINK_SERVER_MYSELF_ONLY      = 1 << 1, /* Only accessible by our own UID */
         VARLINK_SERVER_ACCOUNT_UID      = 1 << 2, /* Do per user accounting */
         VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */
-        _VARLINK_SERVER_FLAGS_ALL = (1 << 4) - 1,
+        VARLINK_SERVER_INPUT_SENSITIVE  = 1 << 4, /* Automatically mark al connection input as sensitive */
+        _VARLINK_SERVER_FLAGS_ALL = (1 << 5) - 1,
 } VarlinkServerFlags;
 
 typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
@@ -87,8 +88,32 @@ int varlink_send(Varlink *v, const char *method, JsonVariant *parameters);
 int varlink_sendb(Varlink *v, const char *method, ...);
 
 /* Send method call and wait for reply */
-int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
-int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...);
+int varlink_call_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
+static inline int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) {
+        return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
+}
+int varlink_call_and_log(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters);
+
+int varlink_callb_ap(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, va_list ap);
+static inline int varlink_callb_full(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, ret_flags);
+        r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, ret_flags, ap);
+        va_end(ap);
+        return r;
+}
+static inline int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, ret_error_id);
+        r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, NULL, ap);
+        va_end(ap);
+        return r;
+}
+int varlink_callb_and_log(Varlink *v, const char *method, JsonVariant **ret_parameters, ...);
 
 /* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */
 int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
@@ -154,6 +179,9 @@ VarlinkServer* varlink_get_server(Varlink *v);
 
 int varlink_set_description(Varlink *v, const char *d);
 
+/* Automatically mark the parameters part of incoming messages as security sensitive */
+int varlink_set_input_sensitive(Varlink *v);
+
 /* Create a varlink server */
 int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags);
 VarlinkServer *varlink_server_ref(VarlinkServer *s);
@@ -209,6 +237,8 @@ typedef enum VarlinkInvocationFlags {
 
 int varlink_invocation(VarlinkInvocationFlags flags);
 
+int varlink_error_to_errno(const char *error, JsonVariant *parameters);
+
 DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref);
 DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref);
 DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref);
index 70a706f2aa8de647e1c08f9ade62897e17d4e512..4cc64647ed75f27e10f7408ad5f38ee5dd89999e 100644 (file)
@@ -11,15 +11,17 @@ executables += [
         },
 ]
 
-custom_target(
-        '20-systemd-ssh-proxy.conf',
-        input : '20-systemd-ssh-proxy.conf.in',
-        output : '20-systemd-ssh-proxy.conf',
-        command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
-        install : true,
-        install_dir : libexecdir / 'ssh_config.d')
+if sshconfdir != 'no'
+        custom_target(
+                '20-systemd-ssh-proxy.conf',
+                input : '20-systemd-ssh-proxy.conf.in',
+                output : '20-systemd-ssh-proxy.conf',
+                command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
+                install : true,
+                install_dir : libexecdir / 'ssh_config.d')
 
-install_emptydir(sshconfdir)
+        install_emptydir(sshconfdir)
 
-meson.add_install_script(sh, '-c',
-        ln_s.format(libexecdir / 'ssh_config.d' / '20-systemd-ssh-proxy.conf', sshconfdir / '20-systemd-ssh-proxy.conf'))
+        meson.add_install_script(sh, '-c',
+                ln_s.format(libexecdir / 'ssh_config.d' / '20-systemd-ssh-proxy.conf', sshconfdir / '20-systemd-ssh-proxy.conf'))
+endif
index feb967be747160f0d664bc6af8502397e37b7a89..2c4cf5a16a5a5f775ca67a0e8a6b0c4c538d4231 100644 (file)
@@ -297,7 +297,7 @@ static int add_export_unix_socket(
                         return 0;
                 }
 
-                return log_debug_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
+                return log_error_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
         }
 
         r = make_sshd_template_unit(
index 6b521c93476bfe80aad169d922f65f0d0ec76c07..59be6a7a7ecfef7a78520d9ff1958888afa4c88c 100644 (file)
@@ -29,7 +29,7 @@ int verb_is_system_running(int argc, char *argv[], void *userdata) {
         sd_bus *bus;
         int r;
 
-        if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) {
+        if (!isempty(arg_root) || running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) {
                 if (!arg_quiet)
                         puts("offline");
                 return EXIT_FAILURE;
index c74bcf2642e2b1b2700e8dec253d69a67fc9eac7..5bb8ad9fa07b5f4a841fdf27e195393deb8ee5f1 100644 (file)
@@ -932,11 +932,7 @@ static int map_listen(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus
 
         while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) {
 
-                r = strv_extend(p, type);
-                if (r < 0)
-                        return r;
-
-                r = strv_extend(p, path);
+                r = strv_extend_many(p, type, path);
                 if (r < 0)
                         return r;
         }
index b220fa0113d62a7944618b8b2db99b5b1219c2ab..e3f68068a8437caedd4cdee78bdffedd4ade479f 100644 (file)
@@ -267,6 +267,11 @@ _SD_BEGIN_DECLARATIONS;
 #define SD_MESSAGE_SYSV_GENERATOR_DEPRECATED          SD_ID128_MAKE(a8,fa,8d,ac,db,1d,44,3e,95,03,b8,be,36,7a,6a,db)
 #define SD_MESSAGE_SYSV_GENERATOR_DEPRECATED_STR      SD_ID128_MAKE_STR(a8,fa,8d,ac,db,1d,44,3e,95,03,b8,be,36,7a,6a,db)
 
+#define SD_MESSAGE_PORTABLE_ATTACHED                  SD_ID128_MAKE(18,7c,62,eb,1e,7f,46,3b,b5,30,39,4f,52,cb,09,0f)
+#define SD_MESSAGE_PORTABLE_ATTACHED_STR              SD_ID128_MAKE_STR(18,7c,62,eb,1e,7f,46,3b,b5,30,39,4f,52,cb,09,0f)
+#define SD_MESSAGE_PORTABLE_DETACHED                  SD_ID128_MAKE(76,c5,c7,54,d6,28,49,0d,8e,cb,a4,c9,d0,42,11,2b)
+#define SD_MESSAGE_PORTABLE_DETACHED_STR              SD_ID128_MAKE_STR(76,c5,c7,54,d6,28,49,0d,8e,cb,a4,c9,d0,42,11,2b)
+
 _SD_END_DECLARATIONS;
 
 #endif
index 3f93e3a40682849f673ae689b77fad030cba739a..a5ccd5f6449107585406e701f63833366bee28ed 100644 (file)
@@ -96,6 +96,7 @@ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret);
 int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret);
 int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret);
 int sd_ndisc_router_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);
+int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret);
 int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret);
 
 /* Generic option access */
index 240d21f9cadc1a95a0ef3b1d07cf71ebf571be96..34db2bbf1cc9376addbaa7193552980c80b69102 100644 (file)
@@ -216,6 +216,8 @@ int sd_rtnl_message_traffic_control_get_parent(sd_netlink_message *m, uint32_t *
 
 int sd_rtnl_message_new_mdb(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int mdb_ifindex);
 
+int sd_rtnl_message_new_nsid(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type);
+
 /* genl */
 int sd_genl_socket_open(sd_netlink **ret);
 int sd_genl_message_new(sd_netlink *genl, const char *family_name, uint8_t cmd, sd_netlink_message **ret);
index e7dcb865c53f0d944fe7616a6a235d289dfafa6b..96fb6f8ad556e49b6fab7a02968ba1c940a01053 100644 (file)
@@ -141,12 +141,6 @@ static void context_done(Context *c) {
         uid_range_free(c->uid_range);
 }
 
-static int errno_is_not_exists(int code) {
-        /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
-         * not found. */
-        return IN_SET(code, 0, ENOENT, ESRCH, EBADF, EPERM);
-}
-
 /* Note: the lifetime of the compound literal is the immediately surrounding block,
  * see C11 §6.5.2.5, and
  * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */
@@ -347,6 +341,7 @@ static int putgrent_with_members(
                 FILE *group) {
 
         char **a;
+        int r;
 
         assert(c);
         assert(gr);
@@ -365,15 +360,15 @@ static int putgrent_with_members(
                         if (strv_contains(l, *i))
                                 continue;
 
-                        if (strv_extend(&l, *i) < 0)
-                                return -ENOMEM;
+                        r = strv_extend(&l, *i);
+                        if (r < 0)
+                                return r;
 
                         added = true;
                 }
 
                 if (added) {
                         struct group t;
-                        int r;
 
                         strv_uniq(l);
                         strv_sort(l);
@@ -396,6 +391,7 @@ static int putsgent_with_members(
                 FILE *gshadow) {
 
         char **a;
+        int r;
 
         assert(sg);
         assert(gshadow);
@@ -413,15 +409,15 @@ static int putsgent_with_members(
                         if (strv_contains(l, *i))
                                 continue;
 
-                        if (strv_extend(&l, *i) < 0)
-                                return -ENOMEM;
+                        r = strv_extend(&l, *i);
+                        if (r < 0)
+                                return r;
 
                         added = true;
                 }
 
                 if (added) {
                         struct sgrp t;
-                        int r;
 
                         strv_uniq(l);
                         strv_sort(l);
@@ -1026,6 +1022,7 @@ static int uid_is_ok(
                 const char *name,
                 bool check_with_gid) {
 
+        int r;
         assert(c);
 
         /* Let's see if we already have assigned the UID a second time */
@@ -1056,24 +1053,21 @@ static int uid_is_ok(
 
         /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
         if (!arg_root) {
-                struct passwd *p;
-                struct group *g;
+                _cleanup_free_ struct group *g = NULL;
 
-                errno = 0;
-                p = getpwuid(uid);
-                if (p)
+                r = getpwuid_malloc(uid, /* ret= */ NULL);
+                if (r >= 0)
                         return 0;
-                if (!IN_SET(errno, 0, ENOENT))
-                        return -errno;
+                if (r != -ESRCH)
+                        return r;
 
                 if (check_with_gid) {
-                        errno = 0;
-                        g = getgrgid((gid_t) uid);
-                        if (g) {
+                        r = getgrgid_malloc((gid_t) uid, &g);
+                        if (r >= 0) {
                                 if (!streq(g->gr_name, name))
                                         return 0;
-                        } else if (!IN_SET(errno, 0, ENOENT))
-                                return -errno;
+                        } else if (r != -ESRCH)
+                                return r;
                 }
         }
 
@@ -1162,12 +1156,11 @@ static int add_user(Context *c, Item *i) {
         }
 
         if (!arg_root) {
-                struct passwd *p;
+                _cleanup_free_ struct passwd *p = NULL;
 
                 /* Also check NSS */
-                errno = 0;
-                p = getpwnam(i->name);
-                if (p) {
+                r = getpwnam_malloc(i->name, &p);
+                if (r >= 0) {
                         log_debug("User %s already exists.", i->name);
                         i->uid = p->pw_uid;
                         i->uid_set = true;
@@ -1178,8 +1171,8 @@ static int add_user(Context *c, Item *i) {
 
                         return 0;
                 }
-                if (!errno_is_not_exists(errno))
-                        return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name);
+                if (r != -ESRCH)
+                        return log_error_errno(r, "Failed to check if user %s already exists: %m", i->name);
         }
 
         /* Try to use the suggested numeric UID */
@@ -1268,10 +1261,9 @@ static int gid_is_ok(
                 const char *groupname,
                 bool check_with_uid) {
 
-        struct group *g;
-        struct passwd *p;
         Item *user;
         char *username;
+        int r;
 
         assert(c);
         assert(groupname);
@@ -1296,20 +1288,18 @@ static int gid_is_ok(
         }
 
         if (!arg_root) {
-                errno = 0;
-                g = getgrgid(gid);
-                if (g)
+                r = getgrgid_malloc(gid, /* ret= */ NULL);
+                if (r >= 0)
                         return 0;
-                if (!IN_SET(errno, 0, ENOENT))
-                        return -errno;
+                if (r != -ESRCH)
+                        return r;
 
                 if (check_with_uid) {
-                        errno = 0;
-                        p = getpwuid((uid_t) gid);
-                        if (p)
+                        r = getpwuid_malloc(gid, /* ret= */ NULL);
+                        if (r >= 0)
                                 return 0;
-                        if (!IN_SET(errno, 0, ENOENT))
-                                return -errno;
+                        if (r != -ESRCH)
+                                return r;
                 }
         }
 
@@ -1322,6 +1312,7 @@ static int get_gid_by_name(
                 gid_t *ret_gid) {
 
         void *z;
+        int r;
 
         assert(c);
         assert(ret_gid);
@@ -1335,16 +1326,15 @@ static int get_gid_by_name(
 
         /* Also check NSS */
         if (!arg_root) {
-                struct group *g;
+                _cleanup_free_ struct group *g = NULL;
 
-                errno = 0;
-                g = getgrnam(name);
-                if (g) {
+                r = getgrnam_malloc(name, &g);
+                if (r >= 0) {
                         *ret_gid = g->gr_gid;
                         return 0;
                 }
-                if (!errno_is_not_exists(errno))
-                        return log_error_errno(errno, "Failed to check if group %s already exists: %m", name);
+                if (r != -ESRCH)
+                        return log_error_errno(r, "Failed to check if group %s already exists: %m", name);
         }
 
         return -ENOENT;
index 57cb886c4111455785f3dd98958b61b863082173..24cb5f73eb78cfd85ad0291a70d33631c5032c38 100644 (file)
@@ -100,7 +100,7 @@ TEST(memdup_multiply_and_greedy_realloc) {
         size_t i;
         int *p;
 
-        dup = memdup_suffix0_multiply(org, sizeof(int), 3);
+        dup = memdup_suffix0_multiply(org, 3, sizeof(int));
         assert_se(dup);
         assert_se(dup[0] == 1);
         assert_se(dup[1] == 2);
@@ -108,7 +108,7 @@ TEST(memdup_multiply_and_greedy_realloc) {
         assert_se(((uint8_t*) dup)[sizeof(int) * 3] == 0);
         free(dup);
 
-        dup = memdup_multiply(org, sizeof(int), 3);
+        dup = memdup_multiply(org, 3, sizeof(int));
         assert_se(dup);
         assert_se(dup[0] == 1);
         assert_se(dup[1] == 2);
index e56a2f38e382fec6d5760b4da2819083aebf2dbf..e65aa819dd5751ee581522def3e60af6db20501d 100644 (file)
@@ -2,6 +2,8 @@
 
 #include "creds-util.h"
 #include "fileio.h"
+#include "format-util.h"
+#include "hexdecoct.h"
 #include "id128-util.h"
 #include "iovec-util.h"
 #include "path-util.h"
@@ -213,7 +215,33 @@ TEST(credential_encrypt_decrypt) {
 
         if (ec)
                 assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true) >= 0);
+}
+
+TEST(mime_type_matches) {
+
+        static const sd_id128_t tags[] = {
+                CRED_AES256_GCM_BY_HOST,
+                CRED_AES256_GCM_BY_TPM2_HMAC,
+                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_NULL,
+        };
+
+        /* Generates the right <match/> expressions for these credentials according to the shared mime-info spec */
+        FOREACH_ARRAY(t, tags, ELEMENTSOF(tags)) {
+                _cleanup_free_ char *encoded = NULL;
 
+                assert_se(base64mem(t, sizeof(sd_id128_t), &encoded) >= 0);
+
+                /* Validate that the size matches expectations for the 4/3 factor size increase (rounding up) */
+                assert_se(strlen(encoded) == DIV_ROUND_UP((128U / 8U), 3U) * 4U);
+
+                /* Cut off rounded string where the ID ends, but now round down to get rid of characters that might contain follow-up data */
+                encoded[128 / 6] = 0;
+
+                printf("<match type=\"string\" value=\"%s\" offset=\"0\"/>\n", encoded);
+        }
 }
 
 DEFINE_TEST_MAIN(LOG_INFO);
index 21786ae72aaa856cbeb07632ab444643da6cdfcd..364e0f395643ab54c7937b0cf5013ad2007fbe96 100644 (file)
@@ -239,4 +239,22 @@ TEST(octescape) {
         test_octescape_one("\123\213\222", "\123\\213\\222");
 }
 
+static void test_decescape_one(const char *s, const char *bad, const char *expected) {
+        _cleanup_free_ char *ret = NULL;
+
+        assert_se(ret = decescape(s, bad, strlen_ptr(s)));
+        log_debug("decescape(\"%s\") → \"%s\" (expected: \"%s\")", strnull(s), ret, expected);
+        assert_se(streq(ret, expected));
+}
+
+TEST(decescape) {
+        test_decescape_one(NULL, "bad", "");
+        test_decescape_one("foo", "", "foo");
+        test_decescape_one("foo", "f", "\\102oo");
+        test_decescape_one("foo", "o", "f\\111\\111");
+        test_decescape_one("go\"bb\\ledyg\x03ook\r\n", "", "go\\034bb\\092ledyg\\003ook\\013\\010");
+        test_decescape_one("\\xff\xff" "f", "f", "\\092x\\102\\102\\255\\102");
+        test_decescape_one("all", "all", "\\097\\108\\108");
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index 333fbe6cf2a30c320f13178dba1243b2c3598f17..cb0adc2643774d90640ff6bdb5168661bacbd765 100644 (file)
@@ -107,9 +107,9 @@ static void test_variant_one(const char *data, Test test) {
         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);
+        r = json_variant_format(w, JSON_FORMAT_CENSOR_SENSITIVE, &s);
+        assert_se(s);
+        assert_se(streq_ptr(s, "\"<sensitive data>\""));
 
         s = mfree(s);
         r = json_variant_format(w, JSON_FORMAT_PRETTY, &s);
@@ -886,10 +886,11 @@ TEST(json_sensitive) {
 
         json_variant_sensitive(a);
 
-        assert_se(json_variant_format(a, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
-        assert_se(!s);
+        assert_se(json_variant_format(a, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+        assert_se(streq_ptr(s, "\"<sensitive data>\""));
+        s = mfree(s);
 
-        r = json_variant_format(b, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+        r = json_variant_format(b, JSON_FORMAT_CENSOR_SENSITIVE, &s);
         assert_se(r >= 0);
         assert_se(s);
         assert_se((size_t) r == strlen(s));
@@ -901,7 +902,7 @@ TEST(json_sensitive) {
                                              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);
+        r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s);
         assert_se(r >= 0);
         assert_se(s);
         assert_se((size_t) r == strlen(s));
@@ -915,7 +916,7 @@ TEST(json_sensitive) {
                                              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);
+        r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s);
         assert_se(r >= 0);
         assert_se(s);
         assert_se((size_t) r == strlen(s));
@@ -930,8 +931,9 @@ TEST(json_sensitive) {
                                              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);
+        assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+        assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"a\":\"<sensitive data>\",\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{}}"));
+        s = mfree(s);
         v = json_variant_unref(v);
 
         assert_se(json_build(&v, JSON_BUILD_OBJECT(
@@ -942,8 +944,9 @@ TEST(json_sensitive) {
                                              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);
+        assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+        assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"a\":\"<sensitive data>\",\"d\":\"-9223372036854775808\",\"e\":{}}"));
+        s = mfree(s);
         v = json_variant_unref(v);
 
         assert_se(json_build(&v, JSON_BUILD_OBJECT(
@@ -954,8 +957,9 @@ TEST(json_sensitive) {
                                              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);
+        assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+        assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"a\":\"<sensitive data>\",\"e\":{}}"));
+        s = mfree(s);
         v = json_variant_unref(v);
 
         assert_se(json_build(&v, JSON_BUILD_OBJECT(
@@ -966,8 +970,8 @@ TEST(json_sensitive) {
                                              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);
+        assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+        assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{},\"a\":\"<sensitive data>\"}"));
 }
 
 TEST(json_iovec) {
index 6dbd50f4a1bf83bde1c0a1a1b4176293b6dbf6ca..ad642ca4b1facfe47b902bf95c15e8da500385c8 100644 (file)
@@ -369,4 +369,16 @@ TEST(in_addr_port_ifindex_name_from_string_auto) {
         test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo#hoge.com", AF_INET6, 53, 1, "hoge.com", "[fe80::18]:53%1#hoge.com");
 }
 
+TEST(netns_get_nsid) {
+        uint32_t u;
+        int r;
+
+        r = netns_get_nsid(-EBADF, &u);
+        assert_se(r == -ENODATA || r >= 0);
+        if (r == -ENODATA)
+                log_info("Our network namespace has no NSID assigned.");
+        else
+                log_info("Our NSID is %" PRIu32, u);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index f70e2aa862d3d1f607b8fc91975a7831aab320cb..da8721214a2f42ce9f8b97029032e50c691e9310 100644 (file)
@@ -1014,4 +1014,29 @@ TEST(endswith_strv) {
         assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("knurz", "", "waldo")), ""));
 }
 
+TEST(strv_extend_many) {
+        _cleanup_strv_free_ char **l = NULL;
+
+        assert_se(strv_extend_many(&l, NULL) >= 0);
+        assert_se(strv_isempty(l));
+
+        assert_se(strv_extend_many(&l, NULL, NULL, NULL) >= 0);
+        assert_se(strv_isempty(l));
+
+        assert_se(strv_extend_many(&l, "foo") >= 0);
+        assert_se(strv_equal(l, STRV_MAKE("foo")));
+
+        assert_se(strv_extend_many(&l, NULL, "bar", NULL) >= 0);
+        assert_se(strv_equal(l, STRV_MAKE("foo", "bar")));
+
+        assert_se(strv_extend_many(&l, "waldo", "quux") >= 0);
+        assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux")));
+
+        assert_se(strv_extend_many(&l, "1", "2", "3", "4") >= 0);
+        assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux", "1", "2", "3", "4")));
+
+        assert_se(strv_extend_many(&l, "yes", NULL, "no") >= 0);
+        assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux", "1", "2", "3", "4", "yes", "no")));
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);
index eeaf0b7b8854a36aea553c9aa91d566057473cbc..254c4c5e8bc2218a1a1baf4e44a3f7bc84d569db 100644 (file)
@@ -819,36 +819,75 @@ static void check_tpm2b_public_fingerprint(const TPM2B_PUBLIC *public, const cha
         assert_se(memcmp_nn(fp, fp_size, expected, expected_len) == 0);
 }
 
-TEST(tpm2b_public_from_openssl_pkey) {
-        TPM2B_PUBLIC public;
+static void check_tpm2b_public_name(const TPM2B_PUBLIC *public, const char *hexname) {
+        DEFINE_HEX_PTR(expected, hexname);
+        TPM2B_NAME name = {};
+
+        assert_se(tpm2_calculate_pubkey_name(&public->publicArea, &name) >= 0);
+        assert_se(memcmp_nn(name.name, name.size, expected, expected_len) == 0);
+}
+
+static void check_tpm2b_public_from_ecc_pem(const char *pem, const char *hexx, const char *hexy, const char *hexfp, const char *hexname) {
+        TPM2B_PUBLIC public = {};
         TPMT_PUBLIC *p = &public.publicArea;
 
-        DEFINE_HEX_PTR(key_ecc, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a");
-        get_tpm2b_public_from_pem(key_ecc, key_ecc_len, &public);
+        DEFINE_HEX_PTR(key, pem);
+        get_tpm2b_public_from_pem(key, key_len, &public);
 
         assert_se(p->type == TPM2_ALG_ECC);
         assert_se(p->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256);
 
-        DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de");
+        DEFINE_HEX_PTR(expected_x, hexx);
         assert_se(memcmp_nn(p->unique.ecc.x.buffer, p->unique.ecc.x.size, expected_x, expected_x_len) == 0);
 
-        DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5");
+        DEFINE_HEX_PTR(expected_y, hexy);
         assert_se(memcmp_nn(p->unique.ecc.y.buffer, p->unique.ecc.y.size, expected_y, expected_y_len) == 0);
 
-        check_tpm2b_public_fingerprint(&public, "cd3373293b62a52b48c12100e80ea9bfd806266ce76893a5ec31cb128052d97c");
+        check_tpm2b_public_fingerprint(&public, hexfp);
+        check_tpm2b_public_name(&public, hexname);
+}
+
+static void check_tpm2b_public_from_rsa_pem(const char *pem, const char *hexn, uint32_t exponent, const char *hexfp, const char *hexname) {
+        TPM2B_PUBLIC public = {};
+        TPMT_PUBLIC *p = &public.publicArea;
+
+        DEFINE_HEX_PTR(key, pem);
+        get_tpm2b_public_from_pem(key, key_len, &public);
 
-        DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a");
-        get_tpm2b_public_from_pem(key_rsa, key_rsa_len, &public);
+        assert_se(p->type == TPM2_ALG_RSA);
 
-        DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b");
-        assert_se(p->unique.rsa.size == expected_n_len);
-        assert_se(memcmp(p->unique.rsa.buffer, expected_n, expected_n_len) == 0);
+        DEFINE_HEX_PTR(expected_n, hexn);
+        assert_se(memcmp_nn(p->unique.rsa.buffer, p->unique.rsa.size, expected_n, expected_n_len) == 0);
 
         assert_se(p->parameters.rsaDetail.keyBits == expected_n_len * 8);
 
-        assert_se(p->parameters.rsaDetail.exponent == 0);
+        assert_se(p->parameters.rsaDetail.exponent == exponent);
 
-        check_tpm2b_public_fingerprint(&public, "d9186d13a7fd5b3644cee05448f49ad3574e82a2942ff93cf89598d36cca78a9");
+        check_tpm2b_public_fingerprint(&public, hexfp);
+        check_tpm2b_public_name(&public, hexname);
+}
+
+TEST(tpm2b_public_from_openssl_pkey) {
+        /* standard ECC key */
+        check_tpm2b_public_from_ecc_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a",
+                                        "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de",
+                                        "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5",
+                                        "cd3373293b62a52b48c12100e80ea9bfd806266ce76893a5ec31cb128052d97c",
+                                        "000b5c127e4dbaf8fb7bac641e8db25a84a48db876ca7ee3bd317ae1a4554ff72f17");
+
+        /* standard RSA key */
+        check_tpm2b_public_from_rsa_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a",
+                                        "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b",
+                                        0x10001,
+                                        "d9186d13a7fd5b3644cee05448f49ad3574e82a2942ff93cf89598d36cca78a9",
+                                        "000be1bd75c7976e7a30e9e82223b81a9eff0d42c30618e588db592ed5da94455e81");
+
+        /* RSA key with non-default (i.e. not 0x10001) exponent */
+        check_tpm2b_public_from_rsa_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b434151454179566c7551664b75565171596a5a71436a657a760a364e4a6f58654c736f702f72765375666330773769544d4f73566741557462515452505451725874397065537a4370524467634378656b6a544144577279304b0a6d59786a7a3634776c6a7030463959383068636a6b6b4b3759414d333054664c4648656c2b377574427370777142467a6e2b385a6659567353434b397354706f0a316c61376e5347514e7451576f36444a366c525a336a676d6d584f61544654416145304a432b7046584273564471736d46326438362f314e51714a755a5154520a575852636954704e58357649792f37766b6c5a6a685569526c78764e594f4e3070636476534a37364e74496e447a3048506f775a38705a454f4d2f4a454f59780a617a4c4a6a644936446b355279593578325a7949375074566a3057537242524f4d696f2b674c6556457a43343456336438315a38445138564e334c69625130330a70514944415141460a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a",
+                                        "c9596e41f2ae550a988d9a828decefe8d2685de2eca29febbd2b9f734c3b89330eb1580052d6d04d13d342b5edf69792cc2a510e0702c5e9234c00d6af2d0a998c63cfae30963a7417d63cd217239242bb600337d137cb1477a5fbbbad06ca70a811739fef197d856c4822bdb13a68d656bb9d219036d416a3a0c9ea5459de382699739a4c54c0684d090bea455c1b150eab2617677cebfd4d42a26e6504d159745c893a4d5f9bc8cbfeef925663854891971bcd60e374a5c76f489efa36d2270f3d073e8c19f2964438cfc910e6316b32c98dd23a0e4e51c98e71d99c88ecfb558f4592ac144e322a3e80b7951330b8e15dddf3567c0d0f153772e26d0d37a5",
+                                        0x10005,
+                                        "c8ca80a687d5972e1d961aaa2cfde2ff2e7a20d85e3ea0382804e70e013d65af",
+                                        "000beb8974d36d8cf58fdc87460dda00319e10c94c1b9f222ac9ce29d1c4776246cc");
 }
 #endif
 
index 1770cb3c03859775223e1a949576d53cba02f131..d322c02c55222e40de9521a06f4d4bfb2452942d 100644 (file)
@@ -344,7 +344,7 @@ TEST(validate_method_call) {
 
         JsonVariant *reply = NULL;
         const char *error_id = NULL;
-        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
                                 JSON_BUILD_OBJECT(
                                                 JSON_BUILD_PAIR_UNSIGNED("foo", 8),
                                                 JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0);
@@ -361,7 +361,7 @@ TEST(validate_method_call) {
         json_variant_dump(expected_reply, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
         assert_se(json_variant_equal(reply, expected_reply));
 
-        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
                                 JSON_BUILD_OBJECT(
                                                 JSON_BUILD_PAIR_UNSIGNED("foo", 9),
                                                 JSON_BUILD_PAIR_UNSIGNED("bar", 8),
@@ -370,14 +370,14 @@ TEST(validate_method_call) {
         assert_se(!error_id);
         assert_se(json_variant_equal(reply, expected_reply));
 
-        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
                                 JSON_BUILD_OBJECT(
                                                 JSON_BUILD_PAIR_UNSIGNED("foo", 8),
                                                 JSON_BUILD_PAIR_UNSIGNED("bar", 9),
                                                 JSON_BUILD_PAIR_STRING("zzz", "pfft"))) >= 0);
         assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER));
 
-        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+        assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
                                 JSON_BUILD_OBJECT(
                                                 JSON_BUILD_PAIR_STRING("foo", "wuff"),
                                                 JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0);
index 2617ed0e1aa2292304a60aa5900acd2713910c7f..b0b244e9178a123ebcb34430f80f82f60c010996 100644 (file)
@@ -265,7 +265,7 @@ static void *thread(void *arg) {
         }
         assert_se(x == 6);
 
-        assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0);
+        assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e) >= 0);
         assert_se(json_variant_integer(json_variant_by_key(o, "sum")) == 88 + 99);
         assert_se(!e);
 
@@ -281,7 +281,7 @@ static void *thread(void *arg) {
         assert_se(varlink_push_fd(c, fd2) == 1);
         assert_se(varlink_push_fd(c, fd3) == 2);
 
-        assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0);
+        assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0);
 
         int fd4 = varlink_peek_fd(c, 0);
         int fd5 = varlink_peek_fd(c, 1);
@@ -292,7 +292,7 @@ static void *thread(void *arg) {
         test_fd(fd4, "miau", 4);
         test_fd(fd5, "wuff", 4);
 
-        assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0);
+        assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0);
         assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist"));
         assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND));
 
index de785370ed12dd95bcd37a3a1e266074eee94644..9601c538bcb6c8bfc21479f0f517606bac4b0b63 100644 (file)
@@ -355,15 +355,11 @@ static int user_config_paths(char*** ret) {
         if (r < 0)
                 return r;
 
-        r = strv_extend(&res, persistent_config);
-        if (r < 0)
-                return r;
-
-        r = strv_extend(&res, runtime_config);
-        if (r < 0)
-                return r;
-
-        r = strv_extend(&res, data_home);
+        r = strv_extend_many(
+                        &res,
+                        persistent_config,
+                        runtime_config,
+                        data_home);
         if (r < 0)
                 return r;
 
@@ -3983,16 +3979,16 @@ static int exclude_default_prefixes(void) {
          * likely over-mounted if the root directory is actually used, and it wouldbe less than ideal to have
          * all kinds of files created/adjusted underneath these mount points. */
 
-        r = strv_extend_strv(
+        r = strv_extend_many(
                         &arg_exclude_prefixes,
-                        STRV_MAKE("/dev",
-                                  "/proc",
-                                  "/run",
-                                  "/sys"),
-                                 true);
+                        "/dev",
+                        "/proc",
+                        "/run",
+                        "/sys");
         if (r < 0)
                 return log_oom();
 
+        strv_uniq(arg_exclude_prefixes);
         return 0;
 }
 
index 0b1f0b7157df4fe6861746c0ad0b4cc96886a9fc..4dd7e54973ab11c247448a87cffd1ec4a09d6718 100644 (file)
@@ -298,7 +298,8 @@ static void disk_identify_fixup_uint16(uint8_t identify[512], unsigned offset_wo
  * non-zero with errno set.
  */
 static int disk_identify(int fd,
-                         uint8_t out_identify[512]) {
+                         uint8_t out_identify[512],
+                         int *ret_peripheral_device_type) {
         uint8_t inquiry_buf[36];
         int peripheral_device_type, r;
 
@@ -358,6 +359,9 @@ static int disk_identify(int fd,
         if (all_nul_bytes)
                 return log_debug_errno(SYNTHETIC_ERRNO(EIO), "IDENTIFY data is all zeroes.");
 
+        if (ret_peripheral_device_type)
+                *ret_peripheral_device_type = peripheral_device_type;
+
         return 0;
 }
 
@@ -407,7 +411,7 @@ static int run(int argc, char *argv[]) {
         char model[41], model_enc[256], serial[21], revision[9];
         _cleanup_close_ int fd = -EBADF;
         uint16_t word;
-        int r;
+        int r, peripheral_device_type = -1;
 
         log_set_target(LOG_TARGET_AUTO);
         udev_parse_config();
@@ -422,7 +426,7 @@ static int run(int argc, char *argv[]) {
         if (fd < 0)
                 return log_error_errno(errno, "Cannot open %s: %m", arg_device);
 
-        if (disk_identify(fd, identify.byte) >= 0) {
+        if (disk_identify(fd, identify.byte, &peripheral_device_type) >= 0) {
                 /*
                  * fix up only the fields from the IDENTIFY data that we are going to
                  * use and copy it into the hd_driveid struct for convenience
@@ -615,6 +619,9 @@ static int run(int argc, char *argv[]) {
                 if (IN_SET(identify.wyde[0], 0x848a, 0x844a) ||
                     (identify.wyde[83] & 0xc004) == 0x4004)
                         printf("ID_ATA_CFA=1\n");
+
+                if (peripheral_device_type >= 0)
+                        printf("ID_ATA_PERIPHERAL_DEVICE_TYPE=%d\n", peripheral_device_type);
         } else {
                 if (serial[0] != '\0')
                         printf("%s_%s\n", model, serial);
index 6e9d86b783dee4b849054ca27a4225e4d161a2a8..efa780992fb94f5acd6b2b019e2619d81e5a924c 100755 (executable)
@@ -190,7 +190,7 @@ class Uname:
     @classmethod
     def scrape_x86(cls, filename, opts=None):
         # Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L136
-        # and https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header
+        # and https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
         with open(filename, 'rb') as f:
             f.seek(0x202)
             magic = f.read(4)
@@ -871,7 +871,7 @@ def generate_key_cert_pair(
     # supported/expected:
     # https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-secure-boot-key-creation-and-management-guidance?view=windows-11#12-public-key-cryptography
 
-    now = datetime.datetime.now(datetime.UTC)
+    now = datetime.datetime.now(datetime.timezone.utc)
 
     key = rsa.generate_private_key(
         public_exponent=65537,
index 260dbab2c8539d606b832bff1de0ca250c24ee56..2ee366dbaee29ceb640a75938e2e52d72e043caa 100644 (file)
@@ -24,15 +24,17 @@ executables += [
         },
 ]
 
-custom_target(
-        '20-systemd-userdb.conf',
-        input : '20-systemd-userdb.conf.in',
-        output : '20-systemd-userdb.conf',
-        command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
-        install : conf.get('ENABLE_USERDB') == 1 and sshdconfdir != 'no',
-        install_dir : libexecdir / 'sshd_config.d')
+if conf.get('ENABLE_USERDB') == 1 and sshdconfdir != 'no'
+        custom_target(
+                '20-systemd-userdb.conf',
+                input : '20-systemd-userdb.conf.in',
+                output : '20-systemd-userdb.conf',
+                command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
+                install : true,
+                install_dir : libexecdir / 'sshd_config.d')
 
-install_emptydir(sshdconfdir)
+        install_emptydir(sshdconfdir)
 
-meson.add_install_script(sh, '-c',
-        ln_s.format(libexecdir / 'sshd_config.d' / '20-systemd-userdb.conf', sshdconfdir / '20-systemd-userdb.conf'))
+        meson.add_install_script(sh, '-c',
+                ln_s.format(libexecdir / 'sshd_config.d' / '20-systemd-userdb.conf', sshdconfdir / '20-systemd-userdb.conf'))
+endif
index 64105c7ab209327b94f35c7f20038f27bf648e28..8f3a88aba57f24a0c9d8c89791d2750e009e7359 100644 (file)
@@ -210,12 +210,9 @@ static int verb_info(int argc, char *argv[], void *userdata) {
                 return r;
 
         JsonVariant *reply = NULL;
-        const char *error = NULL;
-        r = varlink_call(vl, "org.varlink.service.GetInfo", NULL, &reply, &error, NULL);
+        r = varlink_call_and_log(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to issue GetInfo() call: %m");
-        if (error)
-                return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInfo() failed: %s", error);
+                return r;
 
         pager_open(arg_pager_flags);
 
@@ -296,12 +293,13 @@ static int verb_introspect(int argc, char *argv[], void *userdata) {
                 return r;
 
         JsonVariant *reply = NULL;
-        const char *error = NULL;
-        r = varlink_callb(vl, "org.varlink.service.GetInterfaceDescription", &reply, &error, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
+        r = varlink_callb_and_log(
+                        vl,
+                        "org.varlink.service.GetInterfaceDescription",
+                        &reply,
+                        JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
         if (r < 0)
-                return log_error_errno(r, "Failed to issue GetInterfaceDescription() call: %m");
-        if (error)
-                return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInterfaceDescription() failed: %s", error);
+                return r;
 
         pager_open(arg_pager_flags);
 
@@ -432,7 +430,7 @@ static int verb_call(int argc, char *argv[], void *userdata) {
                 JsonVariant *reply = NULL;
                 const char *error = NULL;
 
-                r = varlink_call(vl, method, jp, &reply, &error, NULL);
+                r = varlink_call(vl, method, jp, &reply, &error);
                 if (r < 0)
                         return log_error_errno(r, "Failed to issue %s() call: %m", method);
 
index b5b5eafae6d0cf0c122e71b714c9b3a25a3f212e..79e1e5399a2d8448af646123f4667f236061626d 100644 (file)
@@ -29,7 +29,9 @@ OvmfConfig* ovmf_config_free(OvmfConfig *config) {
                 return NULL;
 
         free(config->path);
+        free(config->format);
         free(config->vars);
+        free(config->vars_format);
         return mfree(config);
 }
 
@@ -40,7 +42,7 @@ int qemu_check_kvm_support(void) {
                 log_debug_errno(errno, "/dev/kvm not found. Not using KVM acceleration.");
                 return false;
         }
-        if (errno == EPERM) {
+        if (ERRNO_IS_PRIVILEGE(errno)) {
                 log_debug_errno(errno, "Permission denied to access /dev/kvm. Not using KVM acceleration.");
                 return false;
         }
@@ -62,11 +64,11 @@ int qemu_check_vsock_support(void) {
         fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC);
         if (fd >= 0)
                 return true;
-        if (errno == ENODEV) {
+        if (ERRNO_IS_DEVICE_ABSENT(errno)) {
                 log_debug_errno(errno, "/dev/vhost-vsock device doesn't exist. Not adding a vsock device to the virtual machine.");
                 return false;
         }
-        if (errno == EPERM) {
+        if (ERRNO_IS_PRIVILEGE(errno)) {
                 log_debug_errno(errno, "Permission denied to access /dev/vhost-vsock. Not adding a vsock device to the virtual machine.");
                 return false;
         }
@@ -78,16 +80,26 @@ int qemu_check_vsock_support(void) {
 typedef struct FirmwareData {
         char **features;
         char *firmware;
+        char *firmware_format;
         char *vars;
+        char *vars_format;
 } FirmwareData;
 
+static bool firmware_data_supports_sb(const FirmwareData *fwd) {
+        assert(fwd);
+
+        return strv_contains(fwd->features, "secure-boot");
+}
+
 static FirmwareData* firmware_data_free(FirmwareData *fwd) {
         if (!fwd)
                 return NULL;
 
-        fwd->features = strv_free(fwd->features);
-        fwd->firmware = mfree(fwd->firmware);
-        fwd->vars = mfree(fwd->vars);
+        strv_free(fwd->features);
+        free(fwd->firmware);
+        free(fwd->firmware_format);
+        free(fwd->vars);
+        free(fwd->vars_format);
 
         return mfree(fwd);
 }
@@ -95,22 +107,22 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FirmwareData*, firmware_data_free);
 
 static int firmware_executable(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
         static const JsonDispatch table[] = {
-                { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY },
-                { "format",   JSON_VARIANT_STRING, NULL,                 0,                                JSON_MANDATORY },
+                { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware),        JSON_MANDATORY },
+                { "format",   JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware_format), JSON_MANDATORY },
                 {}
         };
 
-        return json_dispatch(v, table, 0, userdata);
+        return json_dispatch(v, table, flags, userdata);
 }
 
 static int firmware_nvram_template(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
         static const JsonDispatch table[] = {
-                { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY },
-                { "format",   JSON_VARIANT_STRING, NULL,                 0,                            JSON_MANDATORY },
+                { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars),        JSON_MANDATORY },
+                { "format",   JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars_format), JSON_MANDATORY },
                 {}
         };
 
-        return json_dispatch(v, table, 0, userdata);
+        return json_dispatch(v, table, flags, userdata);
 }
 
 static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
@@ -121,15 +133,139 @@ static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags
                 {}
         };
 
-        return json_dispatch(v, table, 0, userdata);
+        return json_dispatch(v, table, flags, userdata);
+}
+
+static int get_firmware_search_dirs(char ***ret) {
+        int r;
+
+        assert(ret);
+
+        /* Search in:
+         * - $XDG_CONFIG_HOME/qemu/firmware
+         * - /etc/qemu/firmware
+         * - /usr/share/qemu/firmware
+         *
+         * Prioritising entries in "more specific" directories */
+
+        _cleanup_free_ char *user_firmware_dir = NULL;
+        r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
+        if (r < 0)
+                return r;
+
+        _cleanup_strv_free_ char **l = NULL;
+        l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware");
+        if (!l)
+                return log_oom_debug();
+
+        *ret = TAKE_PTR(l);
+        return 0;
+}
+
+int list_ovmf_config(char ***ret) {
+        _cleanup_strv_free_ char **search_dirs = NULL;
+        int r;
+
+        assert(ret);
+
+        r = get_firmware_search_dirs(&search_dirs);
+        if (r < 0)
+                return r;
+
+        r = conf_files_list_strv(
+                        ret,
+                        ".json",
+                        /* root= */ NULL,
+                        CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR,
+                        (const char *const*) search_dirs);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to list firmware files: %m");
+
+        return 0;
+}
+
+static int load_firmware_data(const char *path, FirmwareData **ret) {
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        _cleanup_(json_variant_unrefp) JsonVariant *json = NULL;
+        r = json_parse_file(
+                        /* f= */ NULL,
+                        path,
+                        /* flags= */ 0,
+                        &json,
+                        /* ret_line= */ NULL,
+                        /* ret_column= */ NULL);
+        if (r < 0)
+                return r;
+
+        static const JsonDispatch table[] = {
+                { "description",     JSON_VARIANT_STRING, NULL,               0,                                JSON_MANDATORY },
+                { "interface-types", JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
+                { "mapping",         JSON_VARIANT_OBJECT, firmware_mapping,   0,                                JSON_MANDATORY },
+                { "targets",         JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
+                { "features",        JSON_VARIANT_ARRAY,  json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY },
+                { "tags",            JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
+                {}
+        };
+
+        _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
+        fwd = new0(FirmwareData, 1);
+        if (!fwd)
+                return -ENOMEM;
+
+        r = json_dispatch(json, table, JSON_ALLOW_EXTENSIONS, fwd);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(fwd);
+        return 0;
+}
+
+static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) {
+        assert(fwd);
+        assert(ret);
+
+        _cleanup_free_ OvmfConfig *config = NULL;
+        config = new(OvmfConfig, 1);
+        if (!config)
+                return -ENOMEM;
+
+        *config = (OvmfConfig) {
+                .path = TAKE_PTR(fwd->firmware),
+                .format = TAKE_PTR(fwd->firmware_format),
+                .vars = TAKE_PTR(fwd->vars),
+                .vars_format = TAKE_PTR(fwd->vars_format),
+                .supports_sb = firmware_data_supports_sb(fwd),
+        };
+
+        *ret = TAKE_PTR(config);
+        return 0;
+}
+
+int load_ovmf_config(const char *path, OvmfConfig **ret) {
+        _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        r = load_firmware_data(path, &fwd);
+        if (r < 0)
+                return r;
+
+        return ovmf_config_make(fwd, ret);
 }
 
 int find_ovmf_config(int search_sb, OvmfConfig **ret) {
         _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL;
-        _cleanup_free_ char *user_firmware_dir = NULL;
         _cleanup_strv_free_ char **conf_files = NULL;
         int r;
 
+        assert(ret);
+
         /* Search in:
          * - $XDG_CONFIG_HOME/qemu/firmware
          * - /etc/qemu/firmware
@@ -138,74 +274,35 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) {
          * Prioritising entries in "more specific" directories
          */
 
-        r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
+        r = list_ovmf_config(&conf_files);
         if (r < 0)
                 return r;
 
-        r = conf_files_list_strv(&conf_files, ".json", NULL, CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR,
-                        STRV_MAKE_CONST(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware"));
-        if (r < 0)
-                return log_debug_errno(r, "Failed to list config files: %m");
-
         STRV_FOREACH(file, conf_files) {
                 _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
-                _cleanup_(json_variant_unrefp) JsonVariant *config_json = NULL;
-                _cleanup_free_ char *contents = NULL;
-                size_t contents_sz = 0;
-
-                r = read_full_file(*file, &contents, &contents_sz);
-                if (r == -ENOMEM)
-                        return r;
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to read contents of %s - ignoring: %m", *file);
-                        continue;
-                }
 
-                r = json_parse(contents, 0, &config_json, NULL, NULL);
-                if (r == -ENOMEM)
-                        return r;
+                r = load_firmware_data(*file, &fwd);
                 if (r < 0) {
-                        log_debug_errno(r, "Failed to parse the JSON in %s - ignoring: %m", *file);
+                        log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file);
                         continue;
                 }
 
-                static const JsonDispatch table[] = {
-                        { "description",     JSON_VARIANT_STRING, NULL,               0,                                JSON_MANDATORY },
-                        { "interface-types", JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
-                        { "mapping",         JSON_VARIANT_OBJECT, firmware_mapping,   0,                                JSON_MANDATORY },
-                        { "targets",         JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
-                        { "features",        JSON_VARIANT_ARRAY,  json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY },
-                        { "tags",            JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
-                        {}
-                };
-
-                fwd = new0(FirmwareData, 1);
-                if (!fwd)
-                        return -ENOMEM;
-
-                r = json_dispatch(config_json, table, 0, fwd);
-                if (r == -ENOMEM)
-                        return r;
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to extract the required fields from the JSON in %s - ignoring: %m", *file);
+                if (strv_contains(fwd->features, "enrolled-keys")) {
+                        log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file);
                         continue;
                 }
 
-                int sb_present = !!strv_find(fwd->features, "secure-boot");
-
                 /* exclude firmware which doesn't match our Secure Boot requirements */
-                if (search_sb >= 0 && search_sb != sb_present) {
-                        log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration", *file);
+                if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) {
+                        log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file);
                         continue;
                 }
 
-                config = new0(OvmfConfig, 1);
-                if (!config)
-                        return -ENOMEM;
+                r = ovmf_config_make(fwd, &config);
+                if (r < 0)
+                        return r;
 
-                config->path = TAKE_PTR(fwd->firmware);
-                config->vars = TAKE_PTR(fwd->vars);
-                config->supports_sb = sb_present;
+                log_debug("Selected firmware definition %s.", *file);
                 break;
         }
 
index 53ad7dd3d271363bb705e71c4e8a5a195b967b3c..f2d1b621e21fbab17b9ebf3b114ea1026328a1bc 100644 (file)
 #define ARCHITECTURE_SUPPORTS_SMBIOS 0
 #endif
 
+#if defined(__arm__) || defined(__aarch64__)
+#define DEFAULT_SERIAL_TTY "ttyAMA0"
+#elif defined(__s390__) || defined(__s390x__)
+#define DEFAULT_SERIAL_TTY "ttysclp0"
+#elif defined(__powerpc__) || defined(__powerpc64__)
+#define DEFAULT_SERIAL_TTY "hvc0"
+#else
+#define DEFAULT_SERIAL_TTY "ttyS0"
+#endif
+
 typedef struct OvmfConfig {
         char *path;
+        char *format;
         char *vars;
+        char *vars_format;
         bool supports_sb;
 } OvmfConfig;
 
+static inline const char *ovmf_config_format(const OvmfConfig *c) {
+        return ASSERT_PTR(c)->format ?: "raw";
+}
+
+static inline const char *ovmf_config_vars_format(const OvmfConfig *c) {
+        return ASSERT_PTR(c)->vars_format ?: "raw";
+}
+
 OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config);
 DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free);
 
 int qemu_check_kvm_support(void);
 int qemu_check_vsock_support(void);
-int find_ovmf_config(int search_sb, OvmfConfig **ret_ovmf_config);
+int list_ovmf_config(char ***ret);
+int load_ovmf_config(const char *path, OvmfConfig **ret);
+int find_ovmf_config(int search_sb, OvmfConfig **ret);
 int find_qemu_binary(char **ret_qemu_binary);
 int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock);
index 4ccda8bae8b8d8d1b5526c30ae8e961bdadd7d9c..a08bc4c6794c8a289ad9d5f267349f7ac416c3a6 100644 (file)
@@ -13,6 +13,7 @@
 #include "copy.h"
 #include "creds-util.h"
 #include "escape.h"
+#include "event-util.h"
 #include "fileio.h"
 #include "format-util.h"
 #include "fs-util.h"
 #include "vmspawn-settings.h"
 #include "vmspawn-util.h"
 
+static bool arg_quiet = false;
 static PagerFlags arg_pager_flags = 0;
 static char *arg_image = NULL;
 static char *arg_machine = NULL;
 static char *arg_qemu_smp = NULL;
-static uint64_t arg_qemu_mem = 2ULL * 1024ULL * 1024ULL * 1024ULL;
+static uint64_t arg_qemu_mem = UINT64_C(2) * U64_GB;
 static int arg_qemu_kvm = -1;
 static int arg_qemu_vsock = -1;
-static uint64_t arg_vsock_cid = UINT64_MAX;
+static unsigned arg_vsock_cid = VMADDR_CID_ANY;
 static bool arg_qemu_gui = false;
 static int arg_secure_boot = -1;
 static MachineCredentialContext arg_credentials = {};
 static SettingsMask arg_settings_mask = 0;
 static char **arg_parameters = NULL;
+static char *arg_firmware = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
+STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep);
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
@@ -69,11 +73,12 @@ static int help(void) {
                "%5$sSpawn a command or OS in a virtual machine.%6$s\n\n"
                "  -h --help                 Show this help\n"
                "     --version              Print version string\n"
-               "     --no-pager             Do not pipe output into a pager\n\n"
-               "%3$sImage:%4$s\n"
+               "  -q --quiet                Do not show status information\n"
+               "     --no-pager             Do not pipe output into a pager\n"
+               "\n%3$sImage:%4$s\n"
                "  -i --image=PATH           Root file system disk image (or device node) for\n"
-               "                            the virtual machine\n\n"
-               "%3$sHost Configuration:%4$s\n"
+               "                            the virtual machine\n"
+               "\n%3$sHost Configuration:%4$s\n"
                "     --qemu-smp=SMP         Configure guest's SMP settings\n"
                "     --qemu-mem=MEM         Configure guest's RAM size\n"
                "     --qemu-kvm=BOOL        Configure whether to use KVM or not\n"
@@ -81,10 +86,11 @@ static int help(void) {
                "     --vsock-cid=           Specify the CID to use for the qemu guest's vsock\n"
                "     --qemu-gui             Start QEMU in graphical mode\n"
                "     --secure-boot=BOOL     Configure whether to search for firmware which\n"
-               "                            supports Secure Boot\n\n"
-               "%3$sSystem Identity:%4$s\n"
+               "                            supports Secure Boot\n"
+               "     --firmware=PATH|list   Select firmware definition file (or list available)\n"
+               "\n%3$sSystem Identity:%4$s\n"
                "  -M --machine=NAME         Set the machine name for the container\n"
-               "%3$sCredentials:%4$s\n"
+               "\n%3$sCredentials:%4$s\n"
                "     --set-credential=ID:VALUE\n"
                "                            Pass a credential with literal value to container.\n"
                "     --load-credential=ID:PATH\n"
@@ -114,11 +120,13 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SECURE_BOOT,
                 ARG_SET_CREDENTIAL,
                 ARG_LOAD_CREDENTIAL,
+                ARG_FIRMWARE,
         };
 
         static const struct option options[] = {
                 { "help",            no_argument,       NULL, 'h'                 },
                 { "version",         no_argument,       NULL, ARG_VERSION         },
+                { "quiet",           no_argument,       NULL, 'q'                 },
                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
                 { "image",           required_argument, NULL, 'i'                 },
                 { "machine",         required_argument, NULL, 'M'                 },
@@ -131,6 +139,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "secure-boot",     required_argument, NULL, ARG_SECURE_BOOT     },
                 { "set-credential",  required_argument, NULL, ARG_SET_CREDENTIAL  },
                 { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
+                { "firmware",        required_argument, NULL, ARG_FIRMWARE        },
                 {}
         };
 
@@ -140,7 +149,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argv);
 
         optind = 0;
-        while ((c = getopt_long(argc, argv, "+hi:M", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "+hi:Mq", options, NULL)) >= 0)
                 switch (c) {
                 case 'h':
                         return help();
@@ -148,6 +157,10 @@ static int parse_argv(int argc, char *argv[]) {
                 case ARG_VERSION:
                         return version();
 
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
                 case 'i':
                         r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
                         if (r < 0)
@@ -198,20 +211,21 @@ static int parse_argv(int argc, char *argv[]) {
                                 return log_error_errno(r, "Failed to parse --qemu-vsock=%s: %m", optarg);
                         break;
 
-                case ARG_VSOCK_CID: {
-                        unsigned cid;
+                case ARG_VSOCK_CID:
                         if (isempty(optarg))
-                                cid = VMADDR_CID_ANY;
+                                arg_vsock_cid = VMADDR_CID_ANY;
                         else {
-                                r = safe_atou_bounded(optarg, 3, UINT_MAX - 1, &cid);
-                                if (r == -ERANGE)
-                                        return log_error_errno(r, "Invalid value for --vsock-cid=: %m");
+                                unsigned cid;
+
+                                r = vsock_parse_cid(optarg, &cid);
                                 if (r < 0)
-                                        return log_error_errno(r, "Failed to parse --vsock-cid=%s: %m", optarg);
+                                        return log_error_errno(r, "Failed to parse --vsock-cid: %s", optarg);
+                                if (!VSOCK_CID_IS_REGULAR(cid))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid);
+
+                                arg_vsock_cid = cid;
                         }
-                        arg_vsock_cid = (uint64_t)cid;
                         break;
-                }
 
                 case ARG_QEMU_GUI:
                         arg_qemu_gui = true;
@@ -240,6 +254,31 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_FIRMWARE:
+                        if (streq(optarg, "list")) {
+                                _cleanup_strv_free_ char **l = NULL;
+
+                                r = list_ovmf_config(&l);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to list firmwares: %m");
+
+                                bool nl = false;
+                                fputstrv(stdout, l, "\n", &nl);
+                                if (nl)
+                                        putchar('\n');
+
+                                return 0;
+                        }
+
+                        if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./"))
+                                return log_error_errno(SYNTHETIC_ERRNO(errno), "Absolute path or path starting with './' required.");
+
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -377,25 +416,34 @@ static int vmspawn_dispatch_vsock_connections(sd_event_source *source, int fd, u
         return 0;
 }
 
-static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **notify_event_source) {
+static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **ret_notify_event_source) {
         int r;
 
-        r = sd_event_add_io(event, notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status);
+        assert(event);
+        assert(fd >= 0);
+        assert(exit_status);
+        assert(ret_notify_event_source);
+
+        r = sd_event_add_io(event, ret_notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate notify socket event source: %m");
 
-        (void) sd_event_source_set_description(*notify_event_source, "vmspawn-notify-sock");
+        (void) sd_event_source_set_description(*ret_notify_event_source, "vmspawn-notify-sock");
 
         return 0;
 }
 
 static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
-        pid_t pid;
+        PidRef *pidref = userdata;
+        int r;
+
+        /* TODO: actually talk to qemu and ask the guest to shutdown here */
 
-        pid = PTR_TO_PID(userdata);
-        if (pid > 0) {
-                /* TODO: actually talk to qemu and ask the guest to shutdown here */
-                if (kill(pid, SIGKILL) >= 0) {
+        if (pidref) {
+                r = pidref_kill(pidref, SIGKILL);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to kill qemu, terminating: %m");
+                else {
                         log_info("Trying to halt qemu. Send SIGTERM again to trigger vmspawn to immediately terminate.");
                         sd_event_source_set_userdata(s, NULL);
                         return 0;
@@ -449,7 +497,10 @@ static int run_virtual_machine(void) {
                 use_kvm = r;
         }
 
-        r = find_ovmf_config(arg_secure_boot, &ovmf_config);
+        if (arg_firmware)
+                r = load_ovmf_config(arg_firmware, &ovmf_config);
+        else
+                r = find_ovmf_config(arg_secure_boot, &ovmf_config);
         if (r < 0)
                 return log_error_errno(r, "Failed to find OVMF config: %m");
 
@@ -472,7 +523,7 @@ static int run_virtual_machine(void) {
         if (r < 0)
                 return log_error_errno(r, "Failed to find QEMU binary: %m");
 
-        if (asprintf(&mem, "%.4fM", (double)arg_qemu_mem / (1024.0 * 1024.0)) < 0)
+        if (asprintf(&mem, "%" PRIu64, DIV_ROUND_UP(arg_qemu_mem, U64_MB)) < 0)
                 return log_oom();
 
         cmdline = strv_new(
@@ -499,8 +550,7 @@ static int run_virtual_machine(void) {
         unsigned child_cid = VMADDR_CID_ANY;
         _cleanup_close_ int child_vsock_fd = -EBADF;
         if (use_vsock) {
-                if (arg_vsock_cid < UINT_MAX)
-                        child_cid = (unsigned)arg_vsock_cid;
+                child_cid = arg_vsock_cid;
 
                 r = vsock_fix_child_cid(&child_cid, arg_machine, &child_vsock_fd);
                 if (r < 0)
@@ -516,25 +566,25 @@ static int run_virtual_machine(void) {
                         return log_oom();
         }
 
-        r = strv_extend_strv(&cmdline, STRV_MAKE("-cpu", "max"), /* filter_duplicates= */ false);
+        r = strv_extend_many(&cmdline, "-cpu", "max");
         if (r < 0)
                 return log_oom();
 
-        if (arg_qemu_gui) {
-                r = strv_extend_strv(&cmdline, STRV_MAKE("-vga", "virtio"),  /* filter_duplicates= */ false);
-                if (r < 0)
-                        return log_oom();
-        } else {
-                r = strv_extend_strv(&cmdline, STRV_MAKE(
-                        "-nographic",
-                        "-nodefaults",
-                        "-chardev", "stdio,mux=on,id=console,signal=off",
-                        "-serial", "chardev:console",
-                        "-mon", "console"
-                ),  /* filter_duplicates= */ false);
-                if (r < 0)
-                        return log_oom();
-        }
+        if (arg_qemu_gui)
+                r = strv_extend_many(
+                                &cmdline,
+                                "-vga",
+                                "virtio");
+        else
+                r = strv_extend_many(
+                                &cmdline,
+                                "-nographic",
+                                "-nodefaults",
+                                "-chardev", "stdio,mux=on,id=console,signal=off",
+                                "-serial", "chardev:console",
+                                "-mon", "console");
+        if (r < 0)
+                return log_oom();
 
         if (ARCHITECTURE_SUPPORTS_SMBIOS)
                 FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
@@ -558,7 +608,7 @@ static int run_virtual_machine(void) {
         if (r < 0)
                 return log_oom();
 
-        r = strv_extendf(&cmdline, "if=pflash,format=raw,readonly=on,file=%s", ovmf_config->path);
+        r = strv_extendf(&cmdline, "if=pflash,format=%s,readonly=on,file=%s", ovmf_config_format(ovmf_config), ovmf_config->path);
         if (r < 0)
                 return log_oom();
 
@@ -588,15 +638,15 @@ static int run_virtual_machine(void) {
                 (void) copy_access(source_fd, target_fd);
                 (void) copy_times(source_fd, target_fd, 0);
 
-                r = strv_extend_strv(&cmdline, STRV_MAKE(
-                        "-global", "ICH9-LPC.disable_s3=1",
-                        "-global", "driver=cfi.pflash01,property=secure,value=on",
-                        "-drive"
-                ),  /* filter_duplicates= */ false);
+                r = strv_extend_many(
+                                &cmdline,
+                                "-global", "ICH9-LPC.disable_s3=1",
+                                "-global", "driver=cfi.pflash01,property=secure,value=on",
+                                "-drive");
                 if (r < 0)
                         return log_oom();
 
-                r = strv_extendf(&cmdline, "file=%s,if=pflash,format=raw", ovmf_vars_to);
+                r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", ovmf_vars_to, ovmf_config_format(ovmf_config));
                 if (r < 0)
                         return log_oom();
         }
@@ -609,29 +659,31 @@ static int run_virtual_machine(void) {
         if (r < 0)
                 return log_oom();
 
-        r = strv_extend_strv(&cmdline, STRV_MAKE(
-                "-device", "virtio-scsi-pci,id=scsi",
-                "-device", "scsi-hd,drive=mkosi,bootindex=1"
-        ),  /* filter_duplicates= */ false);
+        r = strv_extend_many(
+                        &cmdline,
+                        "-device", "virtio-scsi-pci,id=scsi",
+                        "-device", "scsi-hd,drive=mkosi,bootindex=1");
         if (r < 0)
                 return log_oom();
 
-        if (!strv_isempty(arg_parameters)) {
-                if (ARCHITECTURE_SUPPORTS_SMBIOS) {
-                        _cleanup_free_ char *kcl = strv_join(arg_parameters, " ");
-                        if (!kcl)
-                                return log_oom();
+        r = strv_prepend(&arg_parameters, "console=" DEFAULT_SERIAL_TTY);
+        if (r < 0)
+                return log_oom();
 
-                        r = strv_extend(&cmdline, "-smbios");
-                        if (r < 0)
-                                return log_oom();
+        if (ARCHITECTURE_SUPPORTS_SMBIOS) {
+                _cleanup_free_ char *kcl = strv_join(arg_parameters, " ");
+                if (!kcl)
+                        return log_oom();
 
-                        r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl);
-                        if (r < 0)
-                                return log_oom();
-                } else
-                        log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS");
-        }
+                r = strv_extend(&cmdline, "-smbios");
+                if (r < 0)
+                        return log_oom();
+
+                r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl);
+                if (r < 0)
+                        return log_oom();
+        } else
+                log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS");
 
         if (use_vsock) {
                 vsock_fd = open_vsock();
@@ -645,6 +697,14 @@ static int run_virtual_machine(void) {
                         return log_error_errno(r, "Failed to call getsockname on vsock: %m");
         }
 
+        if (DEBUG_LOGGING) {
+                _cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY);
+                if (!joined)
+                        return log_oom();
+
+                log_debug("Executing: %s", joined);
+        }
+
         _cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
         _cleanup_(sd_event_unrefp) sd_event *event = NULL;
         r = sd_event_new(&event);
@@ -653,15 +713,16 @@ static int run_virtual_machine(void) {
 
         (void) sd_event_set_watchdog(event, true);
 
-        pid_t child_pid;
-        r = safe_fork_full(
+        _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL;
+
+        r = pidref_safe_fork_full(
                         qemu_binary,
-                        NULL,
+                        /* stdio_fds= */ NULL,
                         &child_vsock_fd, 1, /* pass the vsock fd to qemu */
-                        FORK_CLOEXEC_OFF,
-                        &child_pid);
+                        FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE,
+                        &child_pidref);
         if (r < 0)
-                return log_error_errno(r, "Failed to fork off %s: %m", qemu_binary);
+                return r;
         if (r == 0) {
                 /* set TERM and LANG if they are missing */
                 if (setenv("TERM", "vt220", 0) < 0)
@@ -670,11 +731,13 @@ static int run_virtual_machine(void) {
                 if (setenv("LANG", "C.UTF-8", 0) < 0)
                         return log_oom();
 
-                execve(qemu_binary, cmdline, environ);
+                execv(qemu_binary, cmdline);
                 log_error_errno(errno, "Failed to execve %s: %m", qemu_binary);
                 _exit(EXIT_FAILURE);
         }
 
+        /* Close the vsock fd we passed to qemu in the parent. We don't need it anymore. */
+        child_vsock_fd = safe_close(child_vsock_fd);
 
         int exit_status = INT_MAX;
         if (use_vsock) {
@@ -684,13 +747,13 @@ static int run_virtual_machine(void) {
         }
 
         /* shutdown qemu when we are shutdown */
-        (void) sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(child_pid));
-        (void) sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(child_pid));
+        (void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
+        (void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
 
-        (void) sd_event_add_signal(event, NULL, SIGRTMIN+18, sigrtmin18_handler, NULL);
+        (void) sd_event_add_signal(event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL);
 
         /* Exit when the child exits */
-        (void) sd_event_add_child(event, NULL, child_pid, WEXITED, on_child_exit, NULL);
+        (void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
 
         r = sd_event_loop(event);
         if (r < 0)
@@ -713,7 +776,7 @@ static int determine_names(void) {
         int r;
 
         if (!arg_image)
-                return log_error_errno(SYNTHETIC_ERRNO(-EINVAL), "Missing required argument -i/--image=, quitting");
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing required argument -i/--image=, quitting");
 
         if (!arg_machine) {
                 char *e;
@@ -748,7 +811,17 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
-        assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0);
+        if (!arg_quiet) {
+                _cleanup_free_ char *u = NULL;
+                (void) terminal_urlify_path(arg_image, arg_image, &u);
+
+                log_info("%s %sSpawning VM %s on %s.%s\n"
+                         "%s %sPress %sCtrl-a x%s to kill VM.%s",
+                         special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: arg_image, ansi_normal(),
+                         special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+        }
+
+        assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
 
         return run_virtual_machine();
 }
index 2e94156432b63f3b5edb1547e2533e8bf21be547..28d845c94bd38dbdb7f1ced606d1150ba58f0060 100755 (executable)
@@ -13,6 +13,10 @@ test_append_files() {
     local workspace="${1:?}"
     local container="$workspace/testsuite-13-container-template"
 
+    # For virtual wlan interface.
+    instmods mac80211_hwsim
+    generate_module_dependencies
+
     # Create a dummy container "template" with a minimal toolset, which we can
     # then use as a base for our nspawn/machinectl tests
     initdir="$container" setup_basic_dirs
index 93307c0bbd1d899f52d1839dec16594f76885791..18d9c3b30f651553222837b7884be7ca9d7e5ac3 100644 (file)
@@ -496,6 +496,7 @@ KernelVersion=
 Key=
 Kind=
 L2MissNotification=
+L3MasterDevice=
 L3MissNotification=
 LACPTransmitRate=
 LLDP=
index ba5fcebc2d31b2d0f86eeedbf542004165deab61..065ff7e2a00f92414eefae3a2d8483801142d40b 100644 (file)
@@ -19,3 +19,6 @@ ns1.unsigned          AAAA fd00:dead:beef:cafe::1
 onlinesign            NS   ns1.unsigned
 signed                NS   ns1.unsigned
 unsigned              NS   ns1.unsigned
+
+svcb                  SVCB  1   .   alpn=dot ipv4hint=10.0.0.1 ipv6hint=fd00:dead:beef:cafe::1
+https                 HTTPS 1   .   alpn="h2,h3"
diff --git a/test/test-network/conf/25-agent-bridge-port.network b/test/test-network/conf/25-agent-bridge-port.network
new file mode 100644 (file)
index 0000000..709a783
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=client-peer
+
+[Network]
+Bridge=bridge-relay
+IPv6AcceptRA=no
diff --git a/test/test-network/conf/25-agent-bridge.netdev b/test/test-network/conf/25-agent-bridge.netdev
new file mode 100644 (file)
index 0000000..a611337
--- /dev/null
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=bridge-relay
+Kind=bridge
diff --git a/test/test-network/conf/25-agent-bridge.network b/test/test-network/conf/25-agent-bridge.network
new file mode 100644 (file)
index 0000000..8383790
--- /dev/null
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=bridge-relay
+
+[Network]
+Address=192.168.2.1/24
+DHCPServer=yes
+IPv6AcceptRA=no
+
+[DHCPServer]
+RelayTarget=192.168.1.1
+RelayAgentRemoteId=string:aabbccdd
diff --git a/test/test-network/conf/25-dummy.netdev b/test/test-network/conf/25-dummy.netdev
new file mode 100644 (file)
index 0000000..d7cf7b4
--- /dev/null
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=test25
+Kind=dummy
diff --git a/test/test-network/conf/25-dummy.network b/test/test-network/conf/25-dummy.network
new file mode 100644 (file)
index 0000000..a6e93fd
--- /dev/null
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
diff --git a/test/test-network/conf/25-fibrule-l3mdev.network b/test/test-network/conf/25-fibrule-l3mdev.network
new file mode 100644 (file)
index 0000000..a1afcd2
--- /dev/null
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test1
+
+[Network]
+IPv6AcceptRA=no
+
+[RoutingPolicyRule]
+Priority=1500
+L3MasterDevice=true
+
+[RoutingPolicyRule]
+Priority=2000
+L3MasterDevice=true
+Type=unreachable
diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-0s.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-0s.network
new file mode 100644 (file)
index 0000000..04c7c49
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=0
diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-3s.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-3s.network
new file mode 100644 (file)
index 0000000..b4dbd06
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=3
diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-4s.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-4s.network
new file mode 100644 (file)
index 0000000..cbdf4f3
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=4
diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-infinity.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-infinity.network
new file mode 100644 (file)
index 0000000..085cb30
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=infinity
diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-invalid.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-invalid.network
new file mode 100644 (file)
index 0000000..8a0bf83
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=-2
diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-toobig.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-toobig.network
new file mode 100644 (file)
index 0000000..0976bae
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=999999999999999999
diff --git a/test/test-network/conf/25-nexthop-test1.network b/test/test-network/conf/25-nexthop-test1.network
new file mode 100644 (file)
index 0000000..5a4c596
--- /dev/null
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test1
+
+[Network]
+Address=192.168.20.21/24
+IPv6AcceptRA=no
+
+[Route]
+Destination=10.10.11.10
+# Nexthop 21 is configured as a group nexthop of 1 and 20
+NextHop=21
index dfa74c203df45ed1595f77bf58c06bef9583bc69..064ca53193b47b33c2032b36c947c9a2231a70c8 100755 (executable)
@@ -4,6 +4,16 @@
 
 # These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM,
 # simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py.
+#
+# To run an individual test, specify it as a command line argument in the form
+# of <class>.<test_function>. E.g. the NetworkdMTUTests class has a test
+# function called test_ipv6_mtu().  To run just that test use:
+#
+#    sudo ./systemd-networkd-tests.py NetworkdMTUTests.test_ipv6_mtu
+#
+# Similarly, other indivdual tests can be run, eg.:
+#
+#    sudo ./systemd-networkd-tests.py NetworkdNetworkTests.test_ipv6_neigh_retrans_time
 
 import argparse
 import datetime
@@ -12,6 +22,7 @@ import itertools
 import json
 import os
 import pathlib
+import random
 import re
 import shutil
 import signal
@@ -198,6 +209,14 @@ def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable():
 
     return f
 
+def expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable():
+    def f(func):
+        rc = call_quiet('ip rule add not from 192.168.100.19 l3mdev')
+        call_quiet('ip rule del not from 192.168.100.19 l3mdev')
+        return func if rc == 0 else unittest.expectedFailure(func)
+
+    return f
+
 def expectedFailureIfNexthopIsNotAvailable():
     def f(func):
         rc = call_quiet('ip nexthop list')
@@ -573,9 +592,16 @@ def read_ip_sysctl_attr(link, attribute, ipv):
     with open(os.path.join('/proc/sys/net', ipv, 'conf', link, attribute), encoding='utf-8') as f:
         return f.readline().strip()
 
+def read_ip_neigh_sysctl_attr(link, attribute, ipv):
+    with open(os.path.join('/proc/sys/net', ipv, 'neigh', link, attribute), encoding='utf-8') as f:
+        return f.readline().strip()
+
 def read_ipv6_sysctl_attr(link, attribute):
     return read_ip_sysctl_attr(link, attribute, 'ipv6')
 
+def read_ipv6_neigh_sysctl_attr(link, attribute):
+    return read_ip_neigh_sysctl_attr(link, attribute, 'ipv6')
+
 def read_ipv4_sysctl_attr(link, attribute):
     return read_ip_sysctl_attr(link, attribute, 'ipv4')
 
@@ -906,6 +932,9 @@ class Utilities():
     def check_ipv6_sysctl_attr(self, link, attribute, expected):
         self.assertEqual(read_ipv6_sysctl_attr(link, attribute), expected)
 
+    def check_ipv6_neigh_sysctl_attr(self, link, attribute, expected):
+        self.assertEqual(read_ipv6_neigh_sysctl_attr(link, attribute), expected)
+
     def wait_links(self, *links, timeout=20, fail_assert=True):
         def links_exist(*links):
             for link in links:
@@ -1771,6 +1800,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         start_networkd()
 
         self.wait_online(['vcan99:carrier', 'vcan98:carrier'])
+        # For can devices, 'carrier' is the default required operational state.
+        self.wait_online(['vcan99', 'vcan98'])
 
         # https://github.com/systemd/systemd/issues/30140
         output = check_output('ip -d link show vcan99')
@@ -1787,6 +1818,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         start_networkd()
 
         self.wait_online(['vxcan99:carrier', 'vxcan-peer:carrier'])
+        # For can devices, 'carrier' is the default required operational state.
+        self.wait_online(['vxcan99', 'vxcan-peer'])
 
     @expectedFailureIfModuleIsNotAvailable('wireguard')
     def test_wireguard(self):
@@ -1823,7 +1856,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
         output = check_output('ip -4 route show dev wg99 table 1234')
         print(output)
-        self.assertIn('192.168.26.0/24 proto static metric 123', output)
+        self.assertIn('192.168.26.0/24 proto static scope link metric 123', output)
 
         output = check_output('ip -6 route show dev wg99 table 1234')
         print(output)
@@ -3101,6 +3134,17 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'tcp')
         self.assertRegex(output, 'lookup 7')
 
+    @expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable()
+    def test_routing_policy_rule_l3mdev(self):
+        copy_network_unit('25-fibrule-l3mdev.network', '11-dummy.netdev')
+        start_networkd()
+        self.wait_online(['test1:degraded'])
+
+        output = check_output('ip rule')
+        print(output)
+        self.assertIn('1500:   from all lookup [l3mdev-table]', output)
+        self.assertIn('2000:   from all lookup [l3mdev-table] unreachable', output)
+
     @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable()
     def test_routing_policy_rule_uidrange(self):
         copy_network_unit('25-fibrule-uidrange.network', '11-dummy.netdev')
@@ -3481,6 +3525,56 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         for i in range(1, 5):
             self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy')
 
+    def test_ipv6_neigh_retrans_time(self):
+        link='test25'
+        copy_network_unit('25-dummy.netdev', '25-dummy.network')
+        start_networkd()
+
+        self.wait_online([f'{link}:degraded'])
+        remove_network_unit('25-dummy.network')
+
+        # expect retrans_time_ms updated
+        copy_network_unit('25-ipv6-neigh-retrans-time-3s.network')
+        networkctl_reload()
+        self.wait_online([f'{link}:degraded'])
+        self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+        remove_network_unit('25-ipv6-neigh-retrans-time-3s.network')
+
+        # expect retrans_time_ms unchanged
+        copy_network_unit('25-ipv6-neigh-retrans-time-0s.network')
+        networkctl_reload()
+        self.wait_online([f'{link}:degraded'])
+        self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+        remove_network_unit('25-ipv6-neigh-retrans-time-0s.network')
+
+        # expect retrans_time_ms unchanged
+        copy_network_unit('25-ipv6-neigh-retrans-time-toobig.network')
+        networkctl_reload()
+        self.wait_online([f'{link}:degraded'])
+        self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+        remove_network_unit('25-ipv6-neigh-retrans-time-toobig.network')
+
+        # expect retrans_time_ms unchanged
+        copy_network_unit('25-ipv6-neigh-retrans-time-infinity.network')
+        networkctl_reload()
+        self.wait_online([f'{link}:degraded'])
+        self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+        remove_network_unit('25-ipv6-neigh-retrans-time-infinity.network')
+
+        # expect retrans_time_ms unchanged
+        copy_network_unit('25-ipv6-neigh-retrans-time-invalid.network')
+        networkctl_reload()
+        self.wait_online([f'{link}:degraded'])
+        self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+        remove_network_unit('25-ipv6-neigh-retrans-time-invalid.network')
+
+        # expect retrans_time_ms updated
+        copy_network_unit('25-ipv6-neigh-retrans-time-4s.network')
+        networkctl_reload()
+        self.wait_online([f'{link}:degraded'])
+        self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '4000')
+        remove_network_unit('25-ipv6-neigh-retrans-time-4s.network')
+
     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',
@@ -4032,6 +4126,33 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
         self.check_nexthop(manage_foreign_nexthops, first=True)
 
+        # Remove nexthop with ID 20
+        check_output('ip nexthop del id 20')
+        copy_network_unit('11-dummy.netdev', '25-nexthop-test1.network')
+        networkctl_reload()
+
+        # 25-nexthop-test1.network requests a route with nexthop ID 21,
+        # which is silently removed by the kernel when nexthop with ID 20 is removed in the above,
+        # hence test1 should be stuck in the configuring state.
+        self.wait_operstate('test1', operstate='routable', setup_state='configuring')
+
+        # Wait for a while, and check if the interface is still in the configuring state.
+        time.sleep(1)
+        output = networkctl_status('test1')
+        self.assertIn('State: routable (configuring)', output)
+
+        # Reconfigure the interface that has nexthop with ID 20 and 21,
+        # then the route requested by test1 can be configured.
+        networkctl_reconfigure('dummy98')
+        self.wait_online(['test1:routable'])
+
+        # Check if the requested route actually configured.
+        output = check_output('ip route show 10.10.11.10')
+        print(output)
+        self.assertIn('10.10.11.10 nhid 21 proto static', output)
+        self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output)
+        self.assertIn('nexthop via 192.168.20.1 dev dummy98 weight 1', output)
+
         remove_link('veth99')
         time.sleep(2)
 
@@ -5109,9 +5230,7 @@ class NetworkdRATests(unittest.TestCase, Utilities):
 
         self.teardown_nftset('addr6', 'network6', 'ifindex')
 
-    def test_ipv6_token_static(self):
-        copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
-        start_networkd()
+    def check_ipv6_token_static(self):
         self.wait_online(['veth99:routable', 'veth-peer:degraded'])
 
         output = networkctl_status('veth99')
@@ -5121,6 +5240,26 @@ class NetworkdRATests(unittest.TestCase, Utilities):
         self.assertRegex(output, '2002:da8:2:0:1a:2b:3c:4d')
         self.assertRegex(output, '2002:da8:2:0:fa:de:ca:fe')
 
+    def test_ipv6_token_static(self):
+        copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
+        start_networkd()
+
+        self.check_ipv6_token_static()
+
+        for _ in range(20):
+            check_output('ip link set veth99 down')
+            check_output('ip link set veth99 up')
+
+        self.check_ipv6_token_static()
+
+        for _ in range(20):
+            check_output('ip link set veth99 down')
+            time.sleep(random.uniform(0, 0.1))
+            check_output('ip link set veth99 up')
+            time.sleep(random.uniform(0, 0.1))
+
+        self.check_ipv6_token_static()
+
     def test_ipv6_token_prefixstable(self):
         copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network')
         start_networkd()
@@ -5361,6 +5500,24 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
         print(output)
         self.assertRegex(output, r'Address: 192.168.5.150 \(DHCP4 via 192.168.5.1\)')
 
+    def test_replay_agent_on_bridge(self):
+        copy_network_unit('25-agent-bridge.netdev',
+                          '25-agent-veth-client.netdev',
+                          '25-agent-bridge.network',
+                          '25-agent-bridge-port.network',
+                          '25-agent-client.network')
+        start_networkd()
+        self.wait_online(['bridge-relay:routable', 'client-peer:enslaved'])
+
+        # For issue #30763.
+        expect = 'bridge-relay: DHCPv4 server: STARTED'
+        for _ in range(20):
+            if expect in read_networkd_log():
+                break
+            time.sleep(0.5)
+        else:
+            self.fail()
+
 class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
 
     def setUp(self):
diff --git a/test/testsuite-63.units/test63-pr-30768.path b/test/testsuite-63.units/test63-pr-30768.path
new file mode 100644 (file)
index 0000000..b541358
--- /dev/null
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Path]
+PathChanged=/tmp/copyme
diff --git a/test/testsuite-63.units/test63-pr-30768.service b/test/testsuite-63.units/test63-pr-30768.service
new file mode 100644 (file)
index 0000000..5739084
--- /dev/null
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStart=cp -v /tmp/copyme /tmp/copied
+# once cp exits, service goes into deactivating state and then runs ExecStop
+ExecStop=flock -e /tmp/noexit true
diff --git a/test/units/testsuite-07.type-exec-parallel.sh b/test/units/testsuite-07.type-exec-parallel.sh
new file mode 100755 (executable)
index 0000000..30f80c5
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Make sure that we never mistake a process starting but failing quickly for a process failing to start, with Type=exec.
+# See https://github.com/systemd/systemd/pull/30799
+
+seq 25 | xargs -n 1 -P 0 systemd-run -p Type=exec /bin/false
index 8a6fa84dc96ec70cb16795aa634f3cc5e23b3bb4..e984d585ca0d91399a3d5409f42862596e5b03b6 100755 (executable)
@@ -324,7 +324,7 @@ EOF
 }
 
 nspawn_settings_cleanup() {
-    for dev in sd-host-only sd-shared{1,2} sd-macvlan{1,2} sd-ipvlan{1,2}; do
+    for dev in sd-host-only sd-shared{1,2,3} sd-macvlan{1,2} sd-ipvlan{1,2}; do
         ip link del "$dev" || :
     done
 
@@ -332,7 +332,7 @@ nspawn_settings_cleanup() {
 }
 
 testcase_nspawn_settings() {
-    local root container dev private_users
+    local root container dev private_users wlan_names='' wlan_checks=''
 
     mkdir -p /run/systemd/nspawn
     root="$(mktemp -d /var/lib/machines/testsuite-13.nspawn-settings.XXX)"
@@ -341,10 +341,17 @@ testcase_nspawn_settings() {
     rm -f "/etc/systemd/nspawn/$container.nspawn"
     mkdir -p "$root/tmp" "$root"/opt/{tmp,inaccessible,also-inaccessible}
 
-    for dev in sd-host-only sd-shared{1,2} sd-macvlan{1,2} sd-macvlanloong sd-ipvlan{1,2} sd-ipvlanlooong; do
+    # add virtual wlan interfaces
+    if modprobe mac80211_hwsim radios=2; then
+        wlan_names='wlan0 wlan1:wl-renamed1'
+        wlan_checks='ip link | grep wlan0\nip link | grep wl-renamed1'
+    fi
+
+    for dev in sd-host-only sd-shared{1,2,3} sd-macvlan{1,2} sd-macvlanloong sd-ipvlan{1,2} sd-ipvlanlooong; do
         ip link add "$dev" type dummy
     done
     udevadm settle
+    ip link property add dev sd-shared3 altname sd-altname3 altname sd-altname-tooooooooooooo-long
     ip link
     trap nspawn_settings_cleanup RETURN
 
@@ -394,7 +401,7 @@ Private=yes
 VirtualEthernet=yes
 VirtualEthernetExtra=my-fancy-veth1
 VirtualEthernetExtra=fancy-veth2:my-fancy-veth2
-Interface=sd-shared1 sd-shared2:sd-shared2
+Interface=sd-shared1 sd-shared2:sd-renamed2 sd-shared3:sd-altname3 ${wlan_names}
 MACVLAN=sd-macvlan1 sd-macvlan2:my-macvlan2 sd-macvlanloong
 IPVLAN=sd-ipvlan1 sd-ipvlan2:my-ipvlan2 sd-ipvlanlooong
 Zone=sd-zone0
@@ -437,12 +444,17 @@ ip link | grep host0@
 ip link | grep my-fancy-veth1@
 ip link | grep my-fancy-veth2@
 ip link | grep sd-shared1
-ip link | grep sd-shared2
+ip link | grep sd-renamed2
+ip link | grep sd-shared3
+ip link | grep sd-altname3
+ip link | grep sd-altname-tooooooooooooo-long
 ip link | grep mv-sd-macvlan1@
 ip link | grep my-macvlan2@
 ip link | grep iv-sd-ipvlan1@
 ip link | grep my-ipvlan2@
 EOF
+    echo -e "$wlan_checks" >>"$root/entrypoint.sh"
+
     timeout 30 systemd-nspawn --directory="$root"
 
     # And now for stuff that needs to run separately
index 5c230c0a45b02da4f928d83ce6c8d00d8c245a32..e9af67f358a3a11fa0c2e84a548b5cd9128570ab 100755 (executable)
@@ -258,7 +258,7 @@ cleanup_session() (
 
     systemctl stop getty@tty2.service
 
-    for s in $(loginctl --no-legend list-sessions | grep tty | awk '$3 == "logind-test-user" { print $1 }'); do
+    for s in $(loginctl --no-legend list-sessions | grep -v manager | awk '$3 == "logind-test-user" { print $1 }'); do
         echo "INFO: stopping session $s"
         loginctl terminate-session "$s"
     done
@@ -308,18 +308,18 @@ check_session() (
 
     local seat session leader_pid
 
-    if [[ $(loginctl --no-legend | grep tty | grep -c "logind-test-user") != 1 ]]; then
+    if [[ $(loginctl --no-legend | grep -v manager | grep -c "logind-test-user") != 1 ]]; then
         echo "no session or multiple sessions for logind-test-user." >&2
         return 1
     fi
 
-    seat=$(loginctl --no-legend | grep tty | grep 'logind-test-user *seat' | awk '{ print $4 }')
+    seat=$(loginctl --no-legend | grep -v manager | grep 'logind-test-user *seat' | awk '{ print $4 }')
     if [[ -z "$seat" ]]; then
         echo "no seat found for user logind-test-user" >&2
         return 1
     fi
 
-    session=$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')
+    session=$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')
     if [[ -z "$session" ]]; then
         echo "no session found for user logind-test-user" >&2
         return 1
@@ -364,7 +364,7 @@ EOF
         check_session && break
     done
     check_session
-    assert_eq "$(loginctl --no-legend | grep tty | awk '$3=="logind-test-user" { print $5 }')" "tty2"
+    assert_eq "$(loginctl --no-legend | grep -v manager | awk '$3=="logind-test-user" { print $7 }')" "tty2"
 }
 
 testcase_sanity_check() {
@@ -382,6 +382,8 @@ testcase_sanity_check() {
     # the seat/session autodetection work-ish
     systemd-run --user --pipe --wait -M "logind-test-user@.host" bash -eux <<\EOF
     loginctl list-sessions
+    loginctl list-sessions -j
+    loginctl list-sessions --json=short
     loginctl session-status
     loginctl show-session
     loginctl show-session -P DelayInhibited
@@ -455,7 +457,7 @@ EOF
     udevadm info "$dev"
 
     # trigger logind and activate session
-    loginctl activate "$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')"
+    loginctl activate "$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')"
 
     # check ACL
     sleep 1
@@ -496,7 +498,7 @@ testcase_lock_idle_action() {
         return
     fi
 
-    if loginctl --no-legend | grep tty | grep -q logind-test-user; then
+    if loginctl --no-legend | grep -v manager | grep -q logind-test-user; then
         echo >&2 "Session of the 'logind-test-user' is already present."
         exit 1
     fi
@@ -545,7 +547,7 @@ testcase_session_properties() {
     trap cleanup_session RETURN
     create_session
 
-    s=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')
+    s=$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')
     /usr/lib/systemd/tests/unit-tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}" /dev/tty2
 }
 
@@ -561,17 +563,17 @@ testcase_list_users_sessions_seats() {
     create_session
 
     # Activate the session
-    loginctl activate "$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')"
+    loginctl activate "$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')"
 
-    session=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')
+    session=$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')
     : check that we got a valid session id
     busctl get-property org.freedesktop.login1 "/org/freedesktop/login1/session/_3${session?}" org.freedesktop.login1.Session Id
-    assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)"
-    seat=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $4 }')
-    assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $5 }')" tty2
-    assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $6 }')" active
-    assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $7 }')" no
-    assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $8 }')" '-'
+    assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)"
+    seat=$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $4 }')
+    assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $6 }')" user
+    assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $7 }')" tty2
+    assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $8 }')" no
+    assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $9 }')" '-'
 
     loginctl list-seats --no-legend | grep -Fwq "${seat?}"
 
@@ -625,7 +627,7 @@ EOF
     sleep 5
 
     assert_eq "$(journalctl -b -u systemd-logind.service --since="$ts" --grep "Session \"$id\" of user \"logind-test-user\" is idle, stopping." | wc -l)" 1
-    assert_eq "$(loginctl --no-legend | grep tty | grep -c "logind-test-user")" 0
+    assert_eq "$(loginctl --no-legend | grep -v manager | grep -c "logind-test-user")" 0
 }
 
 testcase_ambient_caps() {
index 07e6fc9b6236a504d6fc5088513c634bc200b90c..4f31a33c343a52ba9c7e24da45b592798b9d0eac 100755 (executable)
@@ -6,6 +6,11 @@ set -o pipefail
 # shellcheck source=test/units/util.sh
 . "$(dirname "$0")"/util.sh
 
+if [[ "$(sysctl -ne kernel.apparmor_restrict_unprivileged_userns)" -eq 1 ]]; then
+    echo "Cannot create unprivileged user namespaces" >/skipped
+    exit 0
+fi
+
 systemd-analyze log-level debug
 
 runas testuser systemd-run --wait --user --unit=test-private-users \
index 9bbd700104562428e2b9ab2f57c74f5d49620dcc..ea8cd945edde5ceec12d64e41d801b94c014841f 100755 (executable)
@@ -80,6 +80,46 @@ output=$(systemctl list-jobs --no-legend)
 assert_not_in "test63-issue-24577.service" "$output"
 assert_in "test63-issue-24577-dep.service" "$output"
 
+# Test for race condition fixed by https://github.com/systemd/systemd/pull/30768
+# Here's the schedule of events that we to happen during this test:
+#       (This test)                     (The service)
+#                                       .path unit monitors /tmp/copyme for changes
+#       Take lock on /tmp/noexeit       ↓
+#       Write to /tmp/copyme            ↓
+#       Wait for deactivating           Started
+#       ↓                               Copies /tmp/copyme to /tmp/copied
+#       ↓                               Tells manager it's shutting down
+#       Ensure service did the copy     Tries to lock /tmp/noexit and blocks
+#       Write to /tmp/copyme            ↓
+#
+# Now at this point the test can diverge. If we regress, this second write is
+# missed and we'll see:
+#       ... (second write)              ... (blocked)
+#       Drop lock on /tmp/noexit        ↓
+#       Wait for service to do copy     Unblocks and exits
+#       ↓                               (dead)
+#       ↓
+#       (timeout)
+#       Test fails
+#
+# Otherwise, we'll see:
+#       ... (second write)              ... (blocked)
+#       Drop lock on /tmp/noexit        ↓ and .path unit queues a new start job
+#       Wait for service to do copy     Unblocks and exits
+#       ↓                               Starts again b/c of queued job
+#       ↓                               Copies again
+#       Test Passes
+systemctl start test63-pr-30768.path
+exec {lock}<>/tmp/noexit
+flock -e $lock
+echo test1 > /tmp/copyme
+# shellcheck disable=SC2016
+timeout 30 bash -c 'until test "$(systemctl show test63-pr-30768.service -P ActiveState)" = deactivating; do sleep .2; done'
+diff /tmp/copyme /tmp/copied
+echo test2 > /tmp/copyme
+exec {lock}<&-
+timeout 30 bash -c 'until diff /tmp/copyme /tmp/copied; do sleep .2; done'
+
 systemctl log-level info
 
 touch /testok
index bf87a9bd3a62a226fc9c852c2818002890c10b10..3372ec0b068c813acf1cb769393f225b042224ba 100755 (executable)
@@ -24,6 +24,8 @@ if [[ -v ASAN_OPTIONS ]] ; then
 fi
 
 ROOTID=$(mktemp -u)
+# Needed on Ubuntu/Debian as we copy binaries manually
+mkdir -p /run/sshd
 
 removesshid() {
     rm -f "$ROOTID" "$ROOTID".pub
index f1fb5d943a5ff5b18ab62cb1511dd069b535ced6..92f6b8e030dd7cc0a891b4a38717948b27e5cc2f 100755 (executable)
@@ -371,6 +371,12 @@ run dig +noall +authority +comments SRV .
 grep -qF "status: NOERROR" "$RUN_OUT"
 grep -qE "IN\s+SOA\s+ns1\.unsigned\.test\." "$RUN_OUT"
 
+run resolvectl query -t SVCB svcb.test
+grep -qF 'alpn="dot"' "$RUN_OUT"
+grep -qF "ipv4hint=10.0.0.1" "$RUN_OUT"
+
+run resolvectl query -t HTTPS https.test
+grep -qF 'alpn="h2,h3"' "$RUN_OUT"
 
 : "--- ZONE: unsigned.test. ---"
 run dig @ns1.unsigned.test +short unsigned.test A unsigned.test AAAA
index 25cd7a0ff952fe9fdc5561d5d109a200197b3508..c43195bc076d3f2686c2d8675dd9c7888e7552d8 100644 (file)
@@ -8,7 +8,7 @@
 #  (at your option) any later version.
 
 [Unit]
-Description=Hibernate
+Description=System Hibernate
 Documentation=man:systemd-hibernate.service(8)
 DefaultDependencies=no
 Requires=sleep.target
index fe57f57fae6d006f34db1f67d524b47f5e9cc5e1..c85215bdacfd192a59c7f62f2905a3d630b79758 100644 (file)
@@ -8,7 +8,7 @@
 #  (at your option) any later version.
 
 [Unit]
-Description=Hybrid Suspend+Hibernate
+Description=System Hybrid Suspend+Hibernate
 Documentation=man:systemd-hybrid-sleep.service(8)
 DefaultDependencies=no
 Requires=sleep.target
index 150d8d2c239b119f13a53e8977d6ebb4b3e209d1..d7ab2c195e597b68a5d53f5e7e488a6737f3f762 100644 (file)
@@ -8,7 +8,7 @@
 #  (at your option) any later version.
 
 [Unit]
-Description=Suspend; Hibernate if not used for a period of time
+Description=System Suspend then Hibernate
 Documentation=man:systemd-suspend-then-hibernate.service(8)
 DefaultDependencies=no
 Requires=sleep.target